Merge branch 'v3_openjdk' into forge-1.17-wip

This commit is contained in:
khanhduytran0 2022-05-09 09:44:13 +07:00
commit 3888ddf2e1
252 changed files with 9212 additions and 9758 deletions

View file

@ -22,7 +22,7 @@ body:
Tip: You can attach files by clicking this area to highlight it and then dragging files in or select them on 🖼 option at the toolbar.
validations:
required: false
required: true
- type: textarea
attributes:

View file

@ -26,7 +26,7 @@ jobs:
- name: Get gl4es latest commit hash
id: gl4es-sha
run: echo "::set-output name=sha::$(echo $(git ls-remote https://github.com/PojavLauncherTeam/gl4es refs/heads/master | grep -io '^\S*' && git ls-remote https://github.com/ptitSeb/gl4es refs/heads/master | grep -io '^\S*'))"
run: echo "::set-output name=sha::$(echo $(git ls-remote https://github.com/PojavLauncherTeam/gl4es-114-extra refs/heads/master | grep -io '^\S*'))"
shell: bash
- name: Cache gl4es
@ -34,13 +34,13 @@ jobs:
id: gl4es-cache
with:
path: gl4es/libs
key: gl4es-android-shared-nodbg-test1-2-${{ steps.gl4es-sha.outputs.sha }}
key: gl4es-holy-android-shared-${{ steps.gl4es-sha.outputs.sha }}
- name: Get gl4es
if: steps.gl4es-cache.outputs.cache-hit != 'true'
uses: actions/checkout@v2
with:
repository: 'ptitSeb/gl4es'
repository: 'PojavLauncherTeam/gl4es-114-extra'
path: 'gl4es'
- name: Build gl4es
@ -50,56 +50,15 @@ jobs:
cd gl4es
git config --global user.email "github-actions@users.noreply.github.com"
git config --global user.name "github-actions"
git remote add upstream https://github.com/PojavLauncherTeam/gl4es
git fetch upstream
git checkout master
git merge --allow-unrelated-histories upstream/master || echo "Merge exit code $?"
$ANDROID_NDK_HOME/ndk-build NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=./Android.mk
cp -R libs/* ../app_pojavlauncher/src/main/jniLibs/
- name: Install gl4es
if: steps.gl4es-cache.outputs.cache-hit != 'true'
run: |
cp -R gl4es/libs/* app_pojavlauncher/src/main/jniLibs/
mv gl4es ..
git add .
git commit -am "CI: Update gl4es"
git push
- name: Get vgpu latest commit hash
id: vgpu-sha
run: echo "::set-output name=sha::$(echo $(git ls-remote https://github.com/PojavLauncherTeam/VGPU refs/heads/main | grep -io '^\S*'))"
shell: bash
- name: Cache vgpu
uses: actions/cache@v2
id: vgpu-cache
with:
path: vgpu/libs
key: vgpu-android-shared-1-${{ steps.vgpu-sha.outputs.sha }}
- name: Get vgpu
if: steps.vgpu-cache.outputs.cache-hit != 'true'
uses: actions/checkout@v2
with:
repository: 'PojavLauncherTeam/VGPU'
path: 'vgpu'
- name: Build vgpu
if: steps.vgpu-cache.outputs.cache-hit != 'true'
- name: Push gl4es
if: github.event != 'pull_request' && steps.gl4es-cache.outputs.cache-hit != 'true'
continue-on-error: true
run: |
cd vgpu
$ANDROID_NDK_HOME/ndk-build NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=./Android.mk
- name: Install vgpu
if: steps.vgpu-cache.outputs.cache-hit != 'true'
run: |
cp -R vgpu/libs/* app_pojavlauncher/src/main/jniLibs/
mv vgpu ..
git config --global user.email "github-actions@users.noreply.github.com"
git config --global user.name "github-actions"
git add .
git commit -am "CI: Update vgpu"
git commit -am "CI: Update gl4es"
git push
- name: Get JRE8
@ -112,9 +71,11 @@ jobs:
repo: PojavLauncherTeam/android-openjdk-build-multiarch
branch: buildjre8
name: jre8-pojav
run_id: 2140559381
- name: Build APK with Gradle
- name: Build JRE JAR files
run: |
cp -R gl4es/libs/* app_pojavlauncher/src/main/jniLibs/
chmod +x scripts/languagelist_updater.sh
bash scripts/languagelist_updater.sh
@ -123,35 +84,41 @@ jobs:
./gradlew clean
# Build JRE JAR files (security manager, etc...)
./gradlew :jre_lwjgl3glfw:build
# mkdir app_pojavlauncher/src/main/assets/components/internal_libs
rm app_pojavlauncher/src/main/assets/components/lwjgl3/lwjgl-glfw-classes.jar
cp jre_lwjgl3glfw/build/libs/jre_lwjgl3glfw-3.2.3.jar app_pojavlauncher/src/main/assets/components/lwjgl3/lwjgl-glfw-classes.jar
- name: Build Google Play .aab
if: github.repository_owner == 'PojavLauncherTeam' && github.ref_name == 'v3_openjdk'
run: |
./gradlew :app_pojavlauncher:bundleGplay
mv app_pojavlauncher/build/outputs/bundle/gplay/app_pojavlauncher-gplay.aab out/app-gplay.aab
- name: Build Debug .apk
run: |
# Build the launcher
./gradlew :app_pojavlauncher:assembleDebug
mv app_pojavlauncher/build/outputs/apk/debug/app_pojavlauncher-debug.apk out/app-debug.apk
# mv app_pojavlauncher/build/intermediates/merged_native_libs/debug/out/lib out/debug_lib
- name: Build APK without runtime
run: |
rm -r app_pojavlauncher/src/main/assets/components/jre
./gradlew assembleDebug
mv app_pojavlauncher/build/outputs/apk/debug/app_pojavlauncher-debug.apk out/app-debug-noruntime.apk
- name: Upload APK
uses: actions/upload-artifact@v2
with:
name: app-debug
path: out/app-debug.apk
# - name: Upload onto the Google Play testing track
# uses: r0adkll/upload-google-play@v1.0.15
# with:
# serviceAccountJsonPlainText: ${{ secrets.GPLAY_SERVICE_JSON }}
# packageName: net.kdt.pojavlaunch
# releaseFiles: out/app-gplay.aab
# track: internal
# inAppUpdatePriority: 5
- name: Upload onto the Google Play testing track
if: github.repository_owner == 'PojavLauncherTeam' && github.ref_name == 'v3_openjdk'
uses: r0adkll/upload-google-play@v1.0.15
with:
serviceAccountJsonPlainText: ${{ secrets.GPLAY_SERVICE_JSON }}
packageName: net.kdt.pojavlaunch
releaseFiles: out/app-gplay.aab
track: internal
inAppUpdatePriority: 5
- name: Upload APK (without runtime)
uses: actions/upload-artifact@v2
with:

View file

@ -2,7 +2,7 @@
[![Crowdin](https://badges.crowdin.net/pojavlauncher/localized.svg)](https://crowdin.com/project/pojavlauncher)
[![Discord](https://img.shields.io/discord/724163890803638273.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/6RpEJda)
[![Reddit](https://img.shields.io/badge/dynamic/json.svg?label=r/PojavLauncher%20member%20count&query=$.data.subscribers&url=https://www.reddit.com/r/PojavLauncher/about.json)](https://reddit.com/r/PojavLauncher)
[![Google Play](https://gist.githubusercontent.com/meefik/54a54afa7cc1dc600bdb855cb7895a4a/raw/ad617c006a1ac28d067c9a87cec60199ca8fef7c/get-it-on-google-play.png)](https://play.google.com/store/apps/details?id=net.kdt.pojavlaunch)
[![Google Play](https://gist.githubusercontent.com/meefik/54a54afa7cc1dc600bdb855cb7895a4a/raw/ad617c006a1ac28d067c9a87cec60199ca8fef7c/get-it-on-google-play.png)](https://play.google.com/store/apps/details?id=net.kdt.pojavlaunch)
# PojavLauncher
@ -10,16 +10,16 @@
- We do not exist on TikTok. No one from the dev team makes TikTok videos.
- The official Twitter for PojavLauncher is [@PLaunchTeam](https://twitter.com/PLaunchTeam). Any others (most notably @PojavLauncher) are fake, please report them to Twitter's moderation team.
## Navigation
## Navigation
- [Introduction](#introduction)
- [Building](#building)
- [Current status](#current-status)
- [License](#license)
- [License](#license)
- [Contributing](#contributing)
- [Credits & Third party components and their licenses](#credits--third-party-components-and-their-licenses)
## Introduction
PojavLauncher is a Minecraft: Java Edition launcher for Android and iOS based on [Boardwalk](https://github.com/zhuowei/Boardwalk). This launcher can launch almost all available Minecraft versions (from rd-132211 to 21w08b (1.17) snapshot, including Combat Test versions). Modding via Forge and Fabric are also supported. This repository contains source code for Android. For iOS/iPadOS, check out [PojavLauncher_iOS](https://github.com/PojavLauncherTeam/PojavLauncher_iOS).
PojavLauncher is a Minecraft: Java Edition launcher for Android and iOS based on [Boardwalk](https://github.com/zhuowei/Boardwalk). This launcher can launch almost all available Minecraft versions (from rd-132211 to 1.18 snapshots (kinda)), including Combat Test versions. Modding via Forge (up to 1.16.5) and Fabric are also supported. This repository contains source code for Android. For iOS/iPadOS, check out [PojavLauncher_iOS](https://github.com/PojavLauncherTeam/PojavLauncher_iOS).
## Building
To get started, you can just get prebuilt app from [stable release](https://github.com/PojavLauncherTeam/PojavLauncher/releases) or [automatic builds](https://github.com/PojavLauncherTeam/PojavLauncher/actions). If you want to build after launcher code changes, follow steps below.
@ -57,9 +57,6 @@ scripts\languagelist_updater.bat
```
# Build GLFW stub
./gradlew :jre_lwjgl3glfw:build
# mkdir app_pojavlauncher/src/main/assets/components/internal_libs
rm app_pojavlauncher/src/main/assets/components/lwjgl3/lwjgl-glfw-classes.jar
cp jre_lwjgl3glfw/build/libs/jre_lwjgl3glfw-3.2.3.jar app_pojavlauncher/src/main/assets/components/lwjgl3/lwjgl-glfw-classes.jar
# Build the launcher
./gradlew :app_pojavlauncher:assembleDebug
@ -80,13 +77,13 @@ cp jre_lwjgl3glfw/build/libs/jre_lwjgl3glfw-3.2.3.jar app_pojavlauncher/src/main
- [ ] More...
## Known Issues
- Minecraft `21w10a` or newer are currently not yet supported due to the new GLSL usage.
- In 1.16 and up spawn eggs banners are white (you can fix this by switching renderer
to `gl4es 1.1.5`, only works on 1.16 and up, do not use under this version)
- Controller mods aren't working
- Minecraft `21w10a` or newer are currently not yet supported due to the new GLSL usage. Fortunately, a workaround is provided and built into the launcher.
- In 1.16 and up, spawn eggs banners are white (you can fix this by switching renderer
to `gl4es 1.1.5`, only works on 1.16 and up, do not use under this version, as the texture
will bug out when hit a mob)
- Controller mods aren't working.
- Random crashes could happen very often on Android 5.x during game load or join world.
- With big modpacks textures could be messed up
- If you're using gl4es 1.1.5 on 1.16 and lower texture will bug out when hit a mob
- probably more, that's why we have a bug tracker ;)
## License
@ -96,13 +93,16 @@ to `gl4es 1.1.5`, only works on 1.16 and up, do not use under this version)
Contributions are welcome! We welcome any type of contribution, not only code.
Any code change should be submitted as a pull request. The description should explain what the code does and give steps to execute it.
## Credits & Third party components and their licenses
## Credits & Third party components and their licenses (if available)
- [Boardwalk](https://github.com/zhuowei/Boardwalk) (JVM Launcher): Unknown License/[Apache License 2.0](https://github.com/zhuowei/Boardwalk/blob/master/LICENSE) or GNU GPLv2.
- Android Support Libraries: [Apache License 2.0](https://android.googlesource.com/platform/prebuilts/maven_repo/android/+/master/NOTICE.txt).
- [GL4ES](https://github.com/ptitSeb/gl4es): [MIT License](https://github.com/ptitSeb/gl4es/blob/master/LICENSE).<br>
- [GL4ES](https://github.com/PojavLauncherTeam/gl4es): [MIT License](https://github.com/ptitSeb/gl4es/blob/master/LICENSE).<br>
- [OpenJDK](https://github.com/PojavLauncherTeam/openjdk-multiarch-jdk8u): [GNU GPLv2 License](https://openjdk.java.net/legal/gplv2+ce.html).<br>
- [LWJGL3](https://github.com/PojavLauncherTeam/lwjgl3): [BSD-3 License](https://github.com/LWJGL/lwjgl3/blob/master/LICENSE.md).
- [LWJGLX](https://github.com/PojavLauncherTeam/lwjglx) (LWJGL2 API compatibility layer for LWJGL3): unknown license.<br>
- [Mesa 3D Graphics Library](https://gitlab.freedesktop.org/mesa/mesa): [MIT License](https://docs.mesa3d.org/license.html).
- [pro-grade](https://github.com/pro-grade/pro-grade) (Java sandboxing security manager): [Apache License 2.0](https://github.com/pro-grade/pro-grade/blob/master/LICENSE.txt).
- [xHook](https://github.com/iqiyi/xHook) (Used for exit code trapping): [MIT and BSD-style licenses](https://github.com/iqiyi/xHook/blob/master/LICENSE)
- [xHook](https://github.com/iqiyi/xHook) (Used for exit code trapping): [MIT and BSD-style licenses](https://github.com/iqiyi/xHook/blob/master/LICENSE).
- [libepoxy](https://github.com/anholt/libepoxy): [MIT License](https://github.com/anholt/libepoxy/blob/master/COPYING).
- [virglrenderer](https://github.com/PojavLauncherTeam/virglrenderer): [MIT License](https://gitlab.freedesktop.org/virgl/virglrenderer/-/blob/master/COPYING).
- Thanks to [MCHeads](https://mc-heads.net) for providing Minecraft avatars.

View file

@ -1,16 +1,16 @@
apply plugin: 'com.android.application'
def getDate() { return new Date().format('yyyyMMdd')}
static def getDate() { return new Date().format('yyyyMMdd')}
def getDateSeconds() {
if(System.getenv("GITHUB_ACTIONS").equals("true")) {
static def getDateSeconds() {
if(System.getenv("GITHUB_ACTIONS") == "true") {
return 9934841+Integer.parseInt(System.getenv("GITHUB_RUN_NUMBER"))
}else{
return 172005
}
}
def getVersionName = {
def getVersionName = {
// Get the last version tag, as well as the short head of the last commit
ByteArrayOutputStream TAG = new ByteArrayOutputStream()
ByteArrayOutputStream BRANCH = new ByteArrayOutputStream()
@ -37,7 +37,7 @@ def getVersionName = {
}
android {
compileSdkVersion 30
compileSdkVersion 31
dexOptions {
javaMaxHeapSize "4g"
}
@ -74,10 +74,16 @@ android {
debug {
applicationIdSuffix '.debug'
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
shrinkResources false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.customDebug
resValue 'string', 'storageProviderAuthorities', 'net.kdt.pojavlaunch.scoped.gamefolder.debug'
}
proguard {
initWith debug
minifyEnabled true
shrinkResources true
}
release {
// Don't set to true or java.awt will be a.a or something similar.
@ -116,17 +122,14 @@ dependencies {
implementation 'javax.annotation:javax.annotation-api:1.3.2'
implementation 'commons-codec:commons-codec:1.14'
// implementation 'com.wu-man:android-bsf-api:3.1.3'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.preference:preference:1.1.1'
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'androidx.legacy:legacy-preference-v14:1.0.0'
implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.annotation:annotation:1.2.0'
implementation 'androidx.browser:browser:1.3.0'
//implementation 'androidx.core:core:1.7.0'
implementation 'androidx.drawerlayout:drawerlayout:1.1.1'
implementation 'androidx.viewpager2:viewpager2:1.0.0'
implementation 'androidx.annotation:annotation:1.3.0'
implementation "androidx.constraintlayout:constraintlayout:2.0.4"
implementation "androidx.constraintlayout:constraintlayout:2.1.2"
implementation 'com.rarepebble:colorpicker:3.0.1'
implementation 'com.github.duanhong169:checkerboarddrawable:1.0.2'
@ -134,8 +137,7 @@ dependencies {
// implementation 'com.intuit.ssp:ssp-android:1.0.5'
implementation 'org.tukaani:xz:1.8'
implementation 'net.objecthunter:exp4j:0.4.8'
implementation 'com.github.PojavLauncherTeam:exp4j:master-SNAPSHOT'
// implementation 'net.sourceforge.streamsupport:streamsupport-cfuture:1.7.0'
implementation fileTree(dir: 'libs', include: ['*.jar'])
}

View file

@ -15,4 +15,13 @@
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
-keep class !android.support.v7.internal.view.menu.**,android.support.** {*;}
# We use Reflection on the builder to avoid creating too many objects
-keep class net.objecthunter.exp4j.ExpressionBuilder**
-keepclassmembers class net.objecthunter.exp4j.ExpressionBuilder** {
*;
}
# Option screens
-keep class net.kdt.pojavlaunch.prefs.screens** {*;}

View file

@ -10,6 +10,7 @@
android:maxSdkVersion="28"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<application
android:allowBackup="true"
@ -59,12 +60,12 @@
</activity>
<activity
android:theme="@style/MenuDialog"
android:theme="@style/Theme.AppCompat.DayNight.Dialog"
android:screenOrientation="sensorLandscape"
android:name=".FatalErrorActivity"
android:configChanges="keyboardHidden|orientation|screenSize|keyboard|navigation"/>
<activity
android:theme="@style/MenuDialog"
android:theme="@style/Theme.AppCompat.DayNight.Dialog"
android:screenOrientation="sensorLandscape"
android:name=".ExitActivity"
android:configChanges="keyboardHidden|orientation|screenSize|keyboard|navigation"/>
@ -90,8 +91,7 @@
android:multiprocess="true"
android:screenOrientation="sensorLandscape"
android:name=".MainActivity"
android:configChanges="keyboardHidden|orientation|screenSize|keyboard|navigation"/>
android:configChanges="keyboardHidden|orientation|screenSize|smallestScreenSize|screenLayout|keyboard|navigation"/>
<provider
android:name=".scoped.GameFolderProvider"
android:authorities="@string/storageProviderAuthorities"

Binary file not shown.

View file

@ -1 +1 @@
20210430
HEllo People

View file

@ -1 +1 @@
20210827
1652063179136

View file

@ -5,15 +5,15 @@ import android.graphics.Rect;
import android.util.AttributeSet;
import android.widget.ScrollView;
/**
Class allowing to ignore the focusing from an item such an EditText within it.
Ignoring it will stop the scrollView from refocusing on the view
*/
public class DefocusableScrollView extends ScrollView {
/*
What is this class for ?
It allows to ignore the focusing from an item such an EditText.
Ignoring it will stop the scrollView from refocusing on the view
*/
private boolean keepFocusing = false;
private boolean mKeepFocusing = false;
public DefocusableScrollView(Context context) {
@ -33,16 +33,16 @@ public class DefocusableScrollView extends ScrollView {
}
public void setKeepFocusing(boolean shouldKeepFocusing){
keepFocusing = shouldKeepFocusing;
mKeepFocusing = shouldKeepFocusing;
}
public boolean isKeepFocusing(){
return keepFocusing;
return mKeepFocusing;
}
@Override
protected int computeScrollDeltaToGetChildRectOnScreen(Rect rect) {
if(!keepFocusing) return 0;
if(!mKeepFocusing) return 0;
return super.computeScrollDeltaToGetChildRectOnScreen(rect);
}

View file

@ -0,0 +1,86 @@
package com.kdt;
import android.content.Context;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageButton;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.ToggleButton;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.constraintlayout.widget.ConstraintLayout;
import net.kdt.pojavlaunch.Logger;
import net.kdt.pojavlaunch.R;
/**
* A class able to display logs to the user.
* It has support for the Logger class
*/
public class LoggerView extends ConstraintLayout {
private Logger.eventLogListener mLogListener;
private ToggleButton mToggleButton;
private ScrollView mScrollView;
private TextView mLogTextView;
public LoggerView(@NonNull Context context) {
this(context, null);
}
public LoggerView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
@Override
public void setVisibility(int visibility) {
super.setVisibility(visibility);
// Triggers the log view shown state by default when viewing it
mToggleButton.setChecked(visibility == VISIBLE);
}
/**
* Inflate the layout, and add component behaviors
*/
private void init(){
inflate(getContext(), R.layout.view_logger, this);
mLogTextView = findViewById(R.id.content_log_view);
mLogTextView.setTypeface(Typeface.MONOSPACE);
//TODO clamp the max text so it doesn't go oob
mLogTextView.setMaxLines(Integer.MAX_VALUE);
mLogTextView.setEllipsize(null);
mLogTextView.setVisibility(GONE);
// Toggle log visibility
mToggleButton = findViewById(R.id.content_log_toggle_log);
mToggleButton.setOnCheckedChangeListener(
(compoundButton, isChecked) -> {
mLogTextView.setVisibility(isChecked ? VISIBLE : GONE);
if(!isChecked) mLogTextView.setText("");
});
mToggleButton.setChecked(false);
// Remove the loggerView from the user View
ImageButton cancelButton = findViewById(R.id.log_view_cancel);
cancelButton.setOnClickListener(view -> LoggerView.this.setVisibility(GONE));
// Set the scroll view
mScrollView = findViewById(R.id.content_log_scroll);
// Listen to logs
mLogListener = text -> {
if(mLogTextView.getVisibility() != VISIBLE) return;
post(() -> {
mLogTextView.append(text + '\n');
mScrollView.fullScroll(View.FOCUS_DOWN);
});
};
Logger.getInstance().setLogListener(mLogListener);
}
}

View file

@ -8,8 +8,7 @@ import androidx.core.content.res.ResourcesCompat;
import net.kdt.pojavlaunch.R;
public class MineButton extends androidx.appcompat.widget.AppCompatButton
{
public class MineButton extends androidx.appcompat.widget.AppCompatButton {
public MineButton(Context ctx) {
this(ctx, null);

View file

@ -3,23 +3,20 @@ package com.kdt.mcgui;
import android.content.*;
import android.util.*;
import android.graphics.*;
import android.widget.EditText;
public class MineEditText extends com.google.android.material.textfield.TextInputEditText
{
public MineEditText(Context ctx)
{
public class MineEditText extends androidx.appcompat.widget.AppCompatEditText {
public MineEditText(Context ctx) {
super(ctx);
init();
}
public MineEditText(Context ctx, AttributeSet attrs)
{
public MineEditText(Context ctx, AttributeSet attrs) {
super(ctx, attrs);
init();
}
public void init()
{
public void init() {
setBackgroundColor(Color.parseColor("#131313"));
setPadding(5, 5, 5, 5);
}

View file

@ -5,61 +5,26 @@ import android.graphics.*;
import android.text.*;
import android.util.*;
import android.view.*;
import android.widget.RelativeLayout;
import java.util.*;
import net.kdt.pojavlaunch.utils.*;
import org.lwjgl.glfw.*;
public class AWTCanvasView extends TextureView implements TextureView.SurfaceTextureListener, Runnable {
private int mScaleFactor;
private int[] mScales;
private int mWidth, mHeight;
private boolean mIsDestroyed = false;
private TextPaint fpsPaint;
private boolean attached = false;
// Temporary count fps https://stackoverflow.com/a/13729241
private LinkedList<Long> times = new LinkedList<Long>(){{add(System.nanoTime());}};
public static final int AWT_CANVAS_WIDTH = 600;
public static final int AWT_CANVAS_HEIGHT = 420;
private final int MAX_SIZE = 100;
private final double NANOS = 1000000000.0;
/** Calculates and returns frames per second */
private double fps() {
long lastTime = System.nanoTime();
double difference = (lastTime - times.getFirst()) / NANOS;
times.addLast(lastTime);
int size = times.size();
if (size > MAX_SIZE) {
times.removeFirst();
}
return difference > 0 ? times.size() / difference : 0.0;
}
private int mWidth, mHeight;
private boolean mIsDestroyed = false;
private final TextPaint mFpsPaint;
private boolean mAttached = false;
private boolean mDrawing;
/** Computes the scale to better fit the screen */
void initScaleFactors(){
initScaleFactors(0);
}
void initScaleFactors(int forcedScale){
//Could be optimized
if(forcedScale < 1) { //Auto scale
int minDimension = Math.min(CallbackBridge.physicalHeight, CallbackBridge.physicalWidth);
mScaleFactor = Math.max(((3 * minDimension) / 1080) - 1, 1);
}else{
mScaleFactor = forcedScale;
}
int[] scales = new int[2]; //Left, Top
scales[0] = (CallbackBridge.physicalWidth/2);
scales[0] -= scales[0]/mScaleFactor;
scales[1] = (CallbackBridge.physicalHeight/2);
scales[1] -= scales[1]/mScaleFactor;
mScales = scales;
}
// Temporary count fps https://stackoverflow.com/a/13729241
private final LinkedList<Long> mTimes = new LinkedList<Long>(){{add(System.nanoTime());}};
public AWTCanvasView(Context ctx) {
this(ctx, null);
@ -67,21 +32,23 @@ public class AWTCanvasView extends TextureView implements TextureView.SurfaceTex
public AWTCanvasView(Context ctx, AttributeSet attrs) {
super(ctx, attrs);
// setWillNotDraw(false);
fpsPaint = new TextPaint();
fpsPaint.setColor(Color.WHITE);
fpsPaint.setTextSize(20);
mFpsPaint = new TextPaint();
mFpsPaint.setColor(Color.WHITE);
mFpsPaint.setTextSize(20);
setSurfaceTextureListener(this);
initScaleFactors();
post(this::refreshSize);
}
@Override
public void onSurfaceTextureAvailable(SurfaceTexture texture, int w, int h) {
getSurfaceTexture().setDefaultBufferSize(AWT_CANVAS_WIDTH, AWT_CANVAS_HEIGHT);
mWidth = w;
mHeight = h;
mIsDestroyed = false;
new Thread(this, "AndroidAWTRenderer").start();
}
@ -94,51 +61,71 @@ public class AWTCanvasView extends TextureView implements TextureView.SurfaceTex
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture texture, int w, int h) {
getSurfaceTexture().setDefaultBufferSize(AWT_CANVAS_WIDTH, AWT_CANVAS_HEIGHT);
mWidth = w;
mHeight = h;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture texture) {
getSurfaceTexture().setDefaultBufferSize(AWT_CANVAS_WIDTH, AWT_CANVAS_HEIGHT);
}
private boolean mDrawing;
private Surface mSurface;
@Override
public void run() {
Canvas canvas;
mSurface = new Surface(getSurfaceTexture());
Surface surface = new Surface(getSurfaceTexture());
try {
while (!mIsDestroyed && mSurface.isValid()) {
canvas = mSurface.lockCanvas(null);
while (!mIsDestroyed && surface.isValid()) {
canvas = surface.lockCanvas(null);
canvas.drawRGB(0, 0, 0);
if (!attached) {
attached = CallbackBridge.nativeAttachThreadToOther(true, BaseMainActivity.isInputStackCall);
if (!mAttached) {
mAttached = CallbackBridge.nativeAttachThreadToOther(true, BaseMainActivity.isInputStackCall);
} else {
int[] rgbArray = JREUtils.renderAWTScreenFrame(/* canvas, mWidth, mHeight */);
mDrawing = rgbArray != null;
if (rgbArray != null) {
canvas.save();
canvas.scale(mScaleFactor, mScaleFactor);
canvas.translate(-mScales[0],-mScales[1]);
canvas.drawBitmap(rgbArray, 0, CallbackBridge.physicalWidth, 0, 0, CallbackBridge.physicalWidth, CallbackBridge.physicalHeight, true, null);
canvas.drawBitmap(rgbArray, 0, AWT_CANVAS_WIDTH, 0, 0, AWT_CANVAS_WIDTH, AWT_CANVAS_HEIGHT, true, null);
canvas.restore();
}
rgbArray = null;
// System.gc();
}
canvas.drawText("FPS: " + (Math.round(fps() * 10) / 10) + ", attached=" + attached + ", drawing=" + mDrawing, 50, 50, fpsPaint);
mSurface.unlockCanvasAndPost(canvas);
canvas.drawText("FPS: " + (Math.round(fps() * 10) / 10) + ", attached=" + mAttached + ", drawing=" + mDrawing, 0, 20, mFpsPaint);
surface.unlockCanvasAndPost(canvas);
}
} catch (Throwable th) {
Tools.showError(getContext(), th);
} catch (Throwable throwable) {
Tools.showError(getContext(), throwable);
}
surface.release();
}
/** Calculates and returns frames per second */
private double fps() {
long lastTime = System.nanoTime();
double difference = (lastTime - mTimes.getFirst()) / NANOS;
mTimes.addLast(lastTime);
int size = mTimes.size();
if (size > MAX_SIZE) {
mTimes.removeFirst();
}
return difference > 0 ? mTimes.size() / difference : 0.0;
}
/** Make the view fit the proper aspect ratio of the surface */
private void refreshSize(){
ViewGroup.LayoutParams layoutParams = getLayoutParams();
if(getHeight() < getWidth()){
layoutParams.width = AWT_CANVAS_WIDTH * getHeight() / AWT_CANVAS_HEIGHT;
}else{
layoutParams.height = AWT_CANVAS_HEIGHT * getWidth() / AWT_CANVAS_WIDTH;
}
setLayoutParams(layoutParams);
}
}

View file

@ -15,6 +15,10 @@ public class AWTInputBridge {
// TODO: Android -> AWT keycode mapping
nativeSendData(EVENT_TYPE_KEY, (int) keychar, keycode, 0, 0);
}
public static void sendChar(char keychar){
nativeSendData(EVENT_TYPE_CHAR, (int) keychar, 0, 0, 0);
}
public static void sendMousePress(int awtButtons, boolean isDown) {
nativeSendData(EVENT_TYPE_MOUSE_BUTTON, awtButtons, isDown ? 1 : 0, 0, 0);

View file

@ -24,8 +24,7 @@ package net.kdt.pojavlaunch;
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
public class AWTInputEvent
{
public class AWTInputEvent {
// InputEvent
/**
* This flag indicates that the Shift key was down when the event

View file

@ -7,8 +7,7 @@ import net.kdt.pojavlaunch.utils.*;
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_IGNORE_NOTCH;
public class BaseActivity extends AppCompatActivity
{
public abstract class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -24,7 +23,7 @@ public class BaseActivity extends AppCompatActivity
@Override
public void startActivity(Intent i) {
super.startActivity(i);
new Throwable("StartActivity").printStackTrace();
//new Throwable("StartActivity").printStackTrace();
}
@Override

View file

@ -1,98 +1,75 @@
package net.kdt.pojavlaunch;
import static net.kdt.pojavlaunch.Tools.getFileName;
import android.app.*;
import android.content.*;
import android.database.Cursor;
import android.net.Uri;
import android.provider.OpenableColumns;
import android.text.*;
import android.text.method.*;
import android.view.*;
import android.webkit.MimeTypeMap;
import android.widget.*;
import androidx.annotation.Nullable;
import androidx.appcompat.app.*;
import com.kdt.pickafile.*;
import java.io.*;
import net.kdt.pojavlaunch.fragments.*;
import java.util.ArrayList;
import java.util.Map;
import net.kdt.pojavlaunch.extra.ExtraCore;
import net.kdt.pojavlaunch.multirt.MultiRTConfigDialog;
import net.kdt.pojavlaunch.multirt.MultiRTUtils;
import net.kdt.pojavlaunch.prefs.*;
import net.kdt.pojavlaunch.extra.ExtraConstants;
import net.kdt.pojavlaunch.tasks.*;
import androidx.appcompat.app.AlertDialog;
import net.kdt.pojavlaunch.value.*;
import net.kdt.pojavlaunch.value.launcherprofiles.LauncherProfiles;
import net.kdt.pojavlaunch.value.launcherprofiles.MinecraftProfile;
import org.apache.commons.io.IOUtils;
public abstract class BaseLauncherActivity extends BaseActivity {
public Button mPlayButton;
public ConsoleFragment mConsoleView;
public CrashFragment mCrashView;
public ProgressBar mLaunchProgress;
public Spinner mVersionSelector;
public MultiRTConfigDialog mRuntimeConfigDialog;
public TextView mLaunchTextStatus, mTextVersion;
public TextView mLaunchTextStatus;
public JMinecraftVersionList mVersionList;
public MinecraftDownloaderTask mTask;
public MinecraftAccount mProfile;
public String[] mAvailableVersions;
//public String[] mAvailableVersions;
public boolean mIsAssetsProcessing = false;
protected boolean canBack = false;
public abstract void statusIsLaunching(boolean isLaunching);
public void mcaccLogout(View view) {
//PojavProfile.reset();
finish();
/**
* Used by the custom control button from the layout_main_v4
* @param view The view triggering the function
*/
public void launchCustomControlsActivity(View view){
startActivity(new Intent(BaseLauncherActivity.this, CustomControlsActivity.class));
}
public void launcherMenu(View view) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.mcl_options);
builder.setItems(R.array.mcl_options, new DialogInterface.OnClickListener(){
@Override
public void onClick(DialogInterface p1, int p2)
{
switch (p2) {
case 0: // Mod installer
installMod(false);
break;
case 1: // Mod installer with java args
installMod(true);
break;
case 2: // Custom controls
startActivity(new Intent(BaseLauncherActivity.this, CustomControlsActivity.class));
break;
case 3: { // About
final AlertDialog.Builder aboutB = new AlertDialog.Builder(BaseLauncherActivity.this);
aboutB.setTitle(R.string.mcl_option_about);
try {
aboutB.setMessage(Html.fromHtml(String.format(Tools.read(getAssets().open("about_en.txt")),
Tools.APP_NAME,
BuildConfig.VERSION_NAME,
"3.2.3")
));
} catch (Exception e) {
throw new RuntimeException(e);
}
aboutB.setPositiveButton(android.R.string.ok, null);
AlertDialog aboutDialog = aboutB.show();
TextView aboutTv = aboutDialog.findViewById(android.R.id.message);
aboutTv.setMovementMethod(LinkMovementMethod.getInstance());
} break;
}
}
});
builder.show();
/**
* Used by the install button from the layout_main_v4
* @param view The view triggering the function
*/
public void installJarFile(View view){
installMod(false);
}
public static final int RUN_MOD_INSTALLER = 2050;
private void installMod(boolean customJavaArgs) {
if (MultiRTUtils.getExactJreName(8) == null) {
Toast.makeText(this, R.string.multirt_nojava8rt, Toast.LENGTH_LONG).show();
return;
}
if (customJavaArgs) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.alerttitle_installmod);
@ -101,15 +78,12 @@ public abstract class BaseLauncherActivity extends BaseActivity {
final EditText edit = new EditText(this);
edit.setSingleLine();
edit.setHint("-jar/-cp /path/to/file.jar ...");
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener(){
@Override
public void onClick(DialogInterface di, int i) {
Intent intent = new Intent(BaseLauncherActivity.this, JavaGUILauncherActivity.class);
intent.putExtra("skipDetectMod", true);
intent.putExtra("javaArgs", edit.getText().toString());
startActivity(intent);
}
});
builder.setPositiveButton(android.R.string.ok, (di, i) -> {
Intent intent = new Intent(BaseLauncherActivity.this, JavaGUILauncherActivity.class);
intent.putExtra("skipDetectMod", true);
intent.putExtra("javaArgs", edit.getText().toString());
startActivity(intent);
});
dialog = builder.create();
dialog.setView(edit);
dialog.show();
@ -131,11 +105,39 @@ public abstract class BaseLauncherActivity extends BaseActivity {
} else if (canBack) {
v.setEnabled(false);
mTask = new MinecraftDownloaderTask(this);
mTask.execute(mProfile.selectedVersion);
mCrashView.resetCrashLog = true;
LauncherProfiles.update();
String selectedProfile = LauncherPreferences.DEFAULT_PREF.getString(LauncherPreferences.PREF_KEY_CURRENT_PROFILE,"");
if (LauncherProfiles.mainProfileJson != null && LauncherProfiles.mainProfileJson.profiles != null && LauncherProfiles.mainProfileJson.profiles.containsKey(selectedProfile)) {
MinecraftProfile prof = LauncherProfiles.mainProfileJson.profiles.get(selectedProfile);
if (prof != null && prof.lastVersionId != null) {
if (mProfile.accessToken.equals("0")) {
String versionId = getVersionId(prof.lastVersionId);
File verJsonFile = new File(Tools.DIR_HOME_VERSION,
versionId + "/" + versionId + ".json");
if (verJsonFile.exists()) {
mTask.onPostExecute(null);
return;
}
Tools.dialogOnUiThread(this,
getString(R.string.global_error),
getString(R.string.mcl_launch_error_localmode)
);
}else {
mTask.execute(getVersionId(prof.lastVersionId));
}
}
}
}
}
public static String getVersionId(String input) {
Map<String,String> releaseTable = (Map<String,String>)ExtraCore.getValue(ExtraConstants.RELEASE_TABLE);
if(releaseTable == null || releaseTable.isEmpty()) return input;
if("latest-release".equals(input)) return releaseTable.get("release");
if("latest-snapshot".equals(input)) return releaseTable.get("snapshot");
return input;
}
@Override
public void onBackPressed() {
if (canBack) {
@ -150,107 +152,60 @@ public abstract class BaseLauncherActivity extends BaseActivity {
Tools.updateWindowSize(this);
System.out.println("call to onPostResume; E");
}
public static void updateVersionSpinner(Context ctx, ArrayList<String> value, Spinner mVersionSelector, String defaultSelection) {
if(value != null && value.size() > 0) {
ArrayAdapter<String> adapter = new ArrayAdapter<String>(ctx, android.R.layout.simple_spinner_item, value);
adapter.setDropDownViewResource(android.R.layout.simple_list_item_single_choice);
mVersionSelector.setAdapter(adapter);
mVersionSelector.setSelection(RefreshVersionListTask.selectAt(value, defaultSelection));
return;
}
mVersionSelector.setSelection(RefreshVersionListTask.selectAt(PojavLauncherActivity.basicVersionList, defaultSelection));
}
@Override
protected void onResume(){
super.onResume();
new RefreshVersionListTask(this).execute();
if(listRefreshListener != null) {
LauncherPreferences.DEFAULT_PREF.unregisterOnSharedPreferenceChangeListener(listRefreshListener);
}
listRefreshListener = (sharedPreferences, key) -> {
if(key.startsWith("vertype_")) {
System.out.println("Verlist update needed!");
LauncherPreferences.PREF_VERTYPE_RELEASE = sharedPreferences.getBoolean("vertype_release",true);
LauncherPreferences.PREF_VERTYPE_SNAPSHOT = sharedPreferences.getBoolean("vertype_snapshot",false);
LauncherPreferences.PREF_VERTYPE_OLDALPHA = sharedPreferences.getBoolean("vertype_oldalpha",false);
LauncherPreferences.PREF_VERTYPE_OLDBETA = sharedPreferences.getBoolean("vertype_oldbeta",false);
new RefreshVersionListTask(this).execute();
}
};
LauncherPreferences.DEFAULT_PREF.registerOnSharedPreferenceChangeListener(listRefreshListener);
System.out.println("call to onResume");
final int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
final View decorView = getWindow().getDecorView();
decorView.setSystemUiVisibility(uiOptions);
System.out.println("call to onResume; E");
}
SharedPreferences.OnSharedPreferenceChangeListener listRefreshListener = null;
SharedPreferences.OnSharedPreferenceChangeListener profileEnableListener = null;
@Override
protected void onResumeFragments() {
super.onResumeFragments();
if(listRefreshListener == null) {
final BaseLauncherActivity thiz = this;
listRefreshListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if(key.startsWith("vertype_")) {
System.out.println("Verlist update needed!");
new RefreshVersionListTask(thiz).execute();
}
}
};
}
LauncherPreferences.DEFAULT_PREF.registerOnSharedPreferenceChangeListener(listRefreshListener);
new RefreshVersionListTask(this).execute();
System.out.println("call to onResumeFragments");
mRuntimeConfigDialog = new MultiRTConfigDialog();
mRuntimeConfigDialog.prepare(this);
try{
final ProgressDialog barrier = new ProgressDialog(this);
barrier.setMessage(getString(R.string.global_waiting));
barrier.setProgressStyle(barrier.STYLE_SPINNER);
barrier.setCancelable(false);
barrier.show();
new Thread(new Runnable(){
((Button)findViewById(R.id.installJarButton)).setOnLongClickListener(view -> {
installMod(true);
return true;
});
@Override
public void run()
{
while (mConsoleView == null) {
try {
Thread.sleep(20);
} catch (Throwable th) {}
}
try {
Thread.sleep(100);
} catch (Throwable th) {}
runOnUiThread(new Runnable() {
@Override
public void run()
{
try {
mConsoleView.putLog("");
barrier.dismiss();
} catch (Throwable th) {
startActivity(getIntent());
finish();
}
}
});
}
}).start();
File lastCrashFile = Tools.lastFileModified(Tools.DIR_HOME_CRASH);
if(CrashFragment.isNewCrash(lastCrashFile) || !mCrashView.getLastCrash().isEmpty()){
mCrashView.resetCrashLog = false;
initTabs(2);
} /*else throw new Exception();*/
} catch(Throwable e) {
e.printStackTrace();
}
//TODO ADD CRASH CHECK AND FOCUS
System.out.println("call to onResumeFragments; E");
}
public static String getFileName(Context ctx, Uri uri) {
String result = null;
if (uri.getScheme().equals("content")) {
Cursor cursor = ctx.getContentResolver().query(uri, null, null, null, null);
try {
if (cursor != null && cursor.moveToFirst()) {
result = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
}
} finally {
cursor.close();
}
}
if (result == null) {
result = uri.getPath();
int cut = result.lastIndexOf('/');
if (cut != -1) {
result = result.substring(cut + 1);
}
}
return result;
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode,resultCode,data);
@ -260,62 +215,58 @@ public abstract class BaseLauncherActivity extends BaseActivity {
barrier.setProgressStyle(barrier.STYLE_SPINNER);
barrier.setCancelable(false);
barrier.show();
if (requestCode == MultiRTConfigDialog.MULTIRT_PICK_RUNTIME) {
if (data != null) {
final Uri uri = data.getData();
Thread t = new Thread(() -> {
try {
String name = getFileName(this, uri);
MultiRTUtils.installRuntimeNamed(getContentResolver().openInputStream(uri), name,
(resid, stuff) -> BaseLauncherActivity.this.runOnUiThread(
() -> barrier.setMessage(BaseLauncherActivity.this.getString(resid, stuff))));
MultiRTUtils.postPrepare(BaseLauncherActivity.this, name);
} catch (IOException e) {
Tools.showError(BaseLauncherActivity.this
, e);
}
BaseLauncherActivity.this.runOnUiThread(new Runnable() {
@Override
public void run() {
barrier.dismiss();
mRuntimeConfigDialog.refresh();
mRuntimeConfigDialog.dialog.show();
}
});
});
t.start();
}
} else if (requestCode == RUN_MOD_INSTALLER) {
if (data != null) {
final Uri uri = data.getData();
barrier.setMessage(BaseLauncherActivity.this.getString(R.string.multirt_progress_caching));
Thread t = new Thread(()->{
try {
final String name = getFileName(this, uri);
final File modInstallerFile = new File(getCacheDir(), name);
FileOutputStream fos = new FileOutputStream(modInstallerFile);
IOUtils.copy(getContentResolver().openInputStream(uri), fos);
fos.close();
BaseLauncherActivity.this.runOnUiThread(() -> {
barrier.dismiss();
Intent intent = new Intent(BaseLauncherActivity.this, JavaGUILauncherActivity.class);
intent.putExtra("modFile", modInstallerFile);
startActivity(intent);
});
}catch(IOException e) {
Tools.showError(BaseLauncherActivity.this,e);
}
});
t.start();
}
}
}
}
// Catching touch exception
@Override
public boolean onTouchEvent(MotionEvent event) {
return super.onTouchEvent(event);
// Install the runtime
if (requestCode == MultiRTConfigDialog.MULTIRT_PICK_RUNTIME) {
if (data == null) return;
final Uri uri = data.getData();
Thread t = new Thread(() -> {
try {
String name = getFileName(this, uri);
MultiRTUtils.installRuntimeNamed(getContentResolver().openInputStream(uri), name,
(resid, stuff) -> BaseLauncherActivity.this.runOnUiThread(
() -> barrier.setMessage(BaseLauncherActivity.this.getString(resid, stuff))));
MultiRTUtils.postPrepare(BaseLauncherActivity.this, name);
} catch (IOException e) {
Tools.showError(BaseLauncherActivity.this, e);
}
BaseLauncherActivity.this.runOnUiThread(() -> {
barrier.dismiss();
mRuntimeConfigDialog.refresh();
mRuntimeConfigDialog.mDialog.show();
});
});
t.start();
}
// Run a mod installer
if (requestCode == RUN_MOD_INSTALLER) {
if (data == null) return;
final Uri uri = data.getData();
barrier.setMessage(BaseLauncherActivity.this.getString(R.string.multirt_progress_caching));
Thread t = new Thread(()->{
try {
final String name = getFileName(this, uri);
final File modInstallerFile = new File(getCacheDir(), name);
FileOutputStream fos = new FileOutputStream(modInstallerFile);
IOUtils.copy(getContentResolver().openInputStream(uri), fos);
fos.close();
BaseLauncherActivity.this.runOnUiThread(() -> {
barrier.dismiss();
Intent intent = new Intent(BaseLauncherActivity.this, JavaGUILauncherActivity.class);
intent.putExtra("modFile", modInstallerFile);
startActivity(intent);
});
}catch(IOException e) {
Tools.showError(BaseLauncherActivity.this,e);
}
});
t.start();
}
}
}
protected abstract void initTabs(int pageIndex);

View file

@ -5,13 +5,12 @@ import android.content.*;
import android.os.*;
import androidx.appcompat.app.*;
import androidx.preference.*;
import android.view.*;
import android.widget.*;
import androidx.drawerlayout.widget.DrawerLayout;
import com.google.android.material.navigation.NavigationView;
import com.google.gson.JsonSyntaxException;
import com.kdt.pickafile.*;
import java.io.*;
@ -19,18 +18,13 @@ import net.kdt.pojavlaunch.prefs.*;
import net.kdt.pojavlaunch.customcontrols.*;
public class CustomControlsActivity extends BaseActivity
{
private DrawerLayout drawerLayout;
private NavigationView navDrawer;
private ControlLayout ctrlLayout;
private SharedPreferences mPref;
public class CustomControlsActivity extends BaseActivity {
private DrawerLayout mDrawerLayout;
private ListView mDrawerNavigationView;
private ControlLayout mControlLayout;
public boolean isModified = false;
public boolean isFromMainActivity = false;
private static String selectedName = "new_control";
private static String sSelectedName = "new_control";
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -41,48 +35,62 @@ public class CustomControlsActivity extends BaseActivity
// setTheme(androidx.appcompat.R.style.Theme_AppCompat_Translucent);
}
setContentView(R.layout.control_mapping);
mPref = PreferenceManager.getDefaultSharedPreferences(this);
setContentView(R.layout.activity_custom_controls);
ctrlLayout = (ControlLayout) findViewById(R.id.customctrl_controllayout);
mControlLayout = (ControlLayout) findViewById(R.id.customctrl_controllayout);
mDrawerLayout = (DrawerLayout) findViewById(R.id.customctrl_drawerlayout);
mDrawerNavigationView = (ListView) findViewById(R.id.customctrl_navigation_view);
// Menu
drawerLayout = (DrawerLayout) findViewById(R.id.customctrl_drawerlayout);
navDrawer = (NavigationView) findViewById(R.id.customctrl_navigation_view);
navDrawer.setNavigationItemSelectedListener(
new NavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(MenuItem menuItem) {
mDrawerNavigationView.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,getResources().getStringArray(R.array.menu_customcontrol)));
mDrawerNavigationView.setOnItemClickListener((parent, view, position, id) -> {
switch(position) {
case 0:
mControlLayout.addControlButton(new ControlData("New"));
break;
case 1:
mControlLayout.addDrawer(new ControlDrawerData());
break;
case 2:
load(mControlLayout);
break;
case 3:
save(false, mControlLayout);
break;
case 4:
dialogSelectDefaultCtrl(mControlLayout);
break;
}
mDrawerLayout.closeDrawers();
});
/*mDrawerNavigationView.setNavigationItemSelectedListener(
menuItem -> {
switch (menuItem.getItemId()) {
case R.id.menu_ctrl_load:
load(ctrlLayout);
load(mControlLayout);
break;
case R.id.menu_ctrl_add:
ctrlLayout.addControlButton(new ControlData("New"));
mControlLayout.addControlButton(new ControlData("New"));
break;
case R.id.menu_ctrl_add_drawer:
ctrlLayout.addDrawer(new ControlDrawerData());
mControlLayout.addDrawer(new ControlDrawerData());
break;
case R.id.menu_ctrl_selectdefault:
dialogSelectDefaultCtrl(ctrlLayout);
dialogSelectDefaultCtrl(mControlLayout);
break;
case R.id.menu_ctrl_save:
save(false,ctrlLayout);
save(false, mControlLayout);
break;
}
//Toast.makeText(MainActivity.this, menuItem.getTitle() + ":" + menuItem.getItemId(), Toast.LENGTH_SHORT).show();
drawerLayout.closeDrawers();
mDrawerLayout.closeDrawers();
return true;
}
});
});
*/
mControlLayout.setActivity(this);
mControlLayout.setModifiable(true);
ctrlLayout.setActivity(this);
ctrlLayout.setModifiable(true);
loadControl(LauncherPreferences.PREF_DEFAULTCTRL_PATH,ctrlLayout);
loadControl(LauncherPreferences.PREF_DEFAULTCTRL_PATH, mControlLayout);
}
@Override
@ -93,18 +101,7 @@ public class CustomControlsActivity extends BaseActivity
return;
}
save(true,ctrlLayout);
}
private static void setDefaultControlJson(String path,ControlLayout ctrlLayout) {
try {
// Load before save to make sure control is not error
ctrlLayout.loadLayout(Tools.GLOBAL_GSON.fromJson(Tools.read(path), CustomControls.class));
LauncherPreferences.DEFAULT_PREF.edit().putString("defaultCtrl", path).commit();
LauncherPreferences.PREF_DEFAULTCTRL_PATH = path;
} catch (Throwable th) {
Tools.showError(ctrlLayout.getContext(), th);
}
save(true, mControlLayout);
}
public static void dialogSelectDefaultCtrl(final ControlLayout layout) {
@ -127,18 +124,12 @@ public class CustomControlsActivity extends BaseActivity
dialog.show();
}
private static String doSaveCtrl(String name, final ControlLayout layout) throws Exception {
String jsonPath = Tools.CTRLMAP_PATH + "/" + name + ".json";
layout.saveLayout(jsonPath);
return jsonPath;
}
public static void save(final boolean exit, final ControlLayout layout) {
final Context ctx = layout.getContext();
final EditText edit = new EditText(ctx);
edit.setSingleLine();
edit.setText(selectedName);
edit.setText(sSelectedName);
AlertDialog.Builder builder = new AlertDialog.Builder(ctx);
builder.setTitle(R.string.global_save);
@ -162,51 +153,38 @@ public class CustomControlsActivity extends BaseActivity
});
}
final AlertDialog dialog = builder.create();
dialog.setOnShowListener(new DialogInterface.OnShowListener() {
dialog.setOnShowListener(dialogInterface -> {
@Override
public void onShow(DialogInterface dialogInterface) {
Button button = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (edit.getText().toString().isEmpty()) {
edit.setError(ctx.getResources().getString(R.string.global_error_field_empty));
} else {
try {
String jsonPath = doSaveCtrl(edit.getText().toString(),layout);
Toast.makeText(ctx, ctx.getString(R.string.global_save) + ": " + jsonPath, Toast.LENGTH_SHORT).show();
dialog.dismiss();
if (exit) {
if(ctx instanceof MainActivity) {
((MainActivity) ctx).leaveCustomControls();
}else{
((Activity)ctx).onBackPressed();
}
//CustomControlsActivity.super.onBackPressed();
}
} catch (Throwable th) {
Tools.showError(ctx, th, exit);
}
}
}
});
Button button = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
button.setOnClickListener(view -> {
if (edit.getText().toString().isEmpty()) {
edit.setError(ctx.getResources().getString(R.string.global_error_field_empty));
return;
}
try {
String jsonPath = doSaveCtrl(edit.getText().toString(),layout);
Toast.makeText(ctx, ctx.getString(R.string.global_save) + ": " + jsonPath, Toast.LENGTH_SHORT).show();
dialog.dismiss();
if (!exit) return;
if(ctx instanceof MainActivity) {
((MainActivity) ctx).leaveCustomControls();
}else{
((Activity)ctx).onBackPressed();
}
} catch (Throwable th) {
Tools.showError(ctx, th, exit);
}
});
});
dialog.show();
}
public static void load(final ControlLayout layout) {
/*ControlJsonSelector sel = new ControlJsonSelector(layout.getContext(), R.string.global_load);
sel.setFinishCallback((f)->{
loadControl(f.getAbsolutePath(),layout);
});
sel.show();*/
AlertDialog.Builder builder = new AlertDialog.Builder(layout.getContext());
builder.setTitle(R.string.global_load);
builder.setPositiveButton(android.R.string.cancel, null);
@ -227,12 +205,30 @@ public class CustomControlsActivity extends BaseActivity
dialog.show();
}
private static void setDefaultControlJson(String path,ControlLayout ctrlLayout) {
// Load before save to make sure control is not error
try {
ctrlLayout.loadLayout(Tools.GLOBAL_GSON.fromJson(Tools.read(path), CustomControls.class));
LauncherPreferences.DEFAULT_PREF.edit().putString("defaultCtrl", path).apply();
LauncherPreferences.PREF_DEFAULTCTRL_PATH = path;
} catch (IOException| JsonSyntaxException exception) {
Tools.showError(ctrlLayout.getContext(), exception);
}
}
private static String doSaveCtrl(String name, final ControlLayout layout) throws Exception {
String jsonPath = Tools.CTRLMAP_PATH + "/" + name + ".json";
layout.saveLayout(jsonPath);
return jsonPath;
}
private static void loadControl(String path,ControlLayout layout) {
try {
layout.loadLayout(path);
selectedName = new File(path).getName();
sSelectedName = new File(path).getName();
// Remove `.json`
selectedName = selectedName.substring(0, selectedName.length() - 5);
sSelectedName = sSelectedName.substring(0, sSelectedName.length() - 5);
} catch (Exception e) {
Tools.showError(layout.getContext(), e);
}

View file

@ -1,16 +0,0 @@
package net.kdt.pojavlaunch;
import android.content.*;
import android.net.*;
import androidx.browser.customtabs.*;
public class CustomTabs {
public static void openTab(Context context, String url) {
CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
builder.setShowTitle(true);
CustomTabsIntent customTabsIntent = builder.build();
customTabsIntent.launchUrl(context, Uri.parse(url));
}
}

View file

@ -1,67 +0,0 @@
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

@ -1,8 +1,8 @@
package net.kdt.pojavlaunch;
import android.view.KeyEvent;
import static org.lwjgl.glfw.CallbackBridge.sendKeyPress;
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
import android.view.KeyEvent;
import org.lwjgl.glfw.CallbackBridge;
@ -14,168 +14,158 @@ public class EfficientAndroidLWJGLKeycode {
//The key being the android keycode from a KeyEvent
//The value its LWJGL equivalent.
private static final int KEYCODE_COUNT = 103;
private static final int[] androidKeycodes = new int[KEYCODE_COUNT];
private static final short[] LWJGLKeycodes = new short[KEYCODE_COUNT];
private static final int[] sAndroidKeycodes = new int[KEYCODE_COUNT];
private static final short[] sLwjglKeycodes = new short[KEYCODE_COUNT];
private static String[] androidKeyNameArray; /* = new String[androidKeycodes.length]; */
private static int mTmpCount = 0;
static {
/* BINARY SEARCH IS PERFORMED ON THE androidKeycodes ARRAY !
WHEN ADDING A MAPPING, ADD IT SO THE androidKeycodes ARRAY STAYS SORTED ! */
// Mapping Android Keycodes to LWJGL Keycodes
add(KeyEvent.KEYCODE_UNKNOWN,LWJGLGLFWKeycode.GLFW_KEY_UNKNOWN);
add(KeyEvent.KEYCODE_HOME, LWJGLGLFWKeycode.GLFW_KEY_HOME);
add(KeyEvent.KEYCODE_UNKNOWN, LwjglGlfwKeycode.GLFW_KEY_UNKNOWN);
add(KeyEvent.KEYCODE_HOME, LwjglGlfwKeycode.GLFW_KEY_HOME);
// Escape key
add(KeyEvent.KEYCODE_BACK, LWJGLGLFWKeycode.GLFW_KEY_ESCAPE);
add(KeyEvent.KEYCODE_BACK, LwjglGlfwKeycode.GLFW_KEY_ESCAPE);
// 0-9 keys
add(KeyEvent.KEYCODE_0, LWJGLGLFWKeycode.GLFW_KEY_0); //7
add(KeyEvent.KEYCODE_1, LWJGLGLFWKeycode.GLFW_KEY_1);
add(KeyEvent.KEYCODE_2, LWJGLGLFWKeycode.GLFW_KEY_2);
add(KeyEvent.KEYCODE_3, LWJGLGLFWKeycode.GLFW_KEY_3);
add(KeyEvent.KEYCODE_4, LWJGLGLFWKeycode.GLFW_KEY_4);
add(KeyEvent.KEYCODE_5, LWJGLGLFWKeycode.GLFW_KEY_5);
add(KeyEvent.KEYCODE_6, LWJGLGLFWKeycode.GLFW_KEY_6);
add(KeyEvent.KEYCODE_7, LWJGLGLFWKeycode.GLFW_KEY_7);
add(KeyEvent.KEYCODE_8, LWJGLGLFWKeycode.GLFW_KEY_8);
add(KeyEvent.KEYCODE_9, LWJGLGLFWKeycode.GLFW_KEY_9); //16
add(KeyEvent.KEYCODE_0, LwjglGlfwKeycode.GLFW_KEY_0); //7
add(KeyEvent.KEYCODE_1, LwjglGlfwKeycode.GLFW_KEY_1);
add(KeyEvent.KEYCODE_2, LwjglGlfwKeycode.GLFW_KEY_2);
add(KeyEvent.KEYCODE_3, LwjglGlfwKeycode.GLFW_KEY_3);
add(KeyEvent.KEYCODE_4, LwjglGlfwKeycode.GLFW_KEY_4);
add(KeyEvent.KEYCODE_5, LwjglGlfwKeycode.GLFW_KEY_5);
add(KeyEvent.KEYCODE_6, LwjglGlfwKeycode.GLFW_KEY_6);
add(KeyEvent.KEYCODE_7, LwjglGlfwKeycode.GLFW_KEY_7);
add(KeyEvent.KEYCODE_8, LwjglGlfwKeycode.GLFW_KEY_8);
add(KeyEvent.KEYCODE_9, LwjglGlfwKeycode.GLFW_KEY_9); //16
add(KeyEvent.KEYCODE_POUND,LWJGLGLFWKeycode.GLFW_KEY_3);
add(KeyEvent.KEYCODE_POUND, LwjglGlfwKeycode.GLFW_KEY_3);
// Arrow keys
add(KeyEvent.KEYCODE_DPAD_UP, LWJGLGLFWKeycode.GLFW_KEY_UP); //19
add(KeyEvent.KEYCODE_DPAD_DOWN, LWJGLGLFWKeycode.GLFW_KEY_DOWN);
add(KeyEvent.KEYCODE_DPAD_LEFT, LWJGLGLFWKeycode.GLFW_KEY_LEFT);
add(KeyEvent.KEYCODE_DPAD_RIGHT, LWJGLGLFWKeycode.GLFW_KEY_RIGHT); //22
add(KeyEvent.KEYCODE_DPAD_UP, LwjglGlfwKeycode.GLFW_KEY_UP); //19
add(KeyEvent.KEYCODE_DPAD_DOWN, LwjglGlfwKeycode.GLFW_KEY_DOWN);
add(KeyEvent.KEYCODE_DPAD_LEFT, LwjglGlfwKeycode.GLFW_KEY_LEFT);
add(KeyEvent.KEYCODE_DPAD_RIGHT, LwjglGlfwKeycode.GLFW_KEY_RIGHT); //22
// A-Z keys
add(KeyEvent.KEYCODE_A, LWJGLGLFWKeycode.GLFW_KEY_A); //29
add(KeyEvent.KEYCODE_B, LWJGLGLFWKeycode.GLFW_KEY_B);
add(KeyEvent.KEYCODE_C, LWJGLGLFWKeycode.GLFW_KEY_C);
add(KeyEvent.KEYCODE_D, LWJGLGLFWKeycode.GLFW_KEY_D);
add(KeyEvent.KEYCODE_E, LWJGLGLFWKeycode.GLFW_KEY_E);
add(KeyEvent.KEYCODE_F, LWJGLGLFWKeycode.GLFW_KEY_F);
add(KeyEvent.KEYCODE_G, LWJGLGLFWKeycode.GLFW_KEY_G);
add(KeyEvent.KEYCODE_H, LWJGLGLFWKeycode.GLFW_KEY_H);
add(KeyEvent.KEYCODE_I, LWJGLGLFWKeycode.GLFW_KEY_I);
add(KeyEvent.KEYCODE_J, LWJGLGLFWKeycode.GLFW_KEY_J);
add(KeyEvent.KEYCODE_K, LWJGLGLFWKeycode.GLFW_KEY_K);
add(KeyEvent.KEYCODE_L, LWJGLGLFWKeycode.GLFW_KEY_L);
add(KeyEvent.KEYCODE_M, LWJGLGLFWKeycode.GLFW_KEY_M);
add(KeyEvent.KEYCODE_N, LWJGLGLFWKeycode.GLFW_KEY_N);
add(KeyEvent.KEYCODE_O, LWJGLGLFWKeycode.GLFW_KEY_O);
add(KeyEvent.KEYCODE_P, LWJGLGLFWKeycode.GLFW_KEY_P);
add(KeyEvent.KEYCODE_Q, LWJGLGLFWKeycode.GLFW_KEY_Q);
add(KeyEvent.KEYCODE_R, LWJGLGLFWKeycode.GLFW_KEY_R);
add(KeyEvent.KEYCODE_S, LWJGLGLFWKeycode.GLFW_KEY_S);
add(KeyEvent.KEYCODE_T, LWJGLGLFWKeycode.GLFW_KEY_T);
add(KeyEvent.KEYCODE_U, LWJGLGLFWKeycode.GLFW_KEY_U);
add(KeyEvent.KEYCODE_V, LWJGLGLFWKeycode.GLFW_KEY_V);
add(KeyEvent.KEYCODE_W, LWJGLGLFWKeycode.GLFW_KEY_W);
add(KeyEvent.KEYCODE_X, LWJGLGLFWKeycode.GLFW_KEY_X);
add(KeyEvent.KEYCODE_Y, LWJGLGLFWKeycode.GLFW_KEY_Y);
add(KeyEvent.KEYCODE_Z, LWJGLGLFWKeycode.GLFW_KEY_Z); //54
add(KeyEvent.KEYCODE_A, LwjglGlfwKeycode.GLFW_KEY_A); //29
add(KeyEvent.KEYCODE_B, LwjglGlfwKeycode.GLFW_KEY_B);
add(KeyEvent.KEYCODE_C, LwjglGlfwKeycode.GLFW_KEY_C);
add(KeyEvent.KEYCODE_D, LwjglGlfwKeycode.GLFW_KEY_D);
add(KeyEvent.KEYCODE_E, LwjglGlfwKeycode.GLFW_KEY_E);
add(KeyEvent.KEYCODE_F, LwjglGlfwKeycode.GLFW_KEY_F);
add(KeyEvent.KEYCODE_G, LwjglGlfwKeycode.GLFW_KEY_G);
add(KeyEvent.KEYCODE_H, LwjglGlfwKeycode.GLFW_KEY_H);
add(KeyEvent.KEYCODE_I, LwjglGlfwKeycode.GLFW_KEY_I);
add(KeyEvent.KEYCODE_J, LwjglGlfwKeycode.GLFW_KEY_J);
add(KeyEvent.KEYCODE_K, LwjglGlfwKeycode.GLFW_KEY_K);
add(KeyEvent.KEYCODE_L, LwjglGlfwKeycode.GLFW_KEY_L);
add(KeyEvent.KEYCODE_M, LwjglGlfwKeycode.GLFW_KEY_M);
add(KeyEvent.KEYCODE_N, LwjglGlfwKeycode.GLFW_KEY_N);
add(KeyEvent.KEYCODE_O, LwjglGlfwKeycode.GLFW_KEY_O);
add(KeyEvent.KEYCODE_P, LwjglGlfwKeycode.GLFW_KEY_P);
add(KeyEvent.KEYCODE_Q, LwjglGlfwKeycode.GLFW_KEY_Q);
add(KeyEvent.KEYCODE_R, LwjglGlfwKeycode.GLFW_KEY_R);
add(KeyEvent.KEYCODE_S, LwjglGlfwKeycode.GLFW_KEY_S);
add(KeyEvent.KEYCODE_T, LwjglGlfwKeycode.GLFW_KEY_T);
add(KeyEvent.KEYCODE_U, LwjglGlfwKeycode.GLFW_KEY_U);
add(KeyEvent.KEYCODE_V, LwjglGlfwKeycode.GLFW_KEY_V);
add(KeyEvent.KEYCODE_W, LwjglGlfwKeycode.GLFW_KEY_W);
add(KeyEvent.KEYCODE_X, LwjglGlfwKeycode.GLFW_KEY_X);
add(KeyEvent.KEYCODE_Y, LwjglGlfwKeycode.GLFW_KEY_Y);
add(KeyEvent.KEYCODE_Z, LwjglGlfwKeycode.GLFW_KEY_Z); //54
add(KeyEvent.KEYCODE_COMMA, LWJGLGLFWKeycode.GLFW_KEY_COMMA);
add(KeyEvent.KEYCODE_PERIOD, LWJGLGLFWKeycode.GLFW_KEY_PERIOD);
add(KeyEvent.KEYCODE_COMMA, LwjglGlfwKeycode.GLFW_KEY_COMMA);
add(KeyEvent.KEYCODE_PERIOD, LwjglGlfwKeycode.GLFW_KEY_PERIOD);
// Alt keys
add(KeyEvent.KEYCODE_ALT_LEFT, LWJGLGLFWKeycode.GLFW_KEY_LEFT_ALT);
add(KeyEvent.KEYCODE_ALT_RIGHT, LWJGLGLFWKeycode.GLFW_KEY_RIGHT_ALT);
add(KeyEvent.KEYCODE_ALT_LEFT, LwjglGlfwKeycode.GLFW_KEY_LEFT_ALT);
add(KeyEvent.KEYCODE_ALT_RIGHT, LwjglGlfwKeycode.GLFW_KEY_RIGHT_ALT);
// Shift keys
add(KeyEvent.KEYCODE_SHIFT_LEFT, LWJGLGLFWKeycode.GLFW_KEY_LEFT_SHIFT);
add(KeyEvent.KEYCODE_SHIFT_RIGHT, LWJGLGLFWKeycode.GLFW_KEY_RIGHT_SHIFT);
add(KeyEvent.KEYCODE_SHIFT_LEFT, LwjglGlfwKeycode.GLFW_KEY_LEFT_SHIFT);
add(KeyEvent.KEYCODE_SHIFT_RIGHT, LwjglGlfwKeycode.GLFW_KEY_RIGHT_SHIFT);
add(KeyEvent.KEYCODE_TAB, LWJGLGLFWKeycode.GLFW_KEY_TAB);
add(KeyEvent.KEYCODE_SPACE, LWJGLGLFWKeycode.GLFW_KEY_SPACE);
add(KeyEvent.KEYCODE_ENTER, LWJGLGLFWKeycode.GLFW_KEY_ENTER); //66
add(KeyEvent.KEYCODE_DEL, LWJGLGLFWKeycode.GLFW_KEY_BACKSPACE); // Backspace
add(KeyEvent.KEYCODE_GRAVE, LWJGLGLFWKeycode.GLFW_KEY_GRAVE_ACCENT);
add(KeyEvent.KEYCODE_MINUS, LWJGLGLFWKeycode.GLFW_KEY_MINUS);
add(KeyEvent.KEYCODE_EQUALS, LWJGLGLFWKeycode.GLFW_KEY_EQUAL);
add(KeyEvent.KEYCODE_LEFT_BRACKET, LWJGLGLFWKeycode.GLFW_KEY_LEFT_BRACKET);
add(KeyEvent.KEYCODE_RIGHT_BRACKET, LWJGLGLFWKeycode.GLFW_KEY_RIGHT_BRACKET);
add(KeyEvent.KEYCODE_BACKSLASH, LWJGLGLFWKeycode.GLFW_KEY_BACKSLASH);
add(KeyEvent.KEYCODE_SEMICOLON, LWJGLGLFWKeycode.GLFW_KEY_SEMICOLON); //74
add(KeyEvent.KEYCODE_TAB, LwjglGlfwKeycode.GLFW_KEY_TAB);
add(KeyEvent.KEYCODE_SPACE, LwjglGlfwKeycode.GLFW_KEY_SPACE);
add(KeyEvent.KEYCODE_ENTER, LwjglGlfwKeycode.GLFW_KEY_ENTER); //66
add(KeyEvent.KEYCODE_DEL, LwjglGlfwKeycode.GLFW_KEY_BACKSPACE); // Backspace
add(KeyEvent.KEYCODE_GRAVE, LwjglGlfwKeycode.GLFW_KEY_GRAVE_ACCENT);
add(KeyEvent.KEYCODE_MINUS, LwjglGlfwKeycode.GLFW_KEY_MINUS);
add(KeyEvent.KEYCODE_EQUALS, LwjglGlfwKeycode.GLFW_KEY_EQUAL);
add(KeyEvent.KEYCODE_LEFT_BRACKET, LwjglGlfwKeycode.GLFW_KEY_LEFT_BRACKET);
add(KeyEvent.KEYCODE_RIGHT_BRACKET, LwjglGlfwKeycode.GLFW_KEY_RIGHT_BRACKET);
add(KeyEvent.KEYCODE_BACKSLASH, LwjglGlfwKeycode.GLFW_KEY_BACKSLASH);
add(KeyEvent.KEYCODE_SEMICOLON, LwjglGlfwKeycode.GLFW_KEY_SEMICOLON); //74
add(KeyEvent.KEYCODE_SLASH, LWJGLGLFWKeycode.GLFW_KEY_SLASH); //76
add(KeyEvent.KEYCODE_AT,LWJGLGLFWKeycode.GLFW_KEY_2);
add(KeyEvent.KEYCODE_SLASH, LwjglGlfwKeycode.GLFW_KEY_SLASH); //76
add(KeyEvent.KEYCODE_AT, LwjglGlfwKeycode.GLFW_KEY_2);
add(KeyEvent.KEYCODE_PLUS, LWJGLGLFWKeycode.GLFW_KEY_KP_ADD);
add(KeyEvent.KEYCODE_PLUS, LwjglGlfwKeycode.GLFW_KEY_KP_ADD);
// Page keys
add(KeyEvent.KEYCODE_PAGE_UP, LWJGLGLFWKeycode.GLFW_KEY_PAGE_UP); //92
add(KeyEvent.KEYCODE_PAGE_DOWN, LWJGLGLFWKeycode.GLFW_KEY_PAGE_DOWN);
add(KeyEvent.KEYCODE_PAGE_UP, LwjglGlfwKeycode.GLFW_KEY_PAGE_UP); //92
add(KeyEvent.KEYCODE_PAGE_DOWN, LwjglGlfwKeycode.GLFW_KEY_PAGE_DOWN);
add(KeyEvent.KEYCODE_ESCAPE, LWJGLGLFWKeycode.GLFW_KEY_ESCAPE);
add(KeyEvent.KEYCODE_ESCAPE, LwjglGlfwKeycode.GLFW_KEY_ESCAPE);
// Control keys
add(KeyEvent.KEYCODE_CTRL_LEFT, LWJGLGLFWKeycode.GLFW_KEY_LEFT_CONTROL);
add(KeyEvent.KEYCODE_CTRL_RIGHT, LWJGLGLFWKeycode.GLFW_KEY_RIGHT_CONTROL);
add(KeyEvent.KEYCODE_CTRL_LEFT, LwjglGlfwKeycode.GLFW_KEY_LEFT_CONTROL);
add(KeyEvent.KEYCODE_CTRL_RIGHT, LwjglGlfwKeycode.GLFW_KEY_RIGHT_CONTROL);
add(KeyEvent.KEYCODE_CAPS_LOCK, LWJGLGLFWKeycode.GLFW_KEY_CAPS_LOCK);
add(KeyEvent.KEYCODE_BREAK, LWJGLGLFWKeycode.GLFW_KEY_PAUSE);
add(KeyEvent.KEYCODE_INSERT, LWJGLGLFWKeycode.GLFW_KEY_INSERT);
add(KeyEvent.KEYCODE_CAPS_LOCK, LwjglGlfwKeycode.GLFW_KEY_CAPS_LOCK);
add(KeyEvent.KEYCODE_BREAK, LwjglGlfwKeycode.GLFW_KEY_PAUSE);
add(KeyEvent.KEYCODE_INSERT, LwjglGlfwKeycode.GLFW_KEY_INSERT);
// Fn keys
add(KeyEvent.KEYCODE_F1, LWJGLGLFWKeycode.GLFW_KEY_F1); //131
add(KeyEvent.KEYCODE_F2, LWJGLGLFWKeycode.GLFW_KEY_F2);
add(KeyEvent.KEYCODE_F3, LWJGLGLFWKeycode.GLFW_KEY_F3);
add(KeyEvent.KEYCODE_F4, LWJGLGLFWKeycode.GLFW_KEY_F4);
add(KeyEvent.KEYCODE_F5, LWJGLGLFWKeycode.GLFW_KEY_F5);
add(KeyEvent.KEYCODE_F6, LWJGLGLFWKeycode.GLFW_KEY_F6);
add(KeyEvent.KEYCODE_F7, LWJGLGLFWKeycode.GLFW_KEY_F7);
add(KeyEvent.KEYCODE_F8, LWJGLGLFWKeycode.GLFW_KEY_F8);
add(KeyEvent.KEYCODE_F9, LWJGLGLFWKeycode.GLFW_KEY_F9);
add(KeyEvent.KEYCODE_F10, LWJGLGLFWKeycode.GLFW_KEY_F10);
add(KeyEvent.KEYCODE_F11, LWJGLGLFWKeycode.GLFW_KEY_F11);
add(KeyEvent.KEYCODE_F12, LWJGLGLFWKeycode.GLFW_KEY_F12); //142
add(KeyEvent.KEYCODE_F1, LwjglGlfwKeycode.GLFW_KEY_F1); //131
add(KeyEvent.KEYCODE_F2, LwjglGlfwKeycode.GLFW_KEY_F2);
add(KeyEvent.KEYCODE_F3, LwjglGlfwKeycode.GLFW_KEY_F3);
add(KeyEvent.KEYCODE_F4, LwjglGlfwKeycode.GLFW_KEY_F4);
add(KeyEvent.KEYCODE_F5, LwjglGlfwKeycode.GLFW_KEY_F5);
add(KeyEvent.KEYCODE_F6, LwjglGlfwKeycode.GLFW_KEY_F6);
add(KeyEvent.KEYCODE_F7, LwjglGlfwKeycode.GLFW_KEY_F7);
add(KeyEvent.KEYCODE_F8, LwjglGlfwKeycode.GLFW_KEY_F8);
add(KeyEvent.KEYCODE_F9, LwjglGlfwKeycode.GLFW_KEY_F9);
add(KeyEvent.KEYCODE_F10, LwjglGlfwKeycode.GLFW_KEY_F10);
add(KeyEvent.KEYCODE_F11, LwjglGlfwKeycode.GLFW_KEY_F11);
add(KeyEvent.KEYCODE_F12, LwjglGlfwKeycode.GLFW_KEY_F12); //142
// Num keys
add(KeyEvent.KEYCODE_NUM_LOCK, LWJGLGLFWKeycode.GLFW_KEY_NUM_LOCK); //143
add(KeyEvent.KEYCODE_NUMPAD_0, LWJGLGLFWKeycode.GLFW_KEY_0);
add(KeyEvent.KEYCODE_NUMPAD_1, LWJGLGLFWKeycode.GLFW_KEY_1);
add(KeyEvent.KEYCODE_NUMPAD_2, LWJGLGLFWKeycode.GLFW_KEY_2);
add(KeyEvent.KEYCODE_NUMPAD_3, LWJGLGLFWKeycode.GLFW_KEY_3);
add(KeyEvent.KEYCODE_NUMPAD_4, LWJGLGLFWKeycode.GLFW_KEY_4);
add(KeyEvent.KEYCODE_NUMPAD_5, LWJGLGLFWKeycode.GLFW_KEY_5);
add(KeyEvent.KEYCODE_NUMPAD_6, LWJGLGLFWKeycode.GLFW_KEY_6);
add(KeyEvent.KEYCODE_NUMPAD_7, LWJGLGLFWKeycode.GLFW_KEY_7);
add(KeyEvent.KEYCODE_NUMPAD_8, LWJGLGLFWKeycode.GLFW_KEY_8);
add(KeyEvent.KEYCODE_NUMPAD_9, LWJGLGLFWKeycode.GLFW_KEY_9);
add(KeyEvent.KEYCODE_NUMPAD_DIVIDE, LWJGLGLFWKeycode.GLFW_KEY_KP_DIVIDE);
add(KeyEvent.KEYCODE_NUMPAD_MULTIPLY, LWJGLGLFWKeycode.GLFW_KEY_KP_MULTIPLY);
add(KeyEvent.KEYCODE_NUMPAD_SUBTRACT, LWJGLGLFWKeycode.GLFW_KEY_KP_SUBTRACT);
add(KeyEvent.KEYCODE_NUMPAD_ADD, LWJGLGLFWKeycode.GLFW_KEY_KP_ADD);
add(KeyEvent.KEYCODE_NUMPAD_DOT, LWJGLGLFWKeycode.GLFW_KEY_PERIOD);
add(KeyEvent.KEYCODE_NUMPAD_COMMA, LWJGLGLFWKeycode.GLFW_KEY_COMMA);
add(KeyEvent.KEYCODE_NUMPAD_ENTER, LWJGLGLFWKeycode.GLFW_KEY_ENTER);
add(KeyEvent.KEYCODE_NUMPAD_EQUALS, LWJGLGLFWKeycode.GLFW_KEY_EQUAL); //161
add(KeyEvent.KEYCODE_NUM_LOCK, LwjglGlfwKeycode.GLFW_KEY_NUM_LOCK); //143
add(KeyEvent.KEYCODE_NUMPAD_0, LwjglGlfwKeycode.GLFW_KEY_0);
add(KeyEvent.KEYCODE_NUMPAD_1, LwjglGlfwKeycode.GLFW_KEY_1);
add(KeyEvent.KEYCODE_NUMPAD_2, LwjglGlfwKeycode.GLFW_KEY_2);
add(KeyEvent.KEYCODE_NUMPAD_3, LwjglGlfwKeycode.GLFW_KEY_3);
add(KeyEvent.KEYCODE_NUMPAD_4, LwjglGlfwKeycode.GLFW_KEY_4);
add(KeyEvent.KEYCODE_NUMPAD_5, LwjglGlfwKeycode.GLFW_KEY_5);
add(KeyEvent.KEYCODE_NUMPAD_6, LwjglGlfwKeycode.GLFW_KEY_6);
add(KeyEvent.KEYCODE_NUMPAD_7, LwjglGlfwKeycode.GLFW_KEY_7);
add(KeyEvent.KEYCODE_NUMPAD_8, LwjglGlfwKeycode.GLFW_KEY_8);
add(KeyEvent.KEYCODE_NUMPAD_9, LwjglGlfwKeycode.GLFW_KEY_9);
add(KeyEvent.KEYCODE_NUMPAD_DIVIDE, LwjglGlfwKeycode.GLFW_KEY_KP_DIVIDE);
add(KeyEvent.KEYCODE_NUMPAD_MULTIPLY, LwjglGlfwKeycode.GLFW_KEY_KP_MULTIPLY);
add(KeyEvent.KEYCODE_NUMPAD_SUBTRACT, LwjglGlfwKeycode.GLFW_KEY_KP_SUBTRACT);
add(KeyEvent.KEYCODE_NUMPAD_ADD, LwjglGlfwKeycode.GLFW_KEY_KP_ADD);
add(KeyEvent.KEYCODE_NUMPAD_DOT, LwjglGlfwKeycode.GLFW_KEY_PERIOD);
add(KeyEvent.KEYCODE_NUMPAD_COMMA, LwjglGlfwKeycode.GLFW_KEY_COMMA);
add(KeyEvent.KEYCODE_NUMPAD_ENTER, LwjglGlfwKeycode.GLFW_KEY_ENTER);
add(KeyEvent.KEYCODE_NUMPAD_EQUALS, LwjglGlfwKeycode.GLFW_KEY_EQUAL); //161
}
private static short index = 0;
private static void add(int androidKeycode, short LWJGLKeycode){
androidKeycodes[index] = androidKeycode;
LWJGLKeycodes[index] = LWJGLKeycode;
++index;
}
public static boolean containsKey(int keycode){
return getIndexByKey(keycode) >= 0;
}
public static String[] generateKeyName() {
if (androidKeyNameArray == null) {
androidKeyNameArray = new String[androidKeycodes.length];
androidKeyNameArray = new String[sAndroidKeycodes.length];
for(int i=0; i < androidKeyNameArray.length; ++i){
androidKeyNameArray[i] = KeyEvent.keyCodeToString(androidKeycodes[i]).replace("KEYCODE_", "");
androidKeyNameArray[i] = KeyEvent.keyCodeToString(sAndroidKeycodes[i]).replace("KEYCODE_", "");
}
}
return androidKeyNameArray;
@ -185,7 +175,6 @@ public class EfficientAndroidLWJGLKeycode {
execKey(keyEvent, getIndexByKey(keyEvent.getKeyCode()));
}
public static void execKey(KeyEvent keyEvent, int valueIndex) {
//valueIndex points to where the value is stored in the array.
CallbackBridge.holdingAlt = keyEvent.isAltPressed();
@ -194,45 +183,45 @@ public class EfficientAndroidLWJGLKeycode {
CallbackBridge.holdingNumlock = keyEvent.isNumLockOn();
CallbackBridge.holdingShift = keyEvent.isShiftPressed();
try {
System.out.println(keyEvent.getKeyCode() + " " +keyEvent.getDisplayLabel());
char key = (char)(keyEvent.getUnicodeChar() != 0 ? keyEvent.getUnicodeChar() : '\u0000');
BaseMainActivity.sendKeyPress(
getValueByIndex(valueIndex),
key,
0,
CallbackBridge.getCurrentMods(),
keyEvent.getAction() == KeyEvent.ACTION_DOWN);
} catch (Throwable th) {
th.printStackTrace();
}
System.out.println(keyEvent.getKeyCode() + " " +keyEvent.getDisplayLabel());
char key = (char)(keyEvent.getUnicodeChar() != 0 ? keyEvent.getUnicodeChar() : '\u0000');
sendKeyPress(
getValueByIndex(valueIndex),
key,
0,
CallbackBridge.getCurrentMods(),
keyEvent.getAction() == KeyEvent.ACTION_DOWN);
}
public static void execKeyIndex(int index){
//Send a quick key press.
BaseMainActivity.sendKeyPress(getValueByIndex(index));
sendKeyPress(getValueByIndex(index));
}
public static int getValueByIndex(int index) {
return LWJGLKeycodes[index];
return sLwjglKeycodes[index];
}
public static int getIndexByKey(int key){
return Arrays.binarySearch(androidKeycodes, key);
return Arrays.binarySearch(sAndroidKeycodes, key);
}
public static short getValue(int key){
return LWJGLKeycodes[Arrays.binarySearch(androidKeycodes, key)];
return sLwjglKeycodes[Arrays.binarySearch(sAndroidKeycodes, key)];
}
/** @return the index at which the key is in the array, searching linearly */
public static int getIndexByValue(int lwjglKey) {
//Since the LWJGL keycodes aren't sorted, linear search is used.
//You should avoid using this function on performance critical areas
for (int i = 0; i < LWJGLKeycodes.length; i++) {
if(LWJGLKeycodes[i] == lwjglKey) return i;
for (int i = 0; i < sLwjglKeycodes.length; i++) {
if(sLwjglKeycodes[i] == lwjglKey) return i;
}
return 0;
}
private static void add(int androidKeycode, short LWJGLKeycode){
sAndroidKeycodes[mTmpCount] = androidKeycode;
sLwjglKeycodes[mTmpCount] = LWJGLKeycode;
mTmpCount ++;
}
}

View file

@ -1,21 +1,18 @@
package net.kdt.pojavlaunch;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import androidx.annotation.Keep;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
@Keep
public class ExitActivity extends AppCompatActivity {
public static void showExitMessage(Context ctx, int code) {
Intent i = new Intent(ctx,ExitActivity.class);
i.putExtra("code",code);
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
ctx.startActivity(i);
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -24,14 +21,20 @@ public class ExitActivity extends AppCompatActivity {
if(extras != null) {
code = extras.getInt("code",-1);
}
new AlertDialog.Builder(this)
.setMessage(getString(R.string.mcn_exit_title,code))
.setPositiveButton(android.R.string.ok,(dialog,which)->{
dialog.dismiss();
ExitActivity.this.finish();
}).setOnCancelListener((z)->{
ExitActivity.this.finish();
})
.setPositiveButton(android.R.string.ok, null)
.setOnDismissListener(dialog -> ExitActivity.this.finish())
.show();
}
public static void showExitMessage(Context ctx, int code) {
Intent i = new Intent(ctx,ExitActivity.class);
i.putExtra("code",code);
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
ctx.startActivity(i);
}
}

View file

@ -1,70 +1,47 @@
package net.kdt.pojavlaunch;
import android.app.*;
import android.content.*;
import android.os.*;
import androidx.appcompat.app.*;
import android.util.*;
import androidx.appcompat.app.AlertDialog;
public class FatalErrorActivity extends AppCompatActivity
{
public static void showError(Context ctx, String savePath, boolean storageAllow, /* boolean isFatalErr, */ Throwable th) {
Intent ferrorIntent = new Intent(ctx, FatalErrorActivity.class);
ferrorIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
ferrorIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
ferrorIntent.putExtra("throwable", th);
ferrorIntent.putExtra("savePath", savePath);
ferrorIntent.putExtra("storageAllow", storageAllow);
// ferrorIntent.putExtra("isFatal", isFatalErr);
ctx.startActivity(ferrorIntent);
}
public class FatalErrorActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle extras = getIntent().getExtras();
boolean storageAllow = extras.getBoolean("storageAllow");
final String strStackTrace = Log.getStackTraceString((Throwable) extras.getSerializable("throwable"));
final String stackTrace = Log.getStackTraceString((Throwable) extras.getSerializable("throwable"));
String strSavePath = extras.getString("savePath");
String errHeader = storageAllow ?
"Crash stack trace saved to " + strSavePath + "." :
"Storage permission is required to save crash stack trace!";
// boolean isFatalError = extras.getBoolean("isFatal", false);
new AlertDialog.Builder(this)
.setTitle(R.string.error_fatal)
.setMessage(errHeader + "\n\n" + strStackTrace)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener(){
.setMessage(errHeader + "\n\n" + stackTrace)
.setPositiveButton(android.R.string.ok, (p1, p2) -> finish())
.setNegativeButton(R.string.global_restart, (p1, p2) -> startActivity(new Intent(FatalErrorActivity.this, PojavLoginActivity.class)))
.setNeutralButton(android.R.string.copy, (p1, p2) -> {
ClipboardManager mgr = (ClipboardManager) FatalErrorActivity.this.getSystemService(CLIPBOARD_SERVICE);
mgr.setPrimaryClip(ClipData.newPlainText("error", stackTrace));
@Override
public void onClick(DialogInterface p1, int p2) {
finish();
}
finish();
})
.setNegativeButton(R.string.global_restart, new DialogInterface.OnClickListener(){
@Override
public void onClick(DialogInterface p1, int p2) {
startActivity(new Intent(FatalErrorActivity.this, PojavLoginActivity.class));
}
})
.setNeutralButton(android.R.string.copy, new DialogInterface.OnClickListener(){
@Override
public void onClick(DialogInterface p1, int p2) {
ClipboardManager mgr = (ClipboardManager) FatalErrorActivity.this.getSystemService(CLIPBOARD_SERVICE);
mgr.setPrimaryClip(ClipData.newPlainText("error", strStackTrace));
finish();
}
})
//.setNegativeButton("Report (not available)", null)
.setCancelable(false)
.show();
// Tools.showError(this, isFatalError ? R.string.error_fatal : R.string.global_error, th, true);
}
public static void showError(Context ctx, String savePath, boolean storageAllow, Throwable th) {
Intent ferrorIntent = new Intent(ctx, FatalErrorActivity.class);
ferrorIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
ferrorIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
ferrorIntent.putExtra("throwable", th);
ferrorIntent.putExtra("savePath", savePath);
ferrorIntent.putExtra("storageAllow", storageAllow);
ctx.startActivity(ferrorIntent);
}
}

View file

@ -43,7 +43,7 @@ public class ImportControlActivity extends Activity {
super.onCreate(savedInstanceState);
Tools.initContextConstants(getApplicationContext());
setContentView(R.layout.import_control_layout);
setContentView(R.layout.activity_import_control);
mEditText = findViewById(R.id.editText_import_control_file_name);
}
@ -185,12 +185,10 @@ public class ImportControlActivity extends Activity {
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) {

View file

@ -1,52 +0,0 @@
package net.kdt.pojavlaunch;
import android.os.*;
public class InstallerTask extends AsyncTask<String, Void, String>
{
@Override
protected String doInBackground(String[] p1)
{
try
{
downloadLibraries(p1[0]);
dexMinecraftLibs();
downloadMinecraft(p1[0]);
dexMinecraftClient(p1[0]);
downloadAssets(p1[0]);
}
catch (Exception e)
{
return e.getMessage();
}
return null;
}
@Override
protected void onPostExecute(String result)
{
super.onPostExecute(result);
if(result == null){
//No errors
}
}
private void downloadLibraries(String versionName) throws Exception
{
}
private void dexMinecraftLibs() throws Exception
{
}
private void downloadMinecraft(String versionName) throws Exception
{
}
private void dexMinecraftClient(String version) throws Exception
{
}
private void downloadAssets(String versionName) throws Exception
{
}
}

View file

@ -1,9 +1,11 @@
package net.kdt.pojavlaunch;
import com.google.gson.annotations.SerializedName;
import java.util.Map;
public class JAssets {
public boolean map_to_resources;
@SerializedName("map_to_resources") public boolean mapToResources;
public Map<String, JAssetInfo> objects;
}

View file

@ -1,9 +1,10 @@
package net.kdt.pojavlaunch;
import java.util.Map;
import net.kdt.pojavlaunch.value.*;
import androidx.annotation.Keep;
import java.util.*;
import net.kdt.pojavlaunch.value.*;
@Keep
public class JMinecraftVersionList {
public static final String TYPE_OLD_ALPHA = "old_alpha";
public static final String TYPE_OLD_BETA = "old_beta";
@ -12,18 +13,24 @@ public class JMinecraftVersionList {
public Map<String, String> latest;
public Version[] versions;
public static class Version {
@Keep
public static class FileProperties {
public String id, sha1, url;
public long size;
}
@Keep
public static class Version extends FileProperties {
// Since 1.13, so it's one of ways to check
public Arguments arguments;
public AssetIndex assetIndex;
public String assets;
public Map<String, MinecraftClientInfo> downloads;
public String id;
public String inheritsFrom;
public JavaVersionInfo javaVersion;
public DependentLibrary[] libraries;
public LoggingConfig logging;
public String mainClass;
public String minecraftArguments;
public int minimumLauncherVersion;
@ -31,25 +38,38 @@ public class JMinecraftVersionList {
public String releaseTime;
public String time;
public String type;
public String url;
public String sha1;
}
@Keep
public static class JavaVersionInfo {
public String component;
public int majorVersion;
}
@Keep
public static class LoggingConfig {
public LoggingClientConfig client;
@Keep
public static class LoggingClientConfig {
public String argument;
public FileProperties file;
public String type;
}
}
// Since 1.13
@Keep
public static class Arguments {
public Object[] game;
public Object[] jvm;
@Keep
public static class ArgValue {
public ArgRules[] rules;
public String value;
// TLauncher styled argument...
public String[] values;
@Keep
public static class ArgRules {
public String action;
public String features;
@ -62,9 +82,9 @@ public class JMinecraftVersionList {
}
}
}
public static class AssetIndex {
public String id, sha1, url;
public long size, totalSize;
@Keep
public static class AssetIndex extends FileProperties {
public long totalSize;
}
}

View file

@ -1,220 +1,172 @@
package net.kdt.pojavlaunch;
import android.graphics.*;
import android.annotation.SuppressLint;
import android.os.*;
import android.util.*;
import android.view.*;
import android.view.View.*;
import android.widget.*;
import java.io.*;
import java.util.*;
import net.kdt.pojavlaunch.customcontrols.keyboard.AwtCharSender;
import net.kdt.pojavlaunch.customcontrols.keyboard.TouchCharInput;
import net.kdt.pojavlaunch.multirt.MultiRTUtils;
import net.kdt.pojavlaunch.prefs.*;
import net.kdt.pojavlaunch.utils.*;
import org.lwjgl.glfw.*;
import static net.kdt.pojavlaunch.utils.MathUtils.map;
import com.kdt.LoggerView;
public class JavaGUILauncherActivity extends LoggableActivity implements View.OnTouchListener {
public class JavaGUILauncherActivity extends BaseActivity implements View.OnTouchListener {
private static final int MSG_LEFT_MOUSE_BUTTON_CHECK = 1028;
private AWTCanvasView mTextureView;
private LinearLayout contentLog;
private TextView textLog;
private ScrollView contentScroll;
private ToggleButton toggleLog;
private LoggerView mLoggerView;
private TouchCharInput mTouchCharInput;
private LinearLayout touchPad;
private ImageView mousePointer;
private GestureDetector gestureDetector;
private File logFile;
private PrintStream logStream;
private final Object mDialogLock = new Object();
private boolean isLogAllow, mSkipDetectMod;
private boolean rightOverride = false;
private int scaleFactor;
private int[] scaleFactors = initScaleFactors();
private final int fingerStillThreshold = 8;
private int initialX;
private int initialY;
private static boolean triggeredLeftMouseButton = false;
private Handler theHandler = new Handler() {
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_LEFT_MOUSE_BUTTON_CHECK: {
int x = CallbackBridge.mouseX;
int y = CallbackBridge.mouseY;
if (CallbackBridge.isGrabbing() &&
Math.abs(initialX - x) < fingerStillThreshold &&
Math.abs(initialY - y) < fingerStillThreshold) {
triggeredLeftMouseButton = true;
AWTInputBridge.sendMousePress(AWTInputEvent.BUTTON1_DOWN_MASK, true);
}
} break;
}
}
};
private LinearLayout mTouchPad;
private ImageView mMousePointerImageView;
private GestureDetector mGestureDetector;
private boolean mSkipDetectMod, mIsVirtualMouseEnabled;
@SuppressLint("ClickableViewAccessibility")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.install_mod);
setContentView(R.layout.activity_java_gui_launcher);
Logger.getInstance().reset();
mTouchCharInput = findViewById(R.id.awt_touch_char);
mTouchCharInput.setCharacterSender(new AwtCharSender());
mTouchPad = findViewById(R.id.main_touchpad);
mLoggerView = findViewById(R.id.launcherLoggerView);
mMousePointerImageView = findViewById(R.id.main_mouse_pointer);
mTextureView = findViewById(R.id.installmod_surfaceview);
mGestureDetector = new GestureDetector(this, new SingleTapConfirm());
mTouchPad.setFocusable(false);
mTouchPad.setVisibility(View.GONE);
findViewById(R.id.installmod_mouse_pri).setOnTouchListener(this);
findViewById(R.id.installmod_mouse_sec).setOnTouchListener(this);
mMousePointerImageView.post(() -> {
ViewGroup.LayoutParams params = mMousePointerImageView.getLayoutParams();
params.width = (int) (36 / 100f * LauncherPreferences.PREF_MOUSESCALE);
params.height = (int) (54 / 100f * LauncherPreferences.PREF_MOUSESCALE);
});
mTouchPad.setOnTouchListener(new View.OnTouchListener() {
float prevX = 0, prevY = 0;
@Override
public boolean onTouch(View v, MotionEvent event) {
// MotionEvent reports input details from the touch screen
// and other input controls. In this case, you are only
// interested in events where the touch position changed.
// int index = event.getActionIndex();
int action = event.getActionMasked();
float x = event.getX();
float y = event.getY();
float mouseX, mouseY;
mouseX = mMousePointerImageView.getX();
mouseY = mMousePointerImageView.getY();
if (mGestureDetector.onTouchEvent(event)) {
sendScaledMousePosition(mouseX,mouseY);
AWTInputBridge.sendMousePress(AWTInputEvent.BUTTON1_DOWN_MASK);
} else {
if (action == MotionEvent.ACTION_MOVE) { // 2
mouseX = Math.max(0, Math.min(CallbackBridge.physicalWidth, mouseX + x - prevX));
mouseY = Math.max(0, Math.min(CallbackBridge.physicalHeight, mouseY + y - prevY));
placeMouseAt(mouseX, mouseY);
sendScaledMousePosition(mouseX, mouseY);
}
}
// debugText.setText(CallbackBridge.DEBUG_STRING.toString());
CallbackBridge.DEBUG_STRING.setLength(0);
prevY = y;
prevX = x;
return true;
}
});
mTextureView.setOnTouchListener((v, event) -> {
float x = event.getX();
float y = event.getY();
if (mGestureDetector.onTouchEvent(event)) {
sendScaledMousePosition(x + mTextureView.getX(), y);
AWTInputBridge.sendMousePress(AWTInputEvent.BUTTON1_DOWN_MASK);
return true;
}
switch (event.getActionMasked()) {
case MotionEvent.ACTION_UP: // 1
case MotionEvent.ACTION_CANCEL: // 3
case MotionEvent.ACTION_POINTER_UP: // 6
break;
case MotionEvent.ACTION_MOVE: // 2
sendScaledMousePosition(x + mTextureView.getX(), y);
break;
}
return true;
});
Tools.updateWindowSize(this);
try {
MultiRTUtils.setRuntimeNamed(this,LauncherPreferences.PREF_DEFAULT_RUNTIME);
gestureDetector = new GestureDetector(this, new SingleTapConfirm());
JREUtils.jreReleaseList = JREUtils.readJREReleaseProperties(LauncherPreferences.PREF_DEFAULT_RUNTIME);
if (JREUtils.jreReleaseList.get("JAVA_VERSION").equals("1.8.0")) {
MultiRTUtils.setRuntimeNamed(this,LauncherPreferences.PREF_DEFAULT_RUNTIME);
} else {
MultiRTUtils.setRuntimeNamed(this,MultiRTUtils.getExactJreName(8));
JREUtils.jreReleaseList = JREUtils.readJREReleaseProperties();
}
findViewById(R.id.installmod_mouse_pri).setOnTouchListener(this);
findViewById(R.id.installmod_mouse_sec).setOnTouchListener(this);
this.touchPad = findViewById(R.id.main_touchpad);
touchPad.setFocusable(false);
this.mousePointer = findViewById(R.id.main_mouse_pointer);
this.mousePointer.post(() -> {
ViewGroup.LayoutParams params = mousePointer.getLayoutParams();
params.width = (int) (36 / 100f * LauncherPreferences.PREF_MOUSESCALE);
params.height = (int) (54 / 100f * LauncherPreferences.PREF_MOUSESCALE);
});
touchPad.setOnTouchListener(new OnTouchListener(){
private float prevX, prevY;
@Override
public boolean onTouch(View v, MotionEvent event) {
// MotionEvent reports input details from the touch screen
// and other input controls. In this case, you are only
// interested in events where the touch position changed.
// int index = event.getActionIndex();
int action = event.getActionMasked();
float x = event.getX();
float y = event.getY();
if(event.getHistorySize() > 0) {
prevX = event.getHistoricalX(0);
prevY = event.getHistoricalY(0);
}else{
prevX = x;
prevY = y;
}
float mouseX = mousePointer.getX();
float mouseY = mousePointer.getY();
if (gestureDetector.onTouchEvent(event)) {
sendScaledMousePosition(mouseX,mouseY);
AWTInputBridge.sendMousePress(rightOverride ? AWTInputEvent.BUTTON3_DOWN_MASK : AWTInputEvent.BUTTON1_DOWN_MASK);
} else {
switch (action) {
case MotionEvent.ACTION_UP: // 1
case MotionEvent.ACTION_CANCEL: // 3
case MotionEvent.ACTION_POINTER_UP: // 6
break;
case MotionEvent.ACTION_MOVE: // 2
mouseX = Math.max(0, Math.min(CallbackBridge.physicalWidth, mouseX + x - prevX));
mouseY = Math.max(0, Math.min(CallbackBridge.physicalHeight, mouseY + y - prevY));
placeMouseAt(mouseX, mouseY);
sendScaledMousePosition(mouseX,mouseY);
/*
if (!CallbackBridge.isGrabbing()) {
CallbackBridge.sendMouseKeycode(LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_LEFT, 0, isLeftMouseDown);
CallbackBridge.sendMouseKeycode(LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_RIGHT, 0, isRightMouseDown);
}
*/
break;
}
}
// debugText.setText(CallbackBridge.DEBUG_STRING.toString());
CallbackBridge.DEBUG_STRING.setLength(0);
return true;
}
});
placeMouseAt(CallbackBridge.physicalWidth / 2, CallbackBridge.physicalHeight / 2);
logFile = new File(Tools.DIR_GAME_HOME, "latestlog.txt");
logFile.delete();
logFile.createNewFile();
logStream = new PrintStream(logFile.getAbsolutePath());
this.contentLog = findViewById(R.id.content_log_layout);
this.contentScroll = (ScrollView) findViewById(R.id.content_log_scroll);
this.textLog = (TextView) contentScroll.getChildAt(0);
this.toggleLog = (ToggleButton) findViewById(R.id.content_log_toggle_log);
this.toggleLog.setChecked(false);
// this.textLogBehindGL = (TextView) findViewById(R.id.main_log_behind_GL);
// this.textLogBehindGL.setTypeface(Typeface.MONOSPACE);
this.textLog.setTypeface(Typeface.MONOSPACE);
this.toggleLog.setOnCheckedChangeListener(new ToggleButton.OnCheckedChangeListener(){
@Override
public void onCheckedChanged(CompoundButton button, boolean isChecked) {
isLogAllow = isChecked;
appendToLog("");
}
});
final File modFile = (File) getIntent().getExtras().getSerializable("modFile");
final String javaArgs = getIntent().getExtras().getString("javaArgs");
mTextureView = findViewById(R.id.installmod_surfaceview);
mSkipDetectMod = getIntent().getExtras().getBoolean("skipDetectMod", false);
if (mSkipDetectMod) {
new Thread(new Runnable(){
@Override
public void run() {
launchJavaRuntime(modFile, javaArgs);
}
}, "JREMainThread").start();
} else {
openLogOutput(null);
new Thread(new Runnable(){
@Override
public void run() {
try {
final int exit = doCustomInstall(modFile, javaArgs);
appendlnToLog(getString(R.string.toast_optifine_success));
if (exit == 0) {
runOnUiThread(new Runnable(){
@Override
public void run() {
Toast.makeText(JavaGUILauncherActivity.this, R.string.toast_optifine_success, Toast.LENGTH_SHORT).show();
MainActivity.fullyExit();
}
});
} /* else {
throw new ErrnoException(getString(R.string.glo, exit);
} */
} catch (Throwable e) {
appendlnToLog("Install failed:");
appendlnToLog(Log.getStackTraceString(e));
Tools.showError(JavaGUILauncherActivity.this, e);
}
}
}, "Installer").start();
new Thread(() -> launchJavaRuntime(modFile, javaArgs), "JREMainThread").start();
return;
}
// No skip detection
openLogOutput(null);
new Thread(() -> {
try {
final int exit = doCustomInstall(modFile, javaArgs);
Logger.getInstance().appendToLog(getString(R.string.toast_optifine_success));
if (exit != 0) return;
runOnUiThread(() -> {
Toast.makeText(JavaGUILauncherActivity.this, R.string.toast_optifine_success, Toast.LENGTH_SHORT).show();
MainActivity.fullyExit();
});
} catch (Throwable e) {
Logger.getInstance().appendToLog("Install failed:");
Logger.getInstance().appendToLog(Log.getStackTraceString(e));
Tools.showError(JavaGUILauncherActivity.this, e);
}
}, "Installer").start();
} catch (Throwable th) {
Tools.showError(this, th, true);
}
}
@Override
public void onResume() {
super.onResume();
final int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
final View decorView = getWindow().getDecorView();
decorView.setSystemUiVisibility(uiOptions);
}
@Override
public boolean onTouch(View v, MotionEvent e) {
boolean isDown;
@ -245,18 +197,24 @@ public class JavaGUILauncherActivity extends LoggableActivity implements View.On
}
public void placeMouseAdd(float x, float y) {
this.mousePointer.setX(mousePointer.getX() + x);
this.mousePointer.setY(mousePointer.getY() + y);
mMousePointerImageView.setX(mMousePointerImageView.getX() + x);
mMousePointerImageView.setY(mMousePointerImageView.getY() + y);
}
public void placeMouseAt(float x, float y) {
this.mousePointer.setX(x);
this.mousePointer.setY(y);
mMousePointerImageView.setX(x);
mMousePointerImageView.setY(y);
}
void sendScaledMousePosition(float x, float y){
AWTInputBridge.sendMousePos((int) map(x,0,CallbackBridge.physicalWidth, scaleFactors[0], scaleFactors[2]),
(int) map(y,0,CallbackBridge.physicalHeight, scaleFactors[1], scaleFactors[3]));
// Clamp positions to the borders of the usable view, then scale them
x = androidx.core.math.MathUtils.clamp(x, mTextureView.getX(), mTextureView.getX() + mTextureView.getWidth());
y = androidx.core.math.MathUtils.clamp(y, mTextureView.getY(), mTextureView.getY() + mTextureView.getHeight());
AWTInputBridge.sendMousePos(
(int) MathUtils.map(x, mTextureView.getX(), mTextureView.getX() + mTextureView.getWidth(), 0, AWTCanvasView.AWT_CANVAS_WIDTH),
(int) MathUtils.map(y, mTextureView.getY(), mTextureView.getY() + mTextureView.getHeight(), 0, AWTCanvasView.AWT_CANVAS_HEIGHT)
);
}
public void forceClose(View v) {
@ -264,34 +222,28 @@ public class JavaGUILauncherActivity extends LoggableActivity implements View.On
}
public void openLogOutput(View v) {
contentLog.setVisibility(View.VISIBLE);
mLoggerView.setVisibility(View.VISIBLE);
}
public void closeLogOutput(View view) {
if (mSkipDetectMod) {
contentLog.setVisibility(View.GONE);
mLoggerView.setVisibility(View.GONE);
} else {
forceClose(null);
}
}
private int doCustomInstall(File modFile, String javaArgs) throws IOException {
isLogAllow = true;
mSkipDetectMod = true;
return launchJavaRuntime(modFile, javaArgs);
public void toggleVirtualMouse(View v) {
mIsVirtualMouseEnabled = !mIsVirtualMouseEnabled;
mTouchPad.setVisibility(mIsVirtualMouseEnabled ? View.VISIBLE : View.GONE);
Toast.makeText(this,
mIsVirtualMouseEnabled ? R.string.control_mouseon : R.string.control_mouseoff,
Toast.LENGTH_SHORT).show();
}
public int launchJavaRuntime(File modFile, String javaArgs) {
JREUtils.redirectAndPrintJRELog(this);
JREUtils.redirectAndPrintJRELog();
try {
jreReleaseList = JREUtils.readJREReleaseProperties();
// Fail immediately when Java 8 is not selected
// TODO: auto override Java 8 if installed
if (!jreReleaseList.get("JAVA_VERSION").equals("1.8.0")) {
throw new RuntimeException("Cannot use the mod installer. In order to use the mod installer, you need to install Java 8 and specify it in the Preferences menu.");
}
List<String> javaArgList = new ArrayList<String>();
// Enable Caciocavallo
@ -304,7 +256,7 @@ public class JavaGUILauncherActivity extends LoggableActivity implements View.On
javaArgList.add(modFile.getAbsolutePath());
}
appendlnToLog("Info: Java arguments: " + Arrays.toString(javaArgList.toArray(new String[0])));
Logger.getInstance().appendToLog("Info: Java arguments: " + Arrays.toString(javaArgList.toArray(new String[0])));
// Run java on sandbox, non-overrideable.
Collections.reverse(javaArgList);
@ -320,64 +272,14 @@ public class JavaGUILauncherActivity extends LoggableActivity implements View.On
}
}
@Override
public void onResume() {
super.onResume();
final int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
final View decorView = getWindow().getDecorView();
decorView.setSystemUiVisibility(uiOptions);
private int doCustomInstall(File modFile, String javaArgs) throws IOException {
mSkipDetectMod = true;
return launchJavaRuntime(modFile, javaArgs);
}
@Override
public void appendToLog(final String text, boolean checkAllow) {
logStream.print(text);
if (checkAllow && !isLogAllow) return;
textLog.post(() -> {
textLog.append(text);
contentScroll.fullScroll(ScrollView.FOCUS_DOWN);
});
}
int[] initScaleFactors(){
return initScaleFactors(true);
}
int[] initScaleFactors(boolean autoScale){
//Could be optimized
if(autoScale) { //Auto scale
int minDimension = Math.min(CallbackBridge.physicalHeight, CallbackBridge.physicalWidth);
scaleFactor = Math.max(((3 * minDimension) / 1080) - 1, 1);
}
int[] scales = new int[4]; //Left, Top, Right, Bottom
scales[0] = (CallbackBridge.physicalWidth/2);
scales[0] -= scales[0]/scaleFactor;
scales[1] = (CallbackBridge.physicalHeight/2);
scales[1] -= scales[1]/scaleFactor;
scales[2] = (CallbackBridge.physicalWidth/2);
scales[2] += scales[2]/scaleFactor;
scales[3] = (CallbackBridge.physicalHeight/2);
scales[3] += scales[3]/scaleFactor;
return scales;
}
public void scaleDown(View view) {
scaleFactor = Math.max(scaleFactor - 1, 1);
scaleFactors = initScaleFactors(false);
mTextureView.initScaleFactors(scaleFactor);
sendScaledMousePosition(mousePointer.getX(),mousePointer.getY());
}
public void scaleUp(View view) {
scaleFactor = Math.min(scaleFactor + 1, 6);
scaleFactors = initScaleFactors(false);
mTextureView.initScaleFactors(scaleFactor);
sendScaledMousePosition(mousePointer.getX(),mousePointer.getY());
public void toggleKeyboard(View view) {
mTouchCharInput.switchKeyboardState();
}
}

View file

@ -1,27 +0,0 @@
package net.kdt.pojavlaunch;
import java.util.Map;
public abstract class LoggableActivity extends BaseActivity {
public Map<String, String> jreReleaseList;
private boolean filteredSessionID = false;
public void appendToLog(String text) {
appendToLog(text, true);
}
public void appendlnToLog(String text) {
// Filter out Session ID here
int index;
if (!filteredSessionID && (index = text.indexOf("(Session ID is ")) != -1) {
text = text.substring(0, index) + "(Session ID is <censored>)";
filteredSessionID = true;
}
appendlnToLog(text, true);
}
public void appendlnToLog(String text, boolean checkAllow) {
appendToLog(text + "\n", checkAllow);
}
public abstract void appendToLog(final String text, boolean checkAllow);
}

View file

@ -0,0 +1,107 @@
package net.kdt.pojavlaunch;
import androidx.annotation.Keep;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.ref.WeakReference;
/** Singleton class made to log on one file
* The singleton part can be removed but will require more implementation from the end-dev
*/
@Keep
public class Logger {
private static Logger sLoggerSingleton = null;
/* Instance variables */
private final File mLogFile;
private PrintStream mLogStream;
private WeakReference<eventLogListener> mLogListenerWeakReference = null;
/* No public construction */
private Logger(){
this("latestlog.txt");
}
private Logger(String fileName){
mLogFile = new File(Tools.DIR_GAME_HOME, fileName);
// Make a new instance of the log file
mLogFile.delete();
try {
mLogFile.createNewFile();
mLogStream = new PrintStream(mLogFile.getAbsolutePath());
}catch (IOException e){e.printStackTrace();}
}
public static Logger getInstance(){
if(sLoggerSingleton == null){
synchronized(Logger.class){
if(sLoggerSingleton == null){
sLoggerSingleton = new Logger();
}
}
}
return sLoggerSingleton;
}
/** Print the text to the log file if not censored */
public void appendToLog(String text){
if(shouldCensorLog(text)) return;
appendToLogUnchecked(text);
}
/** Print the text to the log file, no china censoring there */
public void appendToLogUnchecked(String text){
mLogStream.println(text);
notifyLogListener(text);
}
/** Reset the log file, effectively erasing any previous logs */
public void reset(){
try{
mLogFile.delete();
mLogFile.createNewFile();
mLogStream = new PrintStream(mLogFile.getAbsolutePath());
}catch (IOException e){ e.printStackTrace();}
}
/** Disables the printing */
public void shutdown(){
mLogStream.close();
}
/**
* Perform various checks to see if the log is safe to print
* Subclasses may want to override this behavior
* @param text The text to check
* @return Whether the log should be censored
*/
private static boolean shouldCensorLog(String text){
if(text.contains("Session ID is")) return true;
return false;
}
/** Small listener for anything listening to the log */
public interface eventLogListener {
void onEventLogged(String text);
}
/** Link a log listener to the logger */
public void setLogListener(eventLogListener logListener){
this.mLogListenerWeakReference = new WeakReference<>(logListener);
}
/** Notifies the event listener, if it exists */
private void notifyLogListener(String text){
if(mLogListenerWeakReference == null) return;
eventLogListener logListener = mLogListenerWeakReference.get();
if(logListener == null){
mLogListenerWeakReference = null;
return;
}
logListener.onEventLogged(text);
}
}

View file

@ -30,8 +30,7 @@
package net.kdt.pojavlaunch;
public class LWJGLGLFWKeycode
{
public class LwjglGlfwKeycode {
/** The unknown key. */
public static final short GLFW_KEY_UNKNOWN = 0; // should be -1
@ -195,5 +194,8 @@ public class LWJGLGLFWKeycode
GLFW_MOUSE_BUTTON_LEFT = GLFW_MOUSE_BUTTON_1,
GLFW_MOUSE_BUTTON_RIGHT = GLFW_MOUSE_BUTTON_2,
GLFW_MOUSE_BUTTON_MIDDLE = GLFW_MOUSE_BUTTON_3;
public static final int
GLFW_VISIBLE = 0x20004,
GLFW_HOVERED = 0x2000B;
}

View file

@ -3,10 +3,11 @@ package net.kdt.pojavlaunch;
import android.app.Activity;
import android.content.Intent;
import android.os.*;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import androidx.annotation.Nullable;
import net.kdt.pojavlaunch.customcontrols.*;
import net.kdt.pojavlaunch.prefs.*;
import net.kdt.pojavlaunch.utils.MCOptionUtils;
@ -14,58 +15,45 @@ import net.kdt.pojavlaunch.utils.MCOptionUtils;
import java.io.*;
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.DEFAULT_PREF;
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_SUSTAINED_PERFORMANCE;
public class MainActivity extends BaseMainActivity {
public static ControlLayout mControlLayout;
private FileObserver fileObserver;
private MCOptionUtils.MCOptionListener optionListener;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initLayout(R.layout.main_with_customctrl);
super.ingameControlsEditorListener = menuItem -> {
switch (menuItem.getItemId()) {
case R.id.menu_ctrl_load:
CustomControlsActivity.load(mControlLayout);
break;
case R.id.menu_ctrl_add:
initLayout(R.layout.activity_basemain);
// Set the sustained performance mode for available APIs
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
getWindow().setSustainedPerformanceMode(PREF_SUSTAINED_PERFORMANCE);
ingameControlsEditorArrayAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, getResources().getStringArray(R.array.menu_customcontrol));
ingameControlsEditorListener = (parent, view, position, id) -> {
switch(position) {
case 0:
mControlLayout.addControlButton(new ControlData("New"));
break;
case R.id.menu_ctrl_add_drawer:
case 1:
mControlLayout.addDrawer(new ControlDrawerData());
break;
case R.id.menu_ctrl_selectdefault:
CustomControlsActivity.dialogSelectDefaultCtrl(mControlLayout);
case 2:
CustomControlsActivity.load(mControlLayout);
break;
case R.id.menu_ctrl_save:
case 3:
CustomControlsActivity.save(true,mControlLayout);
break;
case 4:
CustomControlsActivity.dialogSelectDefaultCtrl(mControlLayout);
break;
}
//Toast.makeText(MainActivity.this, menuItem.getTitle() + ":" + menuItem.getItemId(), Toast.LENGTH_SHORT).show();
return true;
};
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q){
fileObserver = new FileObserver(new File(Tools.DIR_GAME_NEW + "/options.txt"), FileObserver.MODIFY) {
@Override
public void onEvent(int i, @Nullable String s) {
MCOptionUtils.load();
getMcScale();
}
};
}else{
fileObserver = new FileObserver(Tools.DIR_GAME_NEW + "/options.txt", FileObserver.MODIFY) {
@Override
public void onEvent(int i, @Nullable String s) {
MCOptionUtils.load();
getMcScale();
}
};
}
fileObserver.startWatching();
// Recompute the gui scale when options are changed
optionListener = MCOptionUtils::getMcScale;
MCOptionUtils.addMCOptionListener(optionListener);
mControlLayout = findViewById(R.id.main_control_layout);
mControlLayout.setModifiable(false);

View file

@ -1,87 +0,0 @@
package net.kdt.pojavlaunch;
import android.content.Context;
import android.text.InputFilter;
import android.text.InputFilter.LengthFilter;
import android.text.Spanned;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputConnectionWrapper;
import android.widget.EditText;
public class MainEditText extends EditText {
public MainKeyWatcher _MainKeyWatcher;
public final int allowedLength;
public final boolean limitInput;
private class MainInputConnection extends InputConnectionWrapper {
MainEditText textbox;
public MainInputConnection(InputConnection target, boolean mutable, MainEditText textbox2) {
super(target, mutable);
this.textbox = textbox2;
}
public boolean sendKeyEvent(KeyEvent event) {
if (this.textbox.getText().length() != 0 || event.getAction() != 0 || event.getKeyCode() != 67) {
return super.sendKeyEvent(event);
}
if (MainEditText.this._MainKeyWatcher != null) {
MainEditText.this._MainKeyWatcher.onDeleteKeyPressed();
}
return false;
}
}
public interface MainKeyWatcher {
void onBackKeyPressed();
void onDeleteKeyPressed();
}
public MainEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
this._MainKeyWatcher = null;
this.allowedLength = 160;
this.limitInput = false;
}
public MainEditText(Context context, AttributeSet attrs) {
super(context, attrs);
this._MainKeyWatcher = null;
this.allowedLength = 160;
this.limitInput = false;
}
public MainEditText(Context context, int allowedLength2, boolean limitInput2) {
super(context);
this._MainKeyWatcher = null;
this.allowedLength = allowedLength2;
this.limitInput = limitInput2;
setFilters(limitInput2 ? new InputFilter[]{new LengthFilter(this.allowedLength), new InputFilter() {
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
return (!source.equals("") && !source.toString().matches("^[a-zA-Z0-9_ -^~'.,;!#&()=`{}]*")) ? "" : source;
}
}} : new InputFilter[]{new LengthFilter(this.allowedLength)});
}
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
return new MainInputConnection(super.onCreateInputConnection(outAttrs), true, this);
}
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
if (keyCode != KeyEvent.KEYCODE_BACK || event.getAction() != KeyEvent.ACTION_UP) {
return super.dispatchKeyEvent(event);
}
if (this._MainKeyWatcher != null) {
this._MainKeyWatcher.onBackKeyPressed();
}
return false;
}
public void setOnMainKeyWatcher(MainKeyWatcher MainKeyWatcher) {
this._MainKeyWatcher = MainKeyWatcher;
}
}

View file

@ -0,0 +1,649 @@
package net.kdt.pojavlaunch;
import static net.kdt.pojavlaunch.BaseMainActivity.touchCharInput;
import static net.kdt.pojavlaunch.utils.MCOptionUtils.getMcScale;
import static org.lwjgl.glfw.CallbackBridge.sendKeyPress;
import static org.lwjgl.glfw.CallbackBridge.sendMouseButton;
import static org.lwjgl.glfw.CallbackBridge.windowHeight;
import static org.lwjgl.glfw.CallbackBridge.windowWidth;
import android.app.Activity;
import android.content.Context;
import android.graphics.SurfaceTexture;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.TextureView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import net.kdt.pojavlaunch.utils.MathUtils;
import net.kdt.pojavlaunch.customcontrols.gamepad.Gamepad;
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
import net.kdt.pojavlaunch.utils.JREUtils;
import net.kdt.pojavlaunch.utils.MCOptionUtils;
import org.lwjgl.glfw.CallbackBridge;
/**
* Class dealing with showing minecraft surface and taking inputs to dispatch them to minecraft
*/
public class MinecraftGLSurface extends View {
/* Gamepad object for gamepad inputs, instantiated on need */
private Gamepad mGamepad = null;
/* Pointer Debug textview, used to show info about the pointer state */
private TextView mPointerDebugTextView;
/* Resolution scaler option, allow downsizing a window */
private final float mScaleFactor = LauncherPreferences.PREF_SCALE_FACTOR/100f;
/* Sensitivity, adjusted according to screen size */
private final double mSensitivityFactor = (1.4 * (1080f/ Tools.getDisplayMetrics((Activity) getContext()).heightPixels));
/* Use to detect simple and double taps */
private final TapDetector mSingleTapDetector = new TapDetector(1, TapDetector.DETECTION_METHOD_BOTH);
private final TapDetector mDoubleTapDetector = new TapDetector(2, TapDetector.DETECTION_METHOD_DOWN);
/* MC GUI scale, listened by MCOptionUtils */
private int mGuiScale = getMcScale();
private final MCOptionUtils.MCOptionListener mGuiScaleListener = () -> mGuiScale = getMcScale();
/* Surface ready listener, used by the activity to launch minecraft */
SurfaceReadyListener mSurfaceReadyListener = null;
/* View holding the surface, either a SurfaceView or a TextureView */
View mSurface;
/* List of hotbarKeys, used when clicking on the hotbar */
private static final int[] HOTBAR_KEYS = {
LwjglGlfwKeycode.GLFW_KEY_1, LwjglGlfwKeycode.GLFW_KEY_2, LwjglGlfwKeycode.GLFW_KEY_3,
LwjglGlfwKeycode.GLFW_KEY_4, LwjglGlfwKeycode.GLFW_KEY_5, LwjglGlfwKeycode.GLFW_KEY_6,
LwjglGlfwKeycode.GLFW_KEY_7, LwjglGlfwKeycode.GLFW_KEY_8, LwjglGlfwKeycode.GLFW_KEY_9};
/* Last hotbar button (0-9) registered */
private int mLastHotbarKey = -1;
/* Events can start with only a move instead of an pointerDown due to mouse passthrough */
private boolean mShouldBeDown = false;
/* When fingers are really near to each other, it tends to either swap or remove a pointer ! */
private int mLastPointerCount = 0;
/* Previous MotionEvent position, not scale */
private float mPrevX, mPrevY;
/* PointerID used for the moving camera */
private int mCurrentPointerID = -1000;
/* Initial first pointer positions non-scaled, used to test touch sloppiness */
private float mInitialX, mInitialY;
/* Last first pointer positions non-scaled, used to scroll distance */
private float mScrollLastInitialX, mScrollLastInitialY;
/* How much distance a finger has to go for touch sloppiness to be disabled */
public static final int FINGER_STILL_THRESHOLD = (int) Tools.dpToPx(9);
/* How much distance a finger has to go to scroll */
public static final int FINGER_SCROLL_THRESHOLD = (int) Tools.dpToPx(6);
/* Whether the button was triggered, used by the handler */
private static boolean triggeredLeftMouseButton = false;
/* Handle hotbar throw button and mouse mining button */
public static final int MSG_LEFT_MOUSE_BUTTON_CHECK = 1028;
public static final int MSG_DROP_ITEM_BUTTON_CHECK = 1029;
private final Handler mHandler = new Handler(Looper.getMainLooper()) {
public void handleMessage(Message msg) {
if(msg.what == MSG_LEFT_MOUSE_BUTTON_CHECK) {
if (LauncherPreferences.PREF_DISABLE_GESTURES) return;
float x = CallbackBridge.mouseX;
float y = CallbackBridge.mouseY;
if (CallbackBridge.isGrabbing() &&
MathUtils.dist(x, y, mInitialX, mInitialY) < FINGER_STILL_THRESHOLD) {
triggeredLeftMouseButton = true;
sendMouseButton(LwjglGlfwKeycode.GLFW_MOUSE_BUTTON_LEFT, true);
}
return;
}
if(msg.what == MSG_DROP_ITEM_BUTTON_CHECK) {
if(CallbackBridge.isGrabbing()){
sendKeyPress(LwjglGlfwKeycode.GLFW_KEY_Q);
mHandler.sendEmptyMessageDelayed(MSG_DROP_ITEM_BUTTON_CHECK, 600);
}
return;
}
}
};
public MinecraftGLSurface(Context context) {
this(context, null);
}
public MinecraftGLSurface(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
setFocusable(true);
MCOptionUtils.addMCOptionListener(mGuiScaleListener);
}
/** Initialize the view and all its settings */
public void start(){
// Add the pointer debug textview
mPointerDebugTextView = new TextView(getContext());
mPointerDebugTextView.setX(0);
mPointerDebugTextView.setY(0);
mPointerDebugTextView.setVisibility(GONE);
((ViewGroup)getParent()).addView(mPointerDebugTextView);
if(LauncherPreferences.PREF_USE_ALTERNATE_SURFACE){
SurfaceView surfaceView = new SurfaceView(getContext());
mSurface = surfaceView;
surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
private boolean isCalled = false;
@Override
public void surfaceCreated(@NonNull SurfaceHolder holder) {
if(isCalled) return;
isCalled = true;
realStart(surfaceView.getHolder().getSurface());
}
@Override
public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
refreshSize();
}
@Override
public void surfaceDestroyed(@NonNull SurfaceHolder holder) {}
});
((ViewGroup)getParent()).addView(surfaceView);
}else{
TextureView textureView = new TextureView(getContext());
textureView.setOpaque(true);
mSurface = textureView;
textureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
private boolean isCalled = false;
@Override
public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surface, int width, int height) {
if(isCalled) return;
isCalled = true;
realStart(new Surface(textureView.getSurfaceTexture()));
}
@Override
public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surface, int width, int height) {
refreshSize();
}
@Override
public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) {
return true;
}
@Override
public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) {}
});
((ViewGroup)getParent()).addView(textureView);
}
}
/**
* The touch event for both grabbed an non-grabbed mouse state on the touch screen
* Does not cover the virtual mouse touchpad
*/
@Override
public boolean onTouchEvent(MotionEvent e) {
// Looking for a mouse to handle, won't have an effect if no mouse exists.
for (int i = 0; i < e.getPointerCount(); i++) {
if (e.getToolType(i) != MotionEvent.TOOL_TYPE_MOUSE) continue;
// Mouse found
if(CallbackBridge.isGrabbing()) return false;
CallbackBridge.sendCursorPos( e.getX(i) * mScaleFactor, e.getY(i) * mScaleFactor);
return true; //mouse event handled successfully
}
// System.out.println("Pre touch, isTouchInHotbar=" + Boolean.toString(isTouchInHotbar) + ", action=" + MotionEvent.actionToString(e.getActionMasked()));
//Getting scaled position from the event
/* Tells if a double tap happened [MOUSE GRAB ONLY]. Doesn't tell where though. */
if(!CallbackBridge.isGrabbing()) {
CallbackBridge.mouseX = (e.getX() * mScaleFactor);
CallbackBridge.mouseY = (e.getY() * mScaleFactor);
//One android click = one MC click
if(mSingleTapDetector.onTouchEvent(e)){
CallbackBridge.putMouseEventWithCoords(LwjglGlfwKeycode.GLFW_MOUSE_BUTTON_LEFT, CallbackBridge.mouseX, CallbackBridge.mouseY);
return true;
}
}
// Check double tap state, used for the hotbar
boolean hasDoubleTapped = mDoubleTapDetector.onTouchEvent(e);
switch (e.getActionMasked()) {
case MotionEvent.ACTION_MOVE:
int pointerCount = e.getPointerCount();
// In-menu interactions
if(!CallbackBridge.isGrabbing()){
// Touch hover
if(pointerCount == 1){
CallbackBridge.sendCursorPos(CallbackBridge.mouseX, CallbackBridge.mouseY);
mPrevX = e.getX();
mPrevY = e.getY();
break;
}
// Scrolling feature
if(LauncherPreferences.PREF_DISABLE_GESTURES) break;
// The pointer count can never be 0, and it is not 1, therefore it is >= 2
int hScroll = ((int) (e.getX() - mScrollLastInitialX)) / FINGER_SCROLL_THRESHOLD;
int vScroll = ((int) (e.getY() - mScrollLastInitialY)) / FINGER_SCROLL_THRESHOLD;
if(vScroll != 0 || hScroll != 0){
CallbackBridge.sendScroll(hScroll, vScroll);
mScrollLastInitialX = e.getX();
mScrollLastInitialY = e.getY();
}
break;
}
// Camera movement
int pointerIndex = e.findPointerIndex(mCurrentPointerID);
int hudKeyHandled = handleGuiBar((int)e.getX(), (int) e.getY());
// Start movement, due to new pointer or loss of pointer
if (pointerIndex == -1 || mLastPointerCount != pointerCount || !mShouldBeDown) {
if(hudKeyHandled != -1) break; //No pointer attribution on hotbar
mShouldBeDown = true;
mCurrentPointerID = e.getPointerId(0);
mPrevX = e.getX();
mPrevY = e.getY();
break;
}
// Continue movement as usual
if(hudKeyHandled == -1){ //No camera on hotbar
CallbackBridge.mouseX += (e.getX(pointerIndex) - mPrevX) * mSensitivityFactor;
CallbackBridge.mouseY += (e.getY(pointerIndex) - mPrevY) * mSensitivityFactor;
}
mPrevX = e.getX(pointerIndex);
mPrevY = e.getY(pointerIndex);
CallbackBridge.sendCursorPos(CallbackBridge.mouseX, CallbackBridge.mouseY);
break;
case MotionEvent.ACTION_DOWN: // 0
CallbackBridge.sendPrepareGrabInitialPos();
hudKeyHandled = handleGuiBar((int)e.getX(), (int) e.getY());
boolean isTouchInHotbar = hudKeyHandled != -1;
if (isTouchInHotbar) {
sendKeyPress(hudKeyHandled);
if(hasDoubleTapped && hudKeyHandled == mLastHotbarKey){
//Prevent double tapping Event on two different slots
sendKeyPress(LwjglGlfwKeycode.GLFW_KEY_F);
}
mHandler.sendEmptyMessageDelayed(MSG_DROP_ITEM_BUTTON_CHECK, 350);
CallbackBridge.sendCursorPos(CallbackBridge.mouseX, CallbackBridge.mouseY);
mLastHotbarKey = hudKeyHandled;
break;
}
CallbackBridge.sendCursorPos(CallbackBridge.mouseX, CallbackBridge.mouseY);
mPrevX = e.getX();
mPrevY = e.getY();
if (CallbackBridge.isGrabbing()) {
mCurrentPointerID = e.getPointerId(0);
// It cause hold left mouse while moving camera
mInitialX = CallbackBridge.mouseX;
mInitialY = CallbackBridge.mouseY;
mHandler.sendEmptyMessageDelayed(MSG_LEFT_MOUSE_BUTTON_CHECK, LauncherPreferences.PREF_LONGPRESS_TRIGGER);
}
mLastHotbarKey = hudKeyHandled;
break;
case MotionEvent.ACTION_UP: // 1
case MotionEvent.ACTION_CANCEL: // 3
mShouldBeDown = false;
mCurrentPointerID = -1;
hudKeyHandled = handleGuiBar((int)e.getX(), (int) e.getY());
isTouchInHotbar = hudKeyHandled != -1;
// We only treat in world events
if (!CallbackBridge.isGrabbing()) break;
// Stop the dropping of items
sendKeyPress(LwjglGlfwKeycode.GLFW_KEY_Q, 0, false);
mHandler.removeMessages(MSG_DROP_ITEM_BUTTON_CHECK);
// Remove the mouse left button
if(triggeredLeftMouseButton){
sendMouseButton(LwjglGlfwKeycode.GLFW_MOUSE_BUTTON_LEFT, false);
triggeredLeftMouseButton = false;
break;
}
mHandler.removeMessages(MSG_LEFT_MOUSE_BUTTON_CHECK);
// In case of a short click, just send a quick right click
if(!LauncherPreferences.PREF_DISABLE_GESTURES &&
MathUtils.dist(mInitialX, mInitialY, CallbackBridge.mouseX, CallbackBridge.mouseY) < FINGER_STILL_THRESHOLD){
sendMouseButton(LwjglGlfwKeycode.GLFW_MOUSE_BUTTON_RIGHT, true);
sendMouseButton(LwjglGlfwKeycode.GLFW_MOUSE_BUTTON_RIGHT, false);
}
break;
case MotionEvent.ACTION_POINTER_DOWN: // 5
//TODO Hey we could have some sort of middle click detection ?
mScrollLastInitialX = e.getX();
mScrollLastInitialY = e.getY();
//Checking if we are pressing the hotbar to select the item
hudKeyHandled = handleGuiBar((int)e.getX(e.getPointerCount()-1), (int) e.getY(e.getPointerCount()-1));
if(hudKeyHandled != -1){
sendKeyPress(hudKeyHandled);
if(hasDoubleTapped && hudKeyHandled == mLastHotbarKey){
//Prevent double tapping Event on two different slots
sendKeyPress(LwjglGlfwKeycode.GLFW_KEY_F);
}
}
mLastHotbarKey = hudKeyHandled;
break;
}
// Actualise the pointer count
mLastPointerCount = e.getPointerCount();
//debugText.setText(CallbackBridge.DEBUG_STRING.toString());
CallbackBridge.DEBUG_STRING.setLength(0);
return true;
}
/**
* The event for mouse/joystick movements
* We don't do the gamepad right now.
*/
@Override
public boolean dispatchGenericMotionEvent(MotionEvent event) {
int mouseCursorIndex = -1;
if(Gamepad.isGamepadEvent(event)){
if(mGamepad == null){
mGamepad = new Gamepad(this, event.getDevice());
}
mGamepad.update(event);
return true;
}
for(int i = 0; i < event.getPointerCount(); i++) {
if(event.getToolType(i) != MotionEvent.TOOL_TYPE_MOUSE) continue;
// Mouse found
mouseCursorIndex = i;
break;
}
if(mouseCursorIndex == -1) return false; // we cant consoom that, theres no mice!
if(CallbackBridge.isGrabbing()) {
if(BaseMainActivity.isAndroid8OrHigher()){
requestFocus();
requestPointerCapture();
}
}
switch(event.getActionMasked()) {
case MotionEvent.ACTION_HOVER_MOVE:
CallbackBridge.mouseX = (event.getX(mouseCursorIndex) * mScaleFactor);
CallbackBridge.mouseY = (event.getY(mouseCursorIndex) * mScaleFactor);
CallbackBridge.sendCursorPos(CallbackBridge.mouseX, CallbackBridge.mouseY);
//debugText.setText(CallbackBridge.DEBUG_STRING.toString());
CallbackBridge.DEBUG_STRING.setLength(0);
return true;
case MotionEvent.ACTION_SCROLL:
CallbackBridge.sendScroll((double) event.getAxisValue(MotionEvent.AXIS_VSCROLL), (double) event.getAxisValue(MotionEvent.AXIS_HSCROLL));
return true;
case MotionEvent.ACTION_BUTTON_PRESS:
return sendMouseButtonUnconverted(event.getActionButton(),true);
case MotionEvent.ACTION_BUTTON_RELEASE:
return sendMouseButtonUnconverted(event.getActionButton(),false);
default:
return false;
}
}
//TODO MOVE THIS SOMEWHERE ELSE
private boolean debugErrored = false;
/** The input event for mouse with a captured pointer */
@RequiresApi(26)
@Override
public boolean dispatchCapturedPointerEvent(MotionEvent e) {
CallbackBridge.mouseX += (e.getX()* mScaleFactor);
CallbackBridge.mouseY += (e.getY()* mScaleFactor);
if(!CallbackBridge.isGrabbing()){
releasePointerCapture();
clearFocus();
}
if (mPointerDebugTextView.getVisibility() == View.VISIBLE && !debugErrored) {
StringBuilder builder = new StringBuilder();
try {
builder.append("PointerCapture debug\n");
builder.append("MotionEvent=").append(e.getActionMasked()).append("\n");
builder.append("PressingBtn=").append(MotionEvent.class.getDeclaredMethod("buttonStateToString").invoke(null, e.getButtonState())).append("\n\n");
builder.append("PointerX=").append(e.getX()).append("\n");
builder.append("PointerY=").append(e.getY()).append("\n");
builder.append("RawX=").append(e.getRawX()).append("\n");
builder.append("RawY=").append(e.getRawY()).append("\n\n");
builder.append("XPos=").append(CallbackBridge.mouseX).append("\n");
builder.append("YPos=").append(CallbackBridge.mouseY).append("\n\n");
builder.append("MovingX=").append(getMoving(e.getX(), true)).append("\n");
builder.append("MovingY=").append(getMoving(e.getY(), false)).append("\n");
} catch (Throwable th) {
debugErrored = true;
builder.append("Error getting debug. The debug will be stopped!\n").append(Log.getStackTraceString(th));
} finally {
mPointerDebugTextView.setText(builder.toString());
builder.setLength(0);
}
}
mPointerDebugTextView.setText(CallbackBridge.DEBUG_STRING.toString());
CallbackBridge.DEBUG_STRING.setLength(0);
switch (e.getActionMasked()) {
case MotionEvent.ACTION_MOVE:
CallbackBridge.sendCursorPos(CallbackBridge.mouseX, CallbackBridge.mouseY);
return true;
case MotionEvent.ACTION_BUTTON_PRESS:
return sendMouseButtonUnconverted(e.getActionButton(), true);
case MotionEvent.ACTION_BUTTON_RELEASE:
return sendMouseButtonUnconverted(e.getActionButton(), false);
case MotionEvent.ACTION_SCROLL:
CallbackBridge.sendScroll(e.getAxisValue(MotionEvent.AXIS_HSCROLL), e.getAxisValue(MotionEvent.AXIS_VSCROLL));
return true;
default:
return false;
}
}
/** The event for keyboard/ gamepad button inputs */
@Override
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
//Toast.makeText(this, event.toString(),Toast.LENGTH_SHORT).show();
//Toast.makeText(this, event.getDevice().toString(), Toast.LENGTH_SHORT).show();
//Filtering useless events by order of probability
if((event.getFlags() & KeyEvent.FLAG_FALLBACK) == KeyEvent.FLAG_FALLBACK) return true;
int eventKeycode = event.getKeyCode();
if(eventKeycode == KeyEvent.KEYCODE_UNKNOWN) return true;
if(eventKeycode == KeyEvent.KEYCODE_VOLUME_DOWN) return false;
if(eventKeycode == KeyEvent.KEYCODE_VOLUME_UP) return false;
if(event.getRepeatCount() != 0) return true;
if(event.getAction() == KeyEvent.ACTION_MULTIPLE) return true;
//Toast.makeText(this, "FIRST VERIF PASSED", Toast.LENGTH_SHORT).show();
//Sometimes, key events comes from SOME keys of the software keyboard
//Even weirder, is is unknown why a key or another is selected to trigger a keyEvent
if((event.getFlags() & KeyEvent.FLAG_SOFT_KEYBOARD) == KeyEvent.FLAG_SOFT_KEYBOARD){
if(eventKeycode == KeyEvent.KEYCODE_ENTER) return true; //We already listen to it.
touchCharInput.dispatchKeyEvent(event);
return true;
}
//Toast.makeText(this, "SECOND VERIF PASSED", Toast.LENGTH_SHORT).show();
//Sometimes, key events may come from the mouse
if(event.getDevice() != null
&& ( (event.getSource() & InputDevice.SOURCE_MOUSE_RELATIVE) == InputDevice.SOURCE_MOUSE_RELATIVE
|| (event.getSource() & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) ){
//Toast.makeText(this, "THE EVENT COMES FROM A MOUSE", Toast.LENGTH_SHORT).show();
if(eventKeycode == KeyEvent.KEYCODE_BACK){
sendMouseButton(LwjglGlfwKeycode.GLFW_MOUSE_BUTTON_RIGHT, event.getAction() == KeyEvent.ACTION_DOWN);
return true;
}
}
//System.out.println(event);
if(Gamepad.isGamepadEvent(event)){
if(mGamepad == null){
mGamepad = new Gamepad(this, event.getDevice());
}
mGamepad.update(event);
return true;
}
int index = EfficientAndroidLWJGLKeycode.getIndexByKey(eventKeycode);
if(index >= 0) {
//Toast.makeText(this,"THIS IS A KEYBOARD EVENT !", Toast.LENGTH_SHORT).show();
EfficientAndroidLWJGLKeycode.execKey(event, index);
return true;
}
return false;
}
/** Get the mouse direction as a string */
private String getMoving(float pos, boolean xOrY) {
if (pos == 0) return "STOPPED";
if (pos > 0) return xOrY ? "RIGHT" : "DOWN";
return xOrY ? "LEFT" : "UP";
}
/** Convert the mouse button, then send it
* @return Whether the event was processed
*/
public static boolean sendMouseButtonUnconverted(int button, boolean status) {
int glfwButton = -256;
switch (button) {
case MotionEvent.BUTTON_PRIMARY:
glfwButton = LwjglGlfwKeycode.GLFW_MOUSE_BUTTON_LEFT;
break;
case MotionEvent.BUTTON_TERTIARY:
glfwButton = LwjglGlfwKeycode.GLFW_MOUSE_BUTTON_MIDDLE;
break;
case MotionEvent.BUTTON_SECONDARY:
glfwButton = LwjglGlfwKeycode.GLFW_MOUSE_BUTTON_RIGHT;
break;
}
if(glfwButton == -256) return false;
sendMouseButton(glfwButton, status);
return true;
}
/** @return the hotbar key, given the position. -1 if no key are pressed */
public int handleGuiBar(int x, int y) {
if (!CallbackBridge.isGrabbing()) return -1;
int barHeight = mcscale(20);
int barY = CallbackBridge.physicalHeight - barHeight;
if(y < barY) return -1;
int barWidth = mcscale(180);
int barX = (CallbackBridge.physicalWidth / 2) - (barWidth / 2);
if(x < barX || x >= barX + barWidth) return -1;
return HOTBAR_KEYS[(int) net.kdt.pojavlaunch.utils.MathUtils.map(x, barX, barX + barWidth, 0, 9)];
}
/** Return the size, given the UI scale size */
private int mcscale(int input) {
return (int)((mGuiScale * input)/ mScaleFactor);
}
/** Toggle the pointerDebugText visibility state */
public void togglepointerDebugging() {
mPointerDebugTextView.setVisibility(mPointerDebugTextView.getVisibility() == View.GONE ? View.VISIBLE : View.GONE);
}
/** Called when the size need to be set at any point during the surface lifecycle **/
public void refreshSize(){
windowWidth = Tools.getDisplayFriendlyRes(Tools.currentDisplayMetrics.widthPixels, mScaleFactor);
windowHeight = Tools.getDisplayFriendlyRes(Tools.currentDisplayMetrics.heightPixels, mScaleFactor);
if(LauncherPreferences.PREF_USE_ALTERNATE_SURFACE){
((SurfaceView)mSurface).getHolder().setFixedSize(windowWidth, windowHeight);
}else{
((TextureView)mSurface).getSurfaceTexture().setDefaultBufferSize(windowWidth, windowHeight);
}
CallbackBridge.sendUpdateWindowSize(windowWidth, windowHeight);
//getMcScale();
//Toast.makeText(getContext(), "width: " + width, Toast.LENGTH_SHORT).show();
//Toast.makeText(getContext(), "height: " + height, Toast.LENGTH_SHORT).show();
}
private void realStart(Surface surface){
// Initial size set
refreshSize();
//Load Minecraft options:
MCOptionUtils.load();
MCOptionUtils.set("fullscreen", "off");
MCOptionUtils.set("overrideWidth", String.valueOf(windowWidth));
MCOptionUtils.set("overrideHeight", String.valueOf(windowHeight));
MCOptionUtils.save();
getMcScale();
JREUtils.setupBridgeWindow(surface);
new Thread(() -> {
try {
Thread.sleep(200);
if(mSurfaceReadyListener != null){
mSurfaceReadyListener.isReady();
}
} catch (Throwable e) {
Tools.showError(getContext(), e, true);
}
}, "JVM Main thread").start();
}
/** A small interface called when the listener is ready for the first time */
public interface SurfaceReadyListener {
void isReady();
}
public void setSurfaceReadyListener(SurfaceReadyListener listener){
mSurfaceReadyListener = listener;
}
}

View file

@ -1,20 +0,0 @@
package net.kdt.pojavlaunch;
import android.content.*;
import android.util.*;
import android.view.*;
public class MinecraftGLView extends TextureView
{
public MinecraftGLView(Context context) {
this(context, null);
}
public MinecraftGLView(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
//Fixes freeform and dex mode having transparent glass,
//since it forces android to used the background color of the view/layout behind it.
setOpaque(false);
setFocusable(true);
}
}

View file

@ -14,40 +14,37 @@ import java.util.*;
import net.kdt.pojavlaunch.utils.*;
public class PojavApplication extends Application
{
public class PojavApplication extends Application {
public static String CRASH_REPORT_TAG = "PojavCrashReport";
@Override
public void onCreate() {
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler(){
@Override
public void uncaughtException(Thread thread, Throwable th) {
boolean storagePermAllowed = Build.VERSION.SDK_INT < 23 || ActivityCompat.checkSelfPermission(PojavApplication.this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
File crashFile = new File(storagePermAllowed ? Tools.DIR_GAME_HOME : Tools.DIR_DATA, "latestcrash.txt");
try {
// Write to file, since some devices may not able to show error
crashFile.getParentFile().mkdirs();
crashFile.createNewFile();
PrintStream crashStream = new PrintStream(crashFile);
crashStream.append("PojavLauncher crash report\n");
crashStream.append(" - Time: " + DateFormat.getDateTimeInstance().format(new Date()) + "\n");
crashStream.append(" - Device: " + Build.PRODUCT + " " + Build.MODEL + "\n");
crashStream.append(" - Android version: " + Build.VERSION.RELEASE + "\n");
crashStream.append(" - Crash stack trace:\n");
crashStream.append(" - Launcher version: " + BuildConfig.VERSION_NAME + "\n");
crashStream.append(Log.getStackTraceString(th));
crashStream.close();
} catch (Throwable th2) {
Log.e(CRASH_REPORT_TAG, " - Exception attempt saving crash stack trace:", th2);
Log.e(CRASH_REPORT_TAG, " - The crash stack trace was:", th);
}
FatalErrorActivity.showError(PojavApplication.this, crashFile.getAbsolutePath(), storagePermAllowed, th);
// android.os.Process.killProcess(android.os.Process.myPid());
BaseMainActivity.fullyExit();
Thread.setDefaultUncaughtExceptionHandler((thread, th) -> {
boolean storagePermAllowed = Build.VERSION.SDK_INT < 23 || Build.VERSION.SDK_INT >= 29 ||
ActivityCompat.checkSelfPermission(PojavApplication.this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
File crashFile = new File(storagePermAllowed ? Tools.DIR_GAME_HOME : Tools.DIR_DATA, "latestcrash.txt");
try {
// Write to file, since some devices may not able to show error
crashFile.getParentFile().mkdirs();
crashFile.createNewFile();
PrintStream crashStream = new PrintStream(crashFile);
crashStream.append("PojavLauncher crash report\n");
crashStream.append(" - Time: " + DateFormat.getDateTimeInstance().format(new Date()) + "\n");
crashStream.append(" - Device: " + Build.PRODUCT + " " + Build.MODEL + "\n");
crashStream.append(" - Android version: " + Build.VERSION.RELEASE + "\n");
crashStream.append(" - Crash stack trace:\n");
crashStream.append(" - Launcher version: " + BuildConfig.VERSION_NAME + "\n");
crashStream.append(Log.getStackTraceString(th));
crashStream.close();
} catch (Throwable throwable) {
Log.e(CRASH_REPORT_TAG, " - Exception attempt saving crash stack trace:", throwable);
Log.e(CRASH_REPORT_TAG, " - The crash stack trace was:", th);
}
FatalErrorActivity.showError(PojavApplication.this, crashFile.getAbsolutePath(), storagePermAllowed, th);
// android.os.Process.killProcess(android.os.Process.myPid());
BaseMainActivity.fullyExit();
});
try {
@ -70,10 +67,9 @@ public class PojavApplication extends Application
.concat("/x86");
}
} catch (Throwable th) {
} catch (Throwable throwable) {
Intent ferrorIntent = new Intent(this, FatalErrorActivity.class);
ferrorIntent.putExtra("throwable", th);
ferrorIntent.putExtra("throwable", throwable);
startActivity(ferrorIntent);
}
}

View file

@ -1,7 +1,12 @@
package net.kdt.pojavlaunch;
import static android.os.Build.VERSION_CODES.P;
import static net.kdt.pojavlaunch.Tools.ignoreNotch;
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_HIDE_SIDEBAR;
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_NOTCH_SIZE;
import android.animation.ValueAnimator;
import android.content.SharedPreferences;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Color;
import android.graphics.Typeface;
@ -9,126 +14,151 @@ import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.support.design.widget.VerticalTabLayout.ViewPagerAdapter;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.ImageView;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.constraintlayout.widget.Guideline;
import androidx.viewpager.widget.ViewPager;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.viewpager2.adapter.FragmentStateAdapter;
import androidx.viewpager2.widget.ViewPager2;
import net.kdt.pojavlaunch.extra.ExtraCore;
import net.kdt.pojavlaunch.extra.ExtraListener;
import net.kdt.pojavlaunch.fragments.ConsoleFragment;
import net.kdt.pojavlaunch.fragments.CrashFragment;
import net.kdt.pojavlaunch.fragments.LauncherFragment;
import net.kdt.pojavlaunch.prefs.LauncherPreferenceFragment;
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
import net.kdt.pojavlaunch.prefs.screens.LauncherPreferenceFragment;
import net.kdt.pojavlaunch.profiles.ProfileAdapter;
import net.kdt.pojavlaunch.extra.ExtraConstants;
import net.kdt.pojavlaunch.profiles.ProfileEditor;
import net.kdt.pojavlaunch.profiles.ProfileIconCache;
import net.kdt.pojavlaunch.value.MinecraftAccount;
import net.kdt.pojavlaunch.value.launcherprofiles.LauncherProfiles;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import static android.os.Build.VERSION_CODES.P;
import static net.kdt.pojavlaunch.Tools.ignoreNotch;
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_HIDE_SIDEBAR;
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_IGNORE_NOTCH;
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_NOTCH_SIZE;
public class PojavLauncherActivity extends BaseLauncherActivity
{
private ViewPager viewPager;
// An equivalent ViewPager2 adapter class
private static class ScreenSlidePagerAdapter extends FragmentStateAdapter {
public ScreenSlidePagerAdapter(FragmentActivity fa) {
super(fa);
}
private TextView tvUsernameView, tvConnectStatus;
@Override
public Fragment createFragment(int position) {
if (position == 0) return new LauncherFragment();
if (position == 1) return new ConsoleFragment();
if (position == 2) return new CrashFragment();
if (position == 3) return new LauncherPreferenceFragment();
return null;
}
@Override
public int getItemCount() {
return 4;
}
}
private TextView tvConnectStatus;
private Spinner accountSelector;
private ViewPagerAdapter viewPageAdapter;
private ViewPager2 viewPager;
private final Button[] Tabs = new Button[4];
private View selected;
private View selectedTab;
private ImageView accountFaceImageView;
private Button logoutBtn; // MineButtons
private ExtraListener backPreferenceListener;
public PojavLauncherActivity() {
}
@Override
protected void onDestroy() {
ExtraCore.removeExtraListenerFromValue(ExtraConstants.BACK_PREFERENCE, backPreferenceListener);
super.onDestroy();
ProfileIconCache.clearIconCache();
Log.i("LauncherActivity","Destroyed!");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.launcher_main_v4);
if (BuildConfig.DEBUG) {
Toast.makeText(this, "Launcher process id: " + android.os.Process.myPid(), Toast.LENGTH_LONG).show();
}
setContentView(R.layout.activity_pojav_launcher);
//Boilerplate linking/initialisation
viewPager = findViewById(R.id.launchermainTabPager);
selected = findViewById(R.id.viewTabSelected);
mConsoleView = new ConsoleFragment();
mCrashView = new CrashFragment();
viewPageAdapter = new ViewPagerAdapter(getSupportFragmentManager());
viewPageAdapter.addFragment(new LauncherFragment(), 0, getString(R.string.mcl_tab_news));
viewPageAdapter.addFragment(mConsoleView, 0, getString(R.string.mcl_tab_console));
viewPageAdapter.addFragment(mCrashView, 0, getString(R.string.mcl_tab_crash));
viewPageAdapter.addFragment(new LauncherPreferenceFragment(), 0, getString(R.string.mcl_option_settings));
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageSelected(int position) {
setTabActive(position);
}
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
viewPager.setAdapter(viewPageAdapter);
tvConnectStatus = (TextView) findViewById(R.id.launchermain_text_accountstatus);
tvUsernameView = (TextView) findViewById(R.id.launchermain_text_welcome);
mTextVersion = (TextView) findViewById(R.id.launcherMainVersionView);
//The following line is used to make this TextView horizontally scroll if the version name is larger than the view
mTextVersion.setSelected(true);
selectedTab = findViewById(R.id.viewTabSelected);
tvConnectStatus = findViewById(R.id.launchermain_text_accountstatus);
accountFaceImageView = findViewById(R.id.launchermain_account_image);
accountSelector = findViewById(R.id.launchermain_spinner_account);
mVersionSelector = findViewById(R.id.launchermain_spinner_version);
mLaunchProgress = findViewById(R.id.progressDownloadBar);
mLaunchTextStatus = findViewById(R.id.progressDownloadText);
logoutBtn = findViewById(R.id.installJarButton);
mPlayButton = findViewById(R.id.launchermainPlayButton);
Tabs[0] = findViewById(R.id.btnTab1);
Tabs[1] = findViewById(R.id.btnTab2);
Tabs[2] = findViewById(R.id.btnTab3);
Tabs[3] = findViewById(R.id.btnTab4);
pickAccount();
if (BuildConfig.DEBUG) {
Toast.makeText(this, "Launcher process id: " + android.os.Process.myPid(), Toast.LENGTH_LONG).show();
}
// Setup the viewPager to slide across fragments
viewPager.setAdapter(new ScreenSlidePagerAdapter(this));
viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageSelected(int position) {
setTabActive(position);
}
});
initTabs(0);
//Setup listener to the backPreference system
backPreferenceListener = (key, value) -> {
if(value.equals("true")){
onBackPressed();
ExtraCore.setValue(key, "false");
}
return false;
};
ExtraCore.addExtraListener(ExtraConstants.BACK_PREFERENCE, backPreferenceListener);
final List<String> accountList = new ArrayList<String>();
final MinecraftAccount tempProfile = PojavProfile.getTempProfileContent(this);
// Try to load the temporary account
final List<String> accountList = new ArrayList<>();
final MinecraftAccount tempProfile = PojavProfile.getTempProfileContent();
if (tempProfile != null) {
accountList.add(tempProfile.username);
}
for (String s : new File(Tools.DIR_ACCOUNT_NEW).list()) {
accountList.add(s.substring(0, s.length() - 5));
}
// Setup account spinner
pickAccount();
ArrayAdapter<String> adapterAcc = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, accountList);
adapterAcc.setDropDownViewResource(android.R.layout.simple_list_item_single_choice);
accountSelector = (Spinner) findViewById(R.id.launchermain_spinner_account);
accountSelector.setAdapter(adapterAcc);
if (tempProfile != null) {
accountSelector.setSelection(0);
} else {
@ -157,13 +187,77 @@ public class PojavLauncherActivity extends BaseLauncherActivity
// TODO: Implement this method
}
});
List<String> versions = new ArrayList<String>();
// Setup the minecraft version list
setupBasicList(this);
//mAvailableVersions;
ProfileAdapter profileAdapter = new ProfileAdapter(this, true);
ProfileEditor profileEditor = new ProfileEditor(this,(name, isNew, deleting)->{
LauncherProfiles.update();
if(isNew) {
mVersionSelector.setSelection(profileAdapter.resolveProfileIndex(name));
}
if(deleting) {
mVersionSelector.setSelection(0);
}
profileAdapter.notifyDataSetChanged();
});
mVersionSelector.setOnLongClickListener((v)->profileEditor.show(LauncherPreferences.DEFAULT_PREF.getString(LauncherPreferences.PREF_KEY_CURRENT_PROFILE,"")));
mVersionSelector.setAdapter(profileAdapter);
mVersionSelector.setSelection(profileAdapter.resolveProfileIndex(LauncherPreferences.DEFAULT_PREF.getString(LauncherPreferences.PREF_KEY_CURRENT_PROFILE,"")));
mVersionSelector.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener(){
@Override
public void onItemSelected(AdapterView<?> p1, View p2, int p3, long p4)
{
String profileName = p1.getItemAtPosition(p3).toString();
if(profileName.equals(ProfileAdapter.CREATE_PROFILE_MAGIC)) {
profileEditor.show(profileName);
mVersionSelector.setSelection(0);
return;
}
LauncherPreferences.DEFAULT_PREF.edit()
.putString(
LauncherPreferences.PREF_KEY_CURRENT_PROFILE,
p1.getItemAtPosition(p3).toString())
.commit();
}
@Override
public void onNothingSelected(AdapterView<?> p1)
{
// TODO: Implement this method
}
});
//
statusIsLaunching(false);
//Add the preference changed listener
LauncherPreferences.DEFAULT_PREF.registerOnSharedPreferenceChangeListener((sharedPreferences, key) -> {
if(key.equals("hideSidebar")){
changeLookAndFeel(sharedPreferences.getBoolean("hideSidebar",false));
return;
}
if(key.equals("ignoreNotch")){
ignoreNotch(sharedPreferences.getBoolean("ignoreNotch", true), PojavLauncherActivity.this);
return;
}
});
changeLookAndFeel(PREF_HIDE_SIDEBAR);
}
private void selectTabPage(int pageIndex){
viewPager.setCurrentItem(pageIndex);
setTabActive(pageIndex);
}
public static String[] basicVersionList;
public static void setupBasicList(Context ctx) {
List<String> versions = new ArrayList<>();
final File fVers = new File(Tools.DIR_HOME_VERSION);
try {
if (fVers.listFiles().length < 1) {
throw new Exception(getString(R.string.error_no_version));
throw new Exception(ctx.getString(R.string.error_no_version));
}
for (File fVer : fVers.listFiles()) {
@ -171,59 +265,21 @@ public class PojavLauncherActivity extends BaseLauncherActivity
versions.add(fVer.getName());
}
} catch (Exception e) {
versions.add(getString(R.string.global_error) + ":");
versions.add(ctx.getString(R.string.global_error) + ":");
versions.add(e.getMessage());
} finally {
mAvailableVersions = versions.toArray(new String[0]);
basicVersionList = versions.toArray(new String[0]);
ExtraCore.setValue(ExtraConstants.VERSION_LIST,versions);
}
//mAvailableVersions;
ArrayAdapter<String> adapterVer = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, mAvailableVersions);
adapterVer.setDropDownViewResource(android.R.layout.simple_list_item_single_choice);
mVersionSelector = (Spinner) findViewById(R.id.launchermain_spinner_version);
mVersionSelector.setAdapter(adapterVer);
mLaunchProgress = (ProgressBar) findViewById(R.id.progressDownloadBar);
mLaunchTextStatus = (TextView) findViewById(R.id.progressDownloadText);
logoutBtn = (Button) findViewById(R.id.switchUserBtn);
mPlayButton = (Button) findViewById(R.id.launchermainPlayButton);
statusIsLaunching(false);
initTabs(0);
LauncherPreferences.DEFAULT_PREF.registerOnSharedPreferenceChangeListener(new SharedPreferences.OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if(key.equals("hideSidebar")){
changeLookAndFeel(sharedPreferences.getBoolean("hideSidebar",false));
return;
}
if(key.equals("ignoreNotch")){
ignoreNotch(sharedPreferences.getBoolean("ignoreNotch", true), PojavLauncherActivity.this);
return;
}
}
});
changeLookAndFeel(PREF_HIDE_SIDEBAR);
ignoreNotch(PREF_IGNORE_NOTCH, PojavLauncherActivity.this);
}
private void selectTabPage(int pageIndex){
viewPager.setCurrentItem(pageIndex);
setTabActive(pageIndex);
}
private void pickAccount() {
try {
mProfile = PojavProfile.getCurrentProfileContent(this);
accountFaceImageView.setImageBitmap(mProfile.getSkinFace());
tvUsernameView.setText(getString(R.string.main_welcome, mProfile.username));
tvConnectStatus.setText(mProfile.accessToken.equals("0") ? R.string.mcl_account_offline : R.string.mcl_account_connected);
//TODO FULL BACKGROUND LOGIN
tvConnectStatus.setText(mProfile.accessToken.equals("0") ? R.string.mcl_account_local : R.string.mcl_account_connected);
} catch(Exception e) {
mProfile = new MinecraftAccount();
Tools.showError(this, e, true);
@ -259,21 +315,18 @@ public class PojavLauncherActivity extends BaseLauncherActivity
Tabs[index].setTextColor(Color.WHITE);
//Animating the white bar on the left
ValueAnimator animation = ValueAnimator.ofFloat(selected.getY(), Tabs[index].getY()+(Tabs[index].getHeight()-selected.getHeight())/2f);
ValueAnimator animation = ValueAnimator.ofFloat(selectedTab.getY(), Tabs[index].getY()+(Tabs[index].getHeight()- selectedTab.getHeight())/2f);
animation.setDuration(250);
animation.addUpdateListener(animation1 -> selected.setY((float) animation1.getAnimatedValue()));
animation.addUpdateListener(animation1 -> selectedTab.setY((float) animation1.getAnimatedValue()));
animation.start();
}
protected void initTabs(int activeTab){
final Handler handler = new Handler(Looper.getMainLooper());
handler.postDelayed(new Runnable() {
@Override
public void run() {
//Do something after 100ms
selectTabPage(activeTab);
}
}, 500);
handler.post(() -> {
//Do something after 100ms
selectTabPage(activeTab);
});
}
private void changeLookAndFeel(boolean useOldLook){
@ -286,8 +339,9 @@ public class PojavLauncherActivity extends BaseLauncherActivity
params.guidePercent = 0; // 0%, range: 0 <-> 1
guideLine.setLayoutParams(params);
//Remove the selected Tab
selected.setVisibility(View.GONE);
//Remove the selected Tab and the head image
selectedTab.setVisibility(View.GONE);
accountFaceImageView.setVisibility(View.GONE);
//Enlarge the button, but just a bit.
params = (ConstraintLayout.LayoutParams) mPlayButton.getLayoutParams();
@ -299,7 +353,8 @@ public class PojavLauncherActivity extends BaseLauncherActivity
guideLine.setLayoutParams(params);
//Show the selected Tab
selected.setVisibility(View.VISIBLE);
selectedTab.setVisibility(View.VISIBLE);
accountFaceImageView.setVisibility(View.VISIBLE);
//Set the default button size
params = (ConstraintLayout.LayoutParams) mPlayButton.getLayoutParams();
@ -323,5 +378,22 @@ public class PojavLauncherActivity extends BaseLauncherActivity
}
}
/**
* Custom back stack system. Use the classic backstack when the focus is on the setting screen,
* finish the activity and remove the back_preference listener otherwise
*/
@Override
public void onBackPressed() {
int count = getSupportFragmentManager().getBackStackEntryCount();
if(count > 0 && viewPager.getCurrentItem() == 3){
getSupportFragmentManager().popBackStack();
}else{
super.onBackPressed();
//additional code
ExtraCore.removeExtraListenerFromValue(ExtraConstants.BACK_PREFERENCE, backPreferenceListener);
finish();
}
}
}

View file

@ -1,6 +1,7 @@
package net.kdt.pojavlaunch;
import static net.kdt.pojavlaunch.Architecture.archAsString;
import static net.kdt.pojavlaunch.Tools.getFileName;
import android.Manifest;
import android.app.Activity;
@ -9,10 +10,7 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
import android.os.Build;
@ -21,7 +19,6 @@ import android.text.Html;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.style.StyleSpan;
import android.util.Base64;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@ -50,33 +47,41 @@ import net.kdt.pojavlaunch.authenticator.mojang.InvalidateTokenTask;
import net.kdt.pojavlaunch.authenticator.mojang.LoginListener;
import net.kdt.pojavlaunch.authenticator.mojang.LoginTask;
import net.kdt.pojavlaunch.authenticator.mojang.RefreshListener;
import net.kdt.pojavlaunch.colorselector.ColorSelector;
import net.kdt.pojavlaunch.customcontrols.CustomControls;
import net.kdt.pojavlaunch.multirt.MultiRTConfigDialog;
import net.kdt.pojavlaunch.multirt.MultiRTUtils;
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
import net.kdt.pojavlaunch.utils.LocaleUtils;
import net.kdt.pojavlaunch.value.MinecraftAccount;
import net.kdt.pojavlaunch.value.PerVersionConfig;
import net.kdt.pojavlaunch.value.launcherprofiles.LauncherProfiles;
import net.kdt.pojavlaunch.value.launcherprofiles.MinecraftProfile;
import org.apache.commons.compress.utils.IOUtils;
import org.apache.commons.io.FileUtils;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.Locale;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import java.util.jar.JarOutputStream;
public class PojavLoginActivity extends BaseActivity
// MineActivity
{
public class PojavLoginActivity extends BaseActivity {
private final Object mLockStoragePerm = new Object();
private final Object mLockSelectJRE = new Object();
private EditText edit2, edit3;
private final int REQUEST_STORAGE_REQUEST_CODE = 1;
private CheckBox sRemember, sOffline;
private CheckBox sRemember, sLocal;
private TextView startupTextView;
private SharedPreferences firstLaunchPrefs;
private MinecraftAccount mProfile = null;
@ -172,7 +177,7 @@ public class PojavLoginActivity extends BaseActivity
}
}
private void uiInit() {
setContentView(R.layout.launcher_login_v3);
setContentView(R.layout.activity_pojav_login);
Spinner spinnerChgLang = findViewById(R.id.login_spinner_language);
@ -241,16 +246,14 @@ public class PojavLoginActivity extends BaseActivity
public void onNothingSelected(AdapterView<?> adapter) {}
});
edit2 = (EditText) findViewById(R.id.login_edit_email);
edit3 = (EditText) findViewById(R.id.login_edit_password);
edit2 = findViewById(R.id.login_edit_email);
edit3 = findViewById(R.id.login_edit_password);
sRemember = findViewById(R.id.login_switch_remember);
sOffline = findViewById(R.id.login_switch_offline);
sOffline.setOnCheckedChangeListener((p1, p2) -> {
sLocal = findViewById(R.id.login_switch_local);
sLocal.setOnCheckedChangeListener((p1, p2) -> {
// May delete later
edit3.setEnabled(!p2);
});
isSkipInit = true;
}
@ -264,8 +267,41 @@ public class PojavLoginActivity extends BaseActivity
PojavProfile.setCurrentProfile(this, null);
}
private void unpackComponent(AssetManager am, String component) throws IOException {
private void repackLWJGL3(boolean ignoreExistence) throws IOException {
File outFile = new File(Tools.DIR_GAME_HOME + "/lwjgl3/lwjgl-fat.jar");
File outTmpFile = new File(Tools.DIR_GAME_HOME + "/lwjgl3/lwjgl-fat.jar.tmp");
File[] innerFiles = outTmpFile.getParentFile().listFiles();
if (outFile.exists() && !ignoreExistence) {
return;
}
outFile.delete();
outTmpFile.delete();
java.util.jar.Manifest manifest = new java.util.jar.Manifest();
manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
JarOutputStream outJar = new JarOutputStream(new FileOutputStream(outTmpFile), manifest);
outJar.setLevel(9);
for (File file : innerFiles) {
if (!file.getName().endsWith(".jar")) continue;
JarInputStream inJar = new JarInputStream(new FileInputStream(file));
JarEntry entry;
while ((entry = inJar.getNextJarEntry()) != null) {
// avoid some duplicated entries
if (entry.getSize() == 0 || entry.getName().equals("META-INF/INDEX.LIST") || entry.getName().startsWith("META-INF/versions")) continue;
outJar.putNextEntry(new JarEntry(entry.getName()));
IOUtils.copy(inJar, outJar);
}
inJar.close();
file.delete();
}
outJar.close();
outTmpFile.renameTo(outFile);
}
private boolean unpackComponent(AssetManager am, String component) throws IOException {
File versionFile = new File(Tools.DIR_GAME_HOME + "/" + component + "/version");
InputStream is = am.open("components/" + component + "/version");
if(!versionFile.exists()) {
@ -295,8 +331,10 @@ public class PojavLoginActivity extends BaseActivity
}
} else {
Log.i("UnpackPrep", component + ": Pack is up-to-date with the launcher, continuing...");
return false;
}
}
return true;
}
public static void disableSplash(String dir) {
mkdirs(dir + "/config");
@ -339,16 +377,22 @@ public class PojavLoginActivity extends BaseActivity
// TODO: Remove after implement.
Tools.copyAssetFile(this, "launcher_profiles.json", Tools.DIR_GAME_NEW, false);
Tools.copyAssetFile(this,"resolv.conf",Tools.DIR_DATA, true);
Tools.copyAssetFile(this,"arc_dns_injector.jar",Tools.DIR_DATA, true);
AssetManager am = this.getAssets();
unpackComponent(am, "caciocavallo");
unpackComponent(am, "lwjgl3");
// Since the Java module system doesn't allow multiple JARs to declare the same module,
// we repack them to a single file here
repackLWJGL3(unpackComponent(am, "lwjgl3"));
if(!installRuntimeAutomatically(am,MultiRTUtils.getRuntimes().size() > 0)) {
MultiRTConfigDialog.openRuntimeSelector(this, MultiRTConfigDialog.MULTIRT_PICK_RUNTIME_STARTUP);
synchronized (mLockSelectJRE) {
mLockSelectJRE.wait();
}
}
migrateToProfiles();
if(Build.VERSION.SDK_INT > 28) runOnUiThread(this::showStorageDialog);
LauncherPreferences.loadPreferences(getApplicationContext());
}
@ -379,7 +423,7 @@ public class PojavLoginActivity extends BaseActivity
final Uri uri = data.getData();
Thread t = new Thread(() -> {
try {
MultiRTUtils.installRuntimeNamed(getContentResolver().openInputStream(uri), BaseLauncherActivity.getFileName(this, uri),
MultiRTUtils.installRuntimeNamed(getContentResolver().openInputStream(uri), getFileName(this, uri),
(resid, stuff) -> PojavLoginActivity.this.runOnUiThread(
() -> {
if (startupTextView != null)
@ -401,6 +445,46 @@ public class PojavLoginActivity extends BaseActivity
}
}
}
private void migrateToProfiles() {
try {
if(!PerVersionConfig.exists()) return;
LauncherProfiles.update();
PerVersionConfig.update();
if(PerVersionConfig.erase()) {
for (String version : PerVersionConfig.configMap.keySet()) {
PerVersionConfig.VersionConfig config = PerVersionConfig.configMap.get(version);
if(config == null) continue; // Skip the version
// Replaced by gl4es_extra
if(config.renderer != null) {
if (config.renderer.contains("zink")) config.renderer = "opengles3_virgl";
if (!config.renderer.contains("virgl")) config.renderer = null;
}
if(config.renderer == null && config.gamePath == null &&
config.jvmArgs == null && config.selectedRuntime == null){
continue; // Empty pvc, skip it.
}
MinecraftProfile profile = new MinecraftProfile();
profile.lastVersionId = version;
profile.name = getString(R.string.migrated_profile_str, version);
profile.pojavRendererName = config.renderer;
profile.gameDir = config.gamePath;
profile.javaDir = Tools.LAUNCHERPROFILES_RTPREFIX + config.selectedRuntime;
profile.javaArgs = config.jvmArgs;
LauncherProfiles.mainProfileJson.profiles.put("pvc-migrated-" + version, profile);
}
LauncherProfiles.update();
}else{
Log.e("ProfileMigrator"," Unable to remove Per Version Config files.");
}
}catch (IOException e) {
Log.e("ProfileMigrator","Failed to migrate!",e);
}
}
private boolean installRuntimeAutomatically(AssetManager am, boolean otherRuntimesAvailable) {
/* Check if JRE is included */
String rt_version = null;
@ -410,11 +494,11 @@ public class PojavLoginActivity extends BaseActivity
} catch (IOException e) {
Log.e("JREAuto", "JRE was not included on this APK.", e);
}
if(current_rt_version == null && otherRuntimesAvailable) return true; //Assume user maintains his own runtime
if(rt_version == null) return false;
if(current_rt_version == null && MultiRTUtils.getExactJreName(8) != null) return true; //Assume user maintains his own runtime
if(rt_version == null) return otherRuntimesAvailable; // On noruntime builds, skip if there is at least 1 runtime installed (no matter if it is 8 or not)
if(!rt_version.equals(current_rt_version)) { //If we already have an integrated one installed, check if it's up-to-date
try {
MultiRTUtils.installRuntimeNamedBinpack(am.open("components/jre/universal.tar.xz"), am.open("components/jre/bin-" + archAsString(Tools.DEVICE_ARCHITECTURE) + ".tar.xz"), "Internal", rt_version,
MultiRTUtils.installRuntimeNamedBinpack(getApplicationInfo().nativeLibraryDir, am.open("components/jre/universal.tar.xz"), am.open("components/jre/bin-" + archAsString(Tools.DEVICE_ARCHITECTURE) + ".tar.xz"), "Internal", rt_version,
(resid, vararg) -> runOnUiThread(()->{if(startupTextView!=null)startupTextView.setText(getString(resid,vararg));}));
MultiRTUtils.postPrepare(PojavLoginActivity.this,"Internal");
return true;
@ -494,27 +578,21 @@ public class PojavLoginActivity extends BaseActivity
final Dialog accountDialog = new Dialog(PojavLoginActivity.this);
accountDialog.setContentView(R.layout.simple_account_list_holder);
accountDialog.setContentView(R.layout.dialog_select_account);
LinearLayout accountListLayout = accountDialog.findViewById(R.id.accountListLayout);
LayoutInflater inflater = (LayoutInflater) this.getSystemService(LAYOUT_INFLATER_SERVICE);
for (int accountIndex = 0; accountIndex < accountArr.length; accountIndex++) {
String s = accountArr[accountIndex];
View child = inflater.inflate(R.layout.simple_account_list_item, accountListLayout,false);
View child = inflater.inflate(R.layout.item_minecraft_account, accountListLayout,false);
TextView accountName = child.findViewById(R.id.accountitem_text_name);
ImageButton removeButton = child.findViewById(R.id.accountitem_button_remove);
ImageView imageView = child.findViewById(R.id.account_head);
String accNameStr = s.substring(0, s.length() - 5);
String skinFaceBase64 = MinecraftAccount.load(accNameStr).skinFaceBase64;
if (skinFaceBase64 != null) {
byte[] faceIconBytes = Base64.decode(skinFaceBase64, Base64.DEFAULT);
Bitmap bitmap = BitmapFactory.decodeByteArray(faceIconBytes, 0, faceIconBytes.length);
imageView.setImageBitmap(MinecraftAccount.load(accNameStr).getSkinFace());
imageView.setImageDrawable(new BitmapDrawable(getResources(),
bitmap));
}
accountName.setText(accNameStr);
accountListLayout.addView(child);
@ -542,8 +620,6 @@ public class PojavLoginActivity extends BaseActivity
if (acc.isMicrosoft){
new MicrosoftAuthTask(PojavLoginActivity.this, authListener)
.execute("true", acc.msaRefreshToken);
} else if (acc.accessToken.length() >= 5) {
PojavProfile.updateTokens(PojavLoginActivity.this, selectedAccName, authListener);
} else {
accountDialog.dismiss();
PojavProfile.launch(PojavLoginActivity.this, selectedAccName);
@ -584,7 +660,7 @@ public class PojavLoginActivity extends BaseActivity
accountDialog.show();
}
private MinecraftAccount loginOffline() {
private MinecraftAccount loginLocal() {
new File(Tools.DIR_ACCOUNT_OLD).mkdir();
String text = edit2.getText().toString();
@ -594,8 +670,6 @@ public class PojavLoginActivity extends BaseActivity
edit2.setError(getString(R.string.login_error_invalid_username));
} else if (new File(Tools.DIR_ACCOUNT_NEW + "/" + text + ".json").exists()) {
edit2.setError(getString(R.string.login_error_exist_username));
} else if (!edit3.getText().toString().isEmpty()) {
edit3.setError(getString(R.string.login_error_offline_password));
} else {
MinecraftAccount builder = new MinecraftAccount();
builder.isMicrosoft = false;
@ -605,47 +679,45 @@ public class PojavLoginActivity extends BaseActivity
}
return null;
}
public void loginMC(final View v)
{
if (sOffline.isChecked()) {
mProfile = loginOffline();
if (sLocal.isChecked()) {
mProfile = loginLocal();
playProfile(false);
} else {
ProgressBar prb = findViewById(R.id.launcherAccProgress);
new LoginTask().setLoginListener(new LoginListener(){
@Override
public void onBeforeLogin() {
v.setEnabled(false);
prb.setVisibility(View.VISIBLE);
}
@Override
public void onBeforeLogin() {
v.setEnabled(false);
prb.setVisibility(View.VISIBLE);
}
@Override
public void onLoginDone(String[] result) {
if(result[0].equals("ERROR")){
Tools.dialogOnUiThread(PojavLoginActivity.this,
@Override
public void onLoginDone(String[] result) {
if(result[0].equals("ERROR")){
Tools.dialogOnUiThread(PojavLoginActivity.this,
getResources().getString(R.string.global_error), strArrToString(result));
} else{
MinecraftAccount builder = new MinecraftAccount();
builder.accessToken = result[1];
builder.clientToken = result[2];
builder.profileId = result[3];
builder.username = result[4];
builder.selectedVersion = "1.12.2";
builder.updateSkinFace();
mProfile = builder;
}
runOnUiThread(() -> {
v.setEnabled(true);
prb.setVisibility(View.GONE);
playProfile(false);
});
} else{
MinecraftAccount builder = new MinecraftAccount();
builder.accessToken = result[1];
builder.clientToken = result[2];
builder.profileId = result[3];
builder.username = result[4];
builder.updateSkinFace();
mProfile = builder;
}
}).execute(edit2.getText().toString(), edit3.getText().toString());
runOnUiThread(() -> {
v.setEnabled(true);
prb.setVisibility(View.GONE);
playProfile(false);
});
}
}).execute(edit2.getText().toString(), edit3.getText().toString());
}
}

View file

@ -6,8 +6,7 @@ import net.kdt.pojavlaunch.value.*;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
public class PojavMigrator
{
public class PojavMigrator {
public static void migrateAccountData(Context ctx) {
File oldAccDir = new File(Tools.DIR_ACCOUNT_OLD);
if (oldAccDir.exists() && oldAccDir.isDirectory()) {

View file

@ -8,14 +8,14 @@ import android.util.Log;
import com.google.gson.JsonSyntaxException;
import java.io.File;
import java.io.IOException;
import net.kdt.pojavlaunch.authenticator.mojang.RefreshListener;
import net.kdt.pojavlaunch.authenticator.mojang.RefreshTokenTask;
import net.kdt.pojavlaunch.value.MinecraftAccount;
public class PojavProfile
{
private static String PROFILE_PREF = "pojav_profile";
private static String PROFILE_PREF_FILE = "file";
public class PojavProfile {
private static final String PROFILE_PREF = "pojav_profile";
private static final String PROFILE_PREF_FILE = "file";
public static String PROFILE_PREF_TEMP_CONTENT = "tempContent";
public static SharedPreferences getPrefs(Context ctx) {
@ -25,34 +25,34 @@ public class PojavProfile
public static MinecraftAccount getCurrentProfileContent(Context ctx) throws JsonSyntaxException {
MinecraftAccount build = MinecraftAccount.load(getCurrentProfileName(ctx));
if (build == null) {
System.out.println("isTempProfile null? " + (getTempProfileContent(ctx) == null));
return getTempProfileContent(ctx);
System.out.println("isTempProfile null? " + (getTempProfileContent() == null));
return getTempProfileContent();
}
return build;
}
public static MinecraftAccount getTempProfileContent(Context ctx) {
public static MinecraftAccount getTempProfileContent() {
try {
MinecraftAccount acc = MinecraftAccount.parse(Tools.read(Tools.DIR_DATA+"/cache/tempacc.json"));
if (acc.accessToken == null) {
acc.accessToken = "0";
MinecraftAccount account = MinecraftAccount.parse(Tools.read(Tools.DIR_DATA+"/cache/tempacc.json"));
if (account.accessToken == null) {
account.accessToken = "0";
}
if (acc.clientToken == null) {
acc.clientToken = "0";
if (account.clientToken == null) {
account.clientToken = "0";
}
if (acc.profileId == null) {
acc.profileId = "00000000-0000-0000-0000-000000000000";
if (account.profileId == null) {
account.profileId = "00000000-0000-0000-0000-000000000000";
}
if (acc.username == null) {
acc.username = "0";
if (account.username == null) {
account.username = "0";
}
if (acc.selectedVersion == null) {
acc.selectedVersion = "1.7.10";
if (account.selectedVersion == null) {
account.selectedVersion = "1.7.10";
}
if (acc.msaRefreshToken == null) {
acc.msaRefreshToken = "0";
if (account.msaRefreshToken == null) {
account.msaRefreshToken = "0";
}
return acc;
return account;
}catch (IOException e) {
Log.e(MinecraftAccount.class.getName(), "Caught an exception while loading the temporary profile",e);
return null;
@ -104,7 +104,6 @@ public class PojavProfile
Intent intent = new Intent(ctx, PojavLauncherActivity.class); //MCLauncherActivity.class);
ctx.startActivity(intent);
}
public static void updateTokens(final Activity ctx, final String name, RefreshListener listen) throws Exception {
new RefreshTokenTask(ctx, listen).execute(name);
}

View file

@ -3,8 +3,7 @@ package net.kdt.pojavlaunch;
import android.view.*;
import android.view.GestureDetector.*;
public class SingleTapConfirm extends SimpleOnGestureListener
{
public class SingleTapConfirm extends SimpleOnGestureListener {
@Override
public boolean onSingleTapUp(MotionEvent event) {
return true;

View file

@ -0,0 +1,121 @@
package net.kdt.pojavlaunch;
import android.view.MotionEvent;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_POINTER_DOWN;
import static android.view.MotionEvent.ACTION_POINTER_UP;
import static android.view.MotionEvent.ACTION_UP;
/**
* Class aiming at better detecting X-tap events regardless of the POINTERS
* Only uses the least amount of events possible,
* since we aren't guaranteed to have all events in order
*/
public class TapDetector {
public final static int DETECTION_METHOD_DOWN = 0x1;
public final static int DETECTION_METHOD_UP = 0x2;
public final static int DETECTION_METHOD_BOTH = 0x3; //Unused for now
private final static int TAP_MIN_DELTA_MS = 10;
private final static int TAP_MAX_DELTA_MS = 300;
private final static int TAP_SLOP_SQUARE_PX = (int) Math.pow(Tools.dpToPx(100), 2);
private final int mTapNumberToDetect;
private int mCurrentTapNumber = 0;
private final int mDetectionMethod;
private long mLastEventTime = 0;
private float mLastX = 9999;
private float mLastY = 9999;
/**
* @param tapNumberToDetect How many taps are needed before onTouchEvent returns True.
* @param detectionMethod Method used to detect touches. See DETECTION_METHOD constants above.
*/
public TapDetector(int tapNumberToDetect, int detectionMethod){
this.mDetectionMethod = detectionMethod;
//We expect both ACTION_DOWN and ACTION_UP for the DETECTION_METHOD_BOTH
this.mTapNumberToDetect = detectBothTouch() ? 2*tapNumberToDetect : tapNumberToDetect;
}
/**
* A function to call when you have a touch event.
* @param e The MotionEvent to inspect
* @return whether or not a X-tap happened for a pointer
*/
public boolean onTouchEvent(MotionEvent e){
int eventAction = e.getActionMasked();
int pointerIndex = -1;
//Get the event to look forward
if(detectDownTouch()){
if(eventAction == ACTION_DOWN) pointerIndex = 0;
else if(eventAction == ACTION_POINTER_DOWN) pointerIndex = e.getActionIndex();
}
if(detectUpTouch()){
if(eventAction == ACTION_UP) pointerIndex = 0;
else if(eventAction == ACTION_POINTER_UP) pointerIndex = e.getActionIndex();
}
if(pointerIndex == -1) return false; // Useless event
//Store current event info
float eventX = e.getX(pointerIndex);
float eventY = e.getY(pointerIndex);
long eventTime = e.getEventTime();
//Compute deltas
long deltaTime = eventTime - mLastEventTime;
int deltaX = (int) mLastX - (int) eventX;
int deltaY = (int) mLastY - (int) eventY;
//Store current event info to persist on next event
mLastEventTime = eventTime;
mLastX = eventX;
mLastY = eventY;
//Check for high enough speed and precision
if(mCurrentTapNumber > 0){
if ((deltaTime < TAP_MIN_DELTA_MS || deltaTime > TAP_MAX_DELTA_MS) ||
((deltaX*deltaX + deltaY*deltaY) > TAP_SLOP_SQUARE_PX)) {
// We invalidate previous taps, not this one though
mCurrentTapNumber = 0;
}
}
//A worthy tap happened
mCurrentTapNumber += 1;
if(mCurrentTapNumber >= mTapNumberToDetect){
resetTapDetectionState();
return true;
}
//If not enough taps are reached
return false;
}
/**
* Reset the double tap values.
*/
private void resetTapDetectionState(){
mCurrentTapNumber = 0;
mLastEventTime = 0;
mLastX = 9999;
mLastY = 9999;
}
private boolean detectDownTouch(){
return (mDetectionMethod & DETECTION_METHOD_DOWN) == DETECTION_METHOD_DOWN;
}
private boolean detectUpTouch(){
return (mDetectionMethod & DETECTION_METHOD_UP) == DETECTION_METHOD_UP;
}
private boolean detectBothTouch(){
return mDetectionMethod == DETECTION_METHOD_BOTH;
}
}

View file

@ -2,8 +2,10 @@ package net.kdt.pojavlaunch;
import android.app.*;
import android.content.*;
import android.database.Cursor;
import android.net.*;
import android.os.*;
import android.provider.OpenableColumns;
import android.system.*;
import android.util.*;
import com.google.gson.*;
@ -21,6 +23,7 @@ import net.kdt.pojavlaunch.prefs.*;
import net.kdt.pojavlaunch.utils.*;
import net.kdt.pojavlaunch.value.*;
import net.kdt.pojavlaunch.value.launcherprofiles.LauncherProfiles;
import net.kdt.pojavlaunch.value.launcherprofiles.MinecraftProfile;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.digest.DigestUtils;
@ -48,6 +51,7 @@ public final class Tools {
public static String MULTIRT_HOME;
public static String LOCAL_RENDERER = null;
public static int DEVICE_ARCHITECTURE;
public static String LAUNCHERPROFILES_RTPREFIX = "pojav://";
// New since 3.3.1
public static String DIR_ACCOUNT_NEW;
@ -97,14 +101,14 @@ public final class Tools {
}
public static void launchMinecraft(final LoggableActivity ctx, MinecraftAccount profile, String versionName) throws Throwable {
public static void launchMinecraft(final Activity activity, MinecraftAccount profile, String versionName) throws Throwable {
ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
((ActivityManager)ctx.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryInfo(mi);
((ActivityManager)activity.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryInfo(mi);
if(LauncherPreferences.PREF_RAM_ALLOCATION > (mi.availMem/1048576L)) {
Object memoryErrorLock = new Object();
ctx.runOnUiThread(() -> {
androidx.appcompat.app.AlertDialog.Builder b = new androidx.appcompat.app.AlertDialog.Builder(ctx)
.setMessage(ctx.getString(R.string.memory_warning_msg,(mi.availMem/1048576L),LauncherPreferences.PREF_RAM_ALLOCATION))
activity.runOnUiThread(() -> {
androidx.appcompat.app.AlertDialog.Builder b = new androidx.appcompat.app.AlertDialog.Builder(activity)
.setMessage(activity.getString(R.string.memory_warning_msg,(mi.availMem/1048576L),LauncherPreferences.PREF_RAM_ALLOCATION))
.setPositiveButton(android.R.string.ok, (dialogInterface, i) -> {synchronized(memoryErrorLock){memoryErrorLock.notifyAll();}})
.setOnCancelListener((i) -> {synchronized(memoryErrorLock){memoryErrorLock.notifyAll();}});
b.show();
@ -115,16 +119,22 @@ public final class Tools {
}
JMinecraftVersionList.Version versionInfo = Tools.getVersionInfo(null,versionName);
PerVersionConfig.update();
PerVersionConfig.VersionConfig pvcConfig = PerVersionConfig.configMap.get(versionName);
String gamedirPath;
if(pvcConfig != null && pvcConfig.gamePath != null && !pvcConfig.gamePath.isEmpty()) gamedirPath = pvcConfig.gamePath;
else gamedirPath = Tools.DIR_GAME_NEW;
if(pvcConfig != null && pvcConfig.jvmArgs != null && !pvcConfig.jvmArgs.isEmpty()) LauncherPreferences.PREF_CUSTOM_JAVA_ARGS = pvcConfig.jvmArgs;
String gamedirPath = Tools.DIR_GAME_NEW;
if(activity instanceof BaseMainActivity) {
LauncherProfiles.update();
MinecraftProfile minecraftProfile = ((BaseMainActivity)activity).minecraftProfile;
if(minecraftProfile == null) throw new Exception("Launching empty Profile");
if(minecraftProfile.gameDir != null && minecraftProfile.gameDir.startsWith(Tools.LAUNCHERPROFILES_RTPREFIX))
gamedirPath = minecraftProfile.gameDir.replace(Tools.LAUNCHERPROFILES_RTPREFIX,Tools.DIR_GAME_HOME+"/");
if(minecraftProfile.javaArgs != null && !minecraftProfile.javaArgs.isEmpty())
LauncherPreferences.PREF_CUSTOM_JAVA_ARGS = minecraftProfile.javaArgs;
}
PojavLoginActivity.disableSplash(gamedirPath);
String[] launchArgs = getMinecraftClientArgs(profile, versionInfo, gamedirPath);
// Select the appropriate openGL version
OldVersionsUtils.selectOpenGlVersion(versionInfo);
// ctx.appendlnToLog("Minecraft Args: " + Arrays.toString(launchArgs));
String launchClassPath = generateLaunchClassPath(versionInfo,versionName);
@ -132,8 +142,11 @@ public final class Tools {
List<String> javaArgList = new ArrayList<String>();
// Only Java 8 supports headful AWT for now
if (ctx.jreReleaseList.get("JAVA_VERSION").equals("1.8.0")) {
if (JREUtils.jreReleaseList.get("JAVA_VERSION").equals("1.8.0")) {
getCacioJavaArgs(javaArgList, false);
} else if (LauncherPreferences.PREF_ARC_CAPES) {
// Opens the java.net package to Arc DNS injector on Java 9+
javaArgList.add("--add-opens=java.base/java.net=ALL-UNNAMED");
}
/*
@ -149,6 +162,9 @@ public final class Tools {
}
*/
if (versionInfo.logging != null) {
javaArgList.add("-Dlog4j.configurationFile=" + Tools.DIR_GAME_NEW + "/" + versionInfo.logging.client.file.id);
}
javaArgList.addAll(Arrays.asList(getMinecraftJVMArgs(versionName, gamedirPath)));
javaArgList.add("-cp");
javaArgList.add(getLWJGL3ClassPath() + ":" + launchClassPath);
@ -156,13 +172,13 @@ public final class Tools {
javaArgList.add(versionInfo.mainClass);
javaArgList.addAll(Arrays.asList(launchArgs));
// ctx.appendlnToLog("full args: "+javaArgList.toString());
JREUtils.launchJavaVM(ctx, javaArgList);
JREUtils.launchJavaVM(activity, javaArgList);
}
public static void getCacioJavaArgs(List<String> javaArgList, boolean isHeadless) {
javaArgList.add("-Djava.awt.headless="+isHeadless);
// Caciocavallo config AWT-enabled version
javaArgList.add("-Dcacio.managed.screensize=" + CallbackBridge.physicalWidth + "x" + CallbackBridge.physicalHeight);
javaArgList.add("-Dcacio.managed.screensize=" + AWTCanvasView.AWT_CANVAS_WIDTH + "x" + AWTCanvasView.AWT_CANVAS_HEIGHT);
// javaArgList.add("-Dcacio.font.fontmanager=net.java.openjdk.cacio.ctc.CTCFontManager");
javaArgList.add("-Dcacio.font.fontmanager=sun.awt.X11FontManager");
javaArgList.add("-Dcacio.font.fontscaler=sun.font.FreetypeFontScaler");
@ -271,14 +287,17 @@ public final class Tools {
}
}
}
/*
minecraftArgs.add("--width");
minecraftArgs.add(Integer.toString(CallbackBridge.windowWidth));
minecraftArgs.add("--height");
minecraftArgs.add(Integer.toString(CallbackBridge.windowHeight));
minecraftArgs.add("--fullscreenWidth");
minecraftArgs.add(Integer.toString(CallbackBridge.windowWidth));
minecraftArgs.add("--fullscreenHeight");
minecraftArgs.add(Integer.toString(CallbackBridge.windowHeight));
*/
String[] argsFromJson = JSONUtils.insertJSONValueList(
splitAndFilterEmpty(
@ -308,7 +327,7 @@ public final class Tools {
strList.add(arg);
}
}
strList.add("--fullscreen");
//strList.add("--fullscreen");
return strList.toArray(new String[0]);
}
@ -378,51 +397,49 @@ public final class Tools {
return libStr.toString();
}
public static DisplayMetrics getDisplayMetrics(Activity ctx) {
public static DisplayMetrics getDisplayMetrics(Activity activity) {
DisplayMetrics displayMetrics = new DisplayMetrics();
if(SDK_INT >= Build.VERSION_CODES.N && (ctx.isInMultiWindowMode() || ctx.isInPictureInPictureMode())
|| PREF_NOTCH_SIZE == -1 ){
if(SDK_INT >= Build.VERSION_CODES.N && (activity.isInMultiWindowMode() || activity.isInPictureInPictureMode())){
//For devices with free form/split screen, we need window size, not screen size.
displayMetrics = ctx.getResources().getDisplayMetrics();
displayMetrics = activity.getResources().getDisplayMetrics();
}else{
if (SDK_INT >= Build.VERSION_CODES.R) {
ctx.getDisplay().getRealMetrics(displayMetrics);
activity.getDisplay().getRealMetrics(displayMetrics);
} else {
ctx.getWindowManager().getDefaultDisplay().getRealMetrics(displayMetrics);
activity.getWindowManager().getDefaultDisplay().getRealMetrics(displayMetrics);
}
if(!PREF_IGNORE_NOTCH){
//Remove notch width when it isn't ignored.
displayMetrics.widthPixels -= PREF_NOTCH_SIZE;
}
}
currentDisplayMetrics = displayMetrics;
return displayMetrics;
}
public static void setFullscreen(Activity act) {
final View decorView = act.getWindow().getDecorView();
decorView.setOnSystemUiVisibilityChangeListener (new View.OnSystemUiVisibilityChangeListener() {
@Override
public void onSystemUiVisibilityChange(int visibility) {
if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
decorView.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
}
}
});
public static void setFullscreen(Activity activity) {
final View decorView = activity.getWindow().getDecorView();
decorView.setOnSystemUiVisibilityChangeListener (visibility -> {
if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
decorView.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
}
});
}
public static DisplayMetrics currentDisplayMetrics;
public static void updateWindowSize(Activity ctx) {
currentDisplayMetrics = getDisplayMetrics(ctx);
public static void updateWindowSize(Activity activity) {
currentDisplayMetrics = getDisplayMetrics(activity);
CallbackBridge.physicalWidth = (int) (currentDisplayMetrics.widthPixels);
CallbackBridge.physicalHeight = (int) (currentDisplayMetrics.heightPixels);
CallbackBridge.physicalWidth = currentDisplayMetrics.widthPixels;
CallbackBridge.physicalHeight = currentDisplayMetrics.heightPixels;
}
public static float dpToPx(float dp) {
@ -466,42 +483,37 @@ public final class Tools {
private static void showError(final Context ctx, final int titleId, final Throwable e, final boolean exitIfOk, final boolean showMore) {
e.printStackTrace();
Runnable runnable = new Runnable(){
@Override
public void run()
{
final String errMsg = showMore ? Log.getStackTraceString(e): e.getMessage();
AlertDialog.Builder builder = new AlertDialog.Builder((Context) ctx)
.setTitle(titleId)
.setMessage(errMsg)
.setPositiveButton(android.R.string.ok, (DialogInterface.OnClickListener) (p1, p2) -> {
if(exitIfOk) {
if (ctx instanceof BaseMainActivity) {
BaseMainActivity.fullyExit();
} else if (ctx instanceof Activity) {
((Activity) ctx).finish();
}
Runnable runnable = () -> {
final String errMsg = showMore ? Log.getStackTraceString(e): e.getMessage();
AlertDialog.Builder builder = new AlertDialog.Builder((Context) ctx)
.setTitle(titleId)
.setMessage(errMsg)
.setPositiveButton(android.R.string.ok, (DialogInterface.OnClickListener) (p1, p2) -> {
if(exitIfOk) {
if (ctx instanceof BaseMainActivity) {
BaseMainActivity.fullyExit();
} else if (ctx instanceof Activity) {
((Activity) ctx).finish();
}
})
.setNegativeButton(showMore ? R.string.error_show_less : R.string.error_show_more, (DialogInterface.OnClickListener) (p1, p2) -> showError(ctx, titleId, e, exitIfOk, !showMore))
.setNeutralButton(android.R.string.copy, (DialogInterface.OnClickListener) (p1, p2) -> {
ClipboardManager mgr = (ClipboardManager) ctx.getSystemService(Context.CLIPBOARD_SERVICE);
mgr.setPrimaryClip(ClipData.newPlainText("error", Log.getStackTraceString(e)));
if(exitIfOk) {
if (ctx instanceof BaseMainActivity) {
BaseMainActivity.fullyExit();
} else {
((Activity) ctx).finish();
}
}
})
.setNegativeButton(showMore ? R.string.error_show_less : R.string.error_show_more, (DialogInterface.OnClickListener) (p1, p2) -> showError(ctx, titleId, e, exitIfOk, !showMore))
.setNeutralButton(android.R.string.copy, (DialogInterface.OnClickListener) (p1, p2) -> {
ClipboardManager mgr = (ClipboardManager) ctx.getSystemService(Context.CLIPBOARD_SERVICE);
mgr.setPrimaryClip(ClipData.newPlainText("error", Log.getStackTraceString(e)));
if(exitIfOk) {
if (ctx instanceof BaseMainActivity) {
BaseMainActivity.fullyExit();
} else {
((Activity) ctx).finish();
}
})
.setCancelable(!exitIfOk);
try {
builder.show();
} catch (Throwable th) {
th.printStackTrace();
}
}
})
.setCancelable(!exitIfOk);
try {
builder.show();
} catch (Throwable th) {
th.printStackTrace();
}
};
@ -512,8 +524,8 @@ public final class Tools {
}
}
public static void dialogOnUiThread(final Activity ctx, final CharSequence title, final CharSequence message) {
ctx.runOnUiThread(() -> new AlertDialog.Builder(ctx)
public static void dialogOnUiThread(final Activity activity, final CharSequence title, final CharSequence message) {
activity.runOnUiThread(() -> new AlertDialog.Builder(activity)
.setTitle(title)
.setMessage(message)
.setPositiveButton(android.R.string.ok, null)
@ -597,7 +609,7 @@ public final class Tools {
}catch(IOException e) {
throw new RuntimeException("Can't find the source version for "+ versionName +" (req version="+customVer.inheritsFrom+")");
}
inheritsVer.inheritsFrom = inheritsVer.id;
//inheritsVer.inheritsFrom = inheritsVer.id;
insertSafety(inheritsVer, customVer,
"assetIndex", "assets", "id",
"mainClass", "minecraftArguments",
@ -693,45 +705,46 @@ public final class Tools {
}
public static String convertStream(InputStream inputStream, Charset charset) throws IOException {
String out = "";
StringBuilder out = new StringBuilder();
int len;
byte[] buf = new byte[512];
while((len = inputStream.read(buf))!=-1) {
out += new String(buf,0,len,charset);
out.append(new String(buf, 0, len, charset));
}
return out;
return out.toString();
}
public static File lastFileModified(String dir) {
File fl = new File(dir);
File[] files = fl.listFiles(new FileFilter() {
public boolean accept(File file) {
return file.isFile();
}
});
File[] files = fl.listFiles(File::isFile);
if(files == null) {
return null;
// The patch was a bit wrong...
// So, this may be null, why? Because this folder may not exist yet
// Or it may not have any files...
// Doesn't matter. We must check for that in the crash fragment.
}
long lastMod = Long.MIN_VALUE;
File choice = null;
for (File file : files) {
if (file.lastModified() > lastMod) {
choice = file;
lastMod = file.lastModified();
if (file.lastModified() > lastMod) {
choice = file;
lastMod = file.lastModified();
}
}
return choice;
}
public static String read(InputStream is) throws IOException {
String out = "";
StringBuilder out = new StringBuilder();
int len;
byte[] buf = new byte[512];
while((len = is.read(buf))!=-1) {
out += new String(buf,0,len);
out.append(new String(buf, 0, len));
}
return out;
return out.toString();
}
public static String read(String path) throws IOException {
@ -777,6 +790,7 @@ public final class Tools {
public abstract static class DownloaderFeedback {
public abstract void updateProgress(int curr, int max);
}
public static void downloadFileMonitored(String urlInput,String nameOutput, DownloaderFeedback monitor) throws IOException {
File nameOutputFile = new File(nameOutput);
if (!nameOutputFile.exists()) {
@ -813,75 +827,6 @@ public final class Tools {
return true;
}
}
public static class ZipTool
{
private ZipTool(){}
public static void zip(List<File> files, File zipFile) throws IOException {
final int BUFFER_SIZE = 2048;
BufferedInputStream origin = null;
ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(zipFile)));
try {
byte data[] = new byte[BUFFER_SIZE];
for (File file : files) {
FileInputStream fileInputStream = new FileInputStream( file );
origin = new BufferedInputStream(fileInputStream, BUFFER_SIZE);
try {
ZipEntry entry = new ZipEntry(file.getName());
out.putNextEntry(entry);
int count;
while ((count = origin.read(data, 0, BUFFER_SIZE)) != -1) {
out.write(data, 0, count);
}
}
finally {
origin.close();
}
}
} finally {
out.close();
}
}
public static void unzip(File zipFile, File targetDirectory) throws IOException {
final int BUFFER_SIZE = 1024;
ZipInputStream zis = new ZipInputStream(
new BufferedInputStream(new FileInputStream(zipFile)));
try {
ZipEntry ze;
int count;
byte[] buffer = new byte[BUFFER_SIZE];
while ((ze = zis.getNextEntry()) != null) {
File file = new File(targetDirectory, ze.getName());
File dir = ze.isDirectory() ? file : file.getParentFile();
if (!dir.isDirectory() && !dir.mkdirs())
throw new FileNotFoundException("Failed to ensure directory: " +
dir.getAbsolutePath());
if (ze.isDirectory())
continue;
FileOutputStream fout = new FileOutputStream(file);
try {
while ((count = zis.read(buffer)) != -1)
fout.write(buffer, 0, count);
} finally {
fout.close();
}
/* if time should be restored as well
long time = ze.getTime();
if (time > 0)
file.setLastModified(time);
*/
}
} finally {
zis.close();
}
}
}
public static void ignoreNotch(boolean shouldIgnore, Activity ctx){
if (SDK_INT >= P) {
@ -914,4 +859,26 @@ public final class Tools {
if(displaySideRes % 2 != 0) displaySideRes ++;
return displaySideRes;
}
public static String getFileName(Context ctx, Uri uri) {
String result = null;
if (uri.getScheme().equals("content")) {
Cursor cursor = ctx.getContentResolver().query(uri, null, null, null, null);
try {
if (cursor != null && cursor.moveToFirst()) {
result = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
}
} finally {
cursor.close();
}
}
if (result == null) {
result = uri.getPath();
int cut = result.lastIndexOf('/');
if (cut != -1) {
result = result.substring(cut + 1);
}
}
return result;
}
}

View file

@ -0,0 +1,189 @@
package net.kdt.pojavlaunch;
import static net.kdt.pojavlaunch.MinecraftGLSurface.FINGER_SCROLL_THRESHOLD;
import static net.kdt.pojavlaunch.Tools.currentDisplayMetrics;
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.DEFAULT_PREF;
import android.content.Context;
import android.os.Build;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.res.ResourcesCompat;
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
import org.lwjgl.glfw.CallbackBridge;
/**
* Class dealing with the virtual mouse
*/
public class Touchpad extends FrameLayout {
/* Whether the Touchpad should be displayed */
private boolean mDisplayState;
/* Mouse pointer icon used by the touchpad */
private final ImageView mMousePointerImageView = new ImageView(getContext());
/* Detect a classic android Tap */
private final GestureDetector mSingleTapDetector = new GestureDetector(getContext(), new SingleTapConfirm());
/* Resolution scaler option, allow downsizing a window */
private final float mScaleFactor = DEFAULT_PREF.getInt("resolutionRatio",100)/100f;
/* Current pointer ID to move the mouse */
private int mCurrentPointerID = -1000;
/* Previous MotionEvent position, not scale */
private float mPrevX, mPrevY;
/* Last first pointer positions non-scaled, used to scroll distance */
private float mScrollLastInitialX, mScrollLastInitialY;
public Touchpad(@NonNull Context context) {
this(context, null);
}
public Touchpad(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// MotionEvent reports input details from the touch screen
// and other input controls. In this case, you are only
// interested in events where the touch position changed.
// int index = event.getActionIndex();
if(CallbackBridge.isGrabbing()) {
disable();
return false;
}
int action = event.getActionMasked();
float x = event.getX();
float y = event.getY();
float mouseX = mMousePointerImageView.getX();
float mouseY = mMousePointerImageView.getY();
if (mSingleTapDetector.onTouchEvent(event)) {
CallbackBridge.mouseX = (mouseX * mScaleFactor);
CallbackBridge.mouseY = (mouseY * mScaleFactor);
CallbackBridge.sendCursorPos(CallbackBridge.mouseX, CallbackBridge.mouseY);
CallbackBridge.sendMouseKeycode(LwjglGlfwKeycode.GLFW_MOUSE_BUTTON_LEFT);
return true;
}
switch (action) {
case MotionEvent.ACTION_POINTER_DOWN: // 5
mScrollLastInitialX = event.getX();
mScrollLastInitialY = event.getY();
break;
case MotionEvent.ACTION_DOWN:
mPrevX = x;
mPrevY = y;
mCurrentPointerID = event.getPointerId(0);
break;
case MotionEvent.ACTION_MOVE: // 2
//Scrolling feature
if (!LauncherPreferences.PREF_DISABLE_GESTURES && !CallbackBridge.isGrabbing() && event.getPointerCount() >= 2) {
int hScroll = ((int) (event.getX() - mScrollLastInitialX)) / FINGER_SCROLL_THRESHOLD;
int vScroll = ((int) (event.getY() - mScrollLastInitialY)) / FINGER_SCROLL_THRESHOLD;
if(vScroll != 0 || hScroll != 0){
CallbackBridge.sendScroll(hScroll, vScroll);
mScrollLastInitialX = event.getX();
mScrollLastInitialY = event.getY();
}
break;
}
// Mouse movement
if(mCurrentPointerID == event.getPointerId(0)) {
mouseX = Math.max(0, Math.min(currentDisplayMetrics.widthPixels, mouseX + (x - mPrevX) * LauncherPreferences.PREF_MOUSESPEED));
mouseY = Math.max(0, Math.min(currentDisplayMetrics.heightPixels, mouseY + (y - mPrevY) * LauncherPreferences.PREF_MOUSESPEED));
placeMouseAt(mouseX, mouseY);
CallbackBridge.sendCursorPos(CallbackBridge.mouseX, CallbackBridge.mouseY);
}else mCurrentPointerID = event.getPointerId(0);
mPrevX = x;
mPrevY = y;
break;
case MotionEvent.ACTION_UP:
mPrevX = x;
mPrevY = y;
mCurrentPointerID = -1000;
break;
}
//debugText.setText(CallbackBridge.DEBUG_STRING.toString());
CallbackBridge.DEBUG_STRING.setLength(0);
return true;
}
/** Enable the touchpad */
public void enable(){
setVisibility(VISIBLE);
placeMouseAt(currentDisplayMetrics.widthPixels / 2, currentDisplayMetrics.heightPixels / 2);
}
/** Disable the touchpad and hides the mouse */
public void disable(){
setVisibility(GONE);
}
/** @return The new state, enabled or disabled */
public boolean switchState(){
mDisplayState = !mDisplayState;
return mDisplayState;
}
public void placeMouseAt(float x, float y) {
mMousePointerImageView.setX(x);
mMousePointerImageView.setY(y);
CallbackBridge.mouseX = (x * mScaleFactor);
CallbackBridge.mouseY = (y * mScaleFactor);
CallbackBridge.sendCursorPos(CallbackBridge.mouseX, CallbackBridge.mouseY);
}
private void init(){
// Setup mouse pointer
mMousePointerImageView.setImageDrawable(ResourcesCompat.getDrawable(getResources(), R.drawable.ic_mouse_pointer, getContext().getTheme()));
mMousePointerImageView.post(() -> {
ViewGroup.LayoutParams params = mMousePointerImageView.getLayoutParams();
params.width = (int) (36 / 100f * LauncherPreferences.PREF_MOUSESCALE);
params.height = (int) (54 / 100f * LauncherPreferences.PREF_MOUSESCALE);
});
addView(mMousePointerImageView);
setFocusable(false);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
setDefaultFocusHighlightEnabled(false);
}
// When the game is grabbing, we should not display the mouse
disable();
mDisplayState = false;
Thread virtualMouseGrabThread = new Thread(() -> {
while (true) {
if (!CallbackBridge.isGrabbing() && mDisplayState && getVisibility() != VISIBLE) {
post(this::enable);
}else{
if ((CallbackBridge.isGrabbing() && getVisibility() != View.GONE) || !mDisplayState && getVisibility() == VISIBLE) {
post(this::disable);
}
}
}
}, "VirtualMouseGrabThread");
virtualMouseGrabThread.setPriority(Thread.MIN_PRIORITY);
virtualMouseGrabThread.start();
}
}

View file

@ -71,7 +71,7 @@ public class MicrosoftAuthTask extends AsyncTask<String, Void, Object> {
*/
Msa msa = new Msa(this, Boolean.parseBoolean(args[0]), args[1]);
MinecraftAccount acc = new MinecraftAccount();
MinecraftAccount acc = MinecraftAccount.load(msa.mcName);
if (msa.doesOwnGame) {
acc.clientToken = "0"; /* FIXME */
acc.accessToken = msa.mcToken;

View file

@ -8,6 +8,7 @@ import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.ViewGroup;
import android.webkit.CookieManager;
import android.webkit.WebResourceRequest;
import android.webkit.WebSettings;
import android.webkit.WebView;
@ -24,6 +25,7 @@ public class MicrosoftLoginGUIActivity extends AppCompatActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
CookieManager.getInstance().removeAllCookie();
waitDialog = new ProgressDialog(this);
waitDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
waitDialog.setIndeterminate(true);

View file

@ -1,3 +1,4 @@
package net.kdt.pojavlaunch.authenticator.mojang.yggdrasil;
import java.util.UUID;
@ -20,5 +21,4 @@ public class AuthenticateRequest {
this.agent.name = clientName;
this.agent.version = clientVersion;
}
}
}

View file

@ -2,6 +2,6 @@ package net.kdt.pojavlaunch.authenticator.mojang;
public interface LoginListener
{
public void onBeforeLogin();
public void onLoginDone(String[] result);
}
public void onBeforeLogin();
public void onLoginDone(String[] result);
}

View file

@ -11,23 +11,23 @@ public class LoginTask extends AsyncTask<String, Void, Void>
private YggdrasilAuthenticator authenticator = new YggdrasilAuthenticator();
//private String TAG = "MojangAuth-login";
private LoginListener listener;
public LoginTask setLoginListener(LoginListener listener) {
this.listener = listener;
return this;
}
private UUID getRandomUUID() {
return UUID.randomUUID();
}
@Override
protected void onPreExecute() {
listener.onBeforeLogin();
super.onPreExecute();
}
@Override
protected Void doInBackground(String[] args) {
ArrayList<String> str = new ArrayList<String>();
@ -49,7 +49,7 @@ public class LoginTask extends AsyncTask<String, Void, Void>
}
}
}
//MainActivity.updateStatus(804);
//MainActivity.updateStatus(804);
catch(Throwable e){
str.add(e.getMessage());
}
@ -57,15 +57,15 @@ public class LoginTask extends AsyncTask<String, Void, Void>
catch(Exception e){
str.add(e.getMessage());
}
listener.onLoginDone(str.toArray(new String[0]));
return null;
}
@Override
protected void onPostExecute(Void result) {
// listener.onLoginDone(result);
super.onPostExecute(result);
}
}
}

View file

@ -52,7 +52,7 @@ public class RefreshTokenTask extends AsyncTask<String, Void, Throwable> {
} else if (response.selectedProfile == null) {
throw new IllegalArgumentException("Can't refresh a demo account!");
}
profilePath.clientToken = response.clientToken.toString();
profilePath.accessToken = response.accessToken;
profilePath.username = response.selectedProfile.name;
@ -75,5 +75,4 @@ public class RefreshTokenTask extends AsyncTask<String, Void, Throwable> {
listener.onFailed(result);
}
}
}
}

View file

@ -7,5 +7,4 @@ public class AuthenticateResponse {
public Profile[] availableProfiles;
public UUID clientToken;
public Profile selectedProfile;
}
}

View file

@ -6,5 +6,4 @@ public class RefreshResponse {
public String accessToken;
public UUID clientToken;
public Profile selectedProfile;
}
}

View file

@ -89,9 +89,8 @@ public class YggdrasilAuthenticator {
throw th;
}
}
public AuthenticateResponse authenticate(String username, String password, UUID clientId) throws IOException, Throwable {
NetworkResponse obj = makeRequest("authenticate", new AuthenticateRequest(username, password, clientId, this.clientName, this.clientVersion), AuthenticateResponse.class);
NetworkResponse obj = makeRequest("authenticate", new net.kdt.pojavlaunch.authenticator.mojang.yggdrasil.AuthenticateRequest(username, password, clientId, this.clientName, this.clientVersion), AuthenticateResponse.class);
/*
if (obj.statusCode != 200) {
throw new RuntimeException("Invalid username or password, status code: " + obj.statusCode);
@ -110,11 +109,12 @@ public class YggdrasilAuthenticator {
return (RefreshResponse) obj.response;
}
}
public NetworkResponse validate(String authToken) throws Throwable {
return makeRequest("validate", new RefreshRequest(authToken, null), null);
}
public NetworkResponse invalidate(String authToken, UUID clientId) throws Throwable {
return makeRequest("invalidate", new RefreshRequest(authToken, clientId), null);
}

View file

@ -0,0 +1,5 @@
package net.kdt.pojavlaunch.colorselector;
public interface AlphaSelectionListener {
void onAlphaSelected(int alpha);
}

View file

@ -0,0 +1,76 @@
package net.kdt.pojavlaunch.colorselector;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import net.kdt.pojavlaunch.Tools;
import top.defaults.checkerboarddrawable.CheckerboardDrawable;
public class AlphaView extends View {
private final Drawable mCheckerboardDrawable = CheckerboardDrawable.create();
private final Paint mShaderPaint = new Paint();
private final Paint mBlackPaint;
private final RectF mViewSize = new RectF(0,0,0,0);
private AlphaSelectionListener mAlphaSelectionListener;
private int mSelectedAlpha;
private float mAlphaDiv; // for quick pos->alpha multiplication
private float mScreenDiv; // for quick alpha->pos multiplication
private float mHeightThird; // 1/3 of the view size for cursor
public AlphaView(Context ctx, AttributeSet attrs) {
super(ctx,attrs);
mBlackPaint = new Paint();
mBlackPaint.setStrokeWidth(Tools.dpToPx(3));
mBlackPaint.setColor(Color.BLACK);
}
public void setAlphaSelectionListener(AlphaSelectionListener alphaSelectionListener) {
mAlphaSelectionListener = alphaSelectionListener;
}
public void setAlpha(int alpha) {
mSelectedAlpha = alpha;
invalidate();
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) {
mSelectedAlpha = (int) (mAlphaDiv * event.getX());
if(mSelectedAlpha < 0) mSelectedAlpha = 0;
if(mSelectedAlpha > 0xff) mSelectedAlpha = 0xff;
if(mAlphaSelectionListener != null) mAlphaSelectionListener.onAlphaSelected(mSelectedAlpha);
invalidate();
return true;
}
@Override
protected void onSizeChanged(int w, int h, int old_w, int old_h) {
mViewSize.right = w;
mViewSize.bottom = h;
float h2 = mViewSize.bottom / 2f;
mShaderPaint.setShader(new LinearGradient(0,h2,w,h2, 0, Color.BLACK, Shader.TileMode.REPEAT));
mAlphaDiv = 255f / mViewSize.right;
mScreenDiv = mViewSize.right / 255f;
mHeightThird = mViewSize.bottom / 3f;
}
@Override
protected void onDraw(Canvas canvas) {
mCheckerboardDrawable.draw(canvas);
canvas.drawRect(mViewSize, mShaderPaint);
float linePos = mSelectedAlpha * mScreenDiv;
canvas.drawLine(linePos, 0 ,linePos, mHeightThird, mBlackPaint);
canvas.drawLine(linePos, mHeightThird * 2 ,linePos, mViewSize.bottom, mBlackPaint);
}
}

View file

@ -0,0 +1,9 @@
package net.kdt.pojavlaunch.colorselector;
public interface ColorSelectionListener {
/**
* This method gets called by the ColorSelector when the color is selected
* @param color the selected color
*/
void onColorSelected(int color);
}

View file

@ -0,0 +1,150 @@
package net.kdt.pojavlaunch.colorselector;
import android.app.AlertDialog;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.EditText;
import net.kdt.pojavlaunch.R;
public class ColorSelector implements HueSelectionListener, RectangleSelectionListener, AlphaSelectionListener, TextWatcher{
private static final int ALPHA_MASK = ~(0xFF << 24);
private final HueView mHueView;
private final SVRectangleView mLuminosityIntensityView;
private final AlphaView mAlphaView;
private final ColorSideBySideView mColorView;
private final EditText mTextView;
private final AlertDialog mDialog;
private ColorSelectionListener mColorSelectionListener;
private float[] mHueTemplate = new float[] {0,1,1};
private float[] mHsvSelected = new float[] {360,1,1};
private int mAlphaSelected = 0xff;
private ColorStateList mTextColors;
private boolean mWatch = true;
/**
* Creates a color selector dialog for this Context.
* @param context Context used for this ColorSelector dialog
* @param colorSelectionListener Color selection listener to which the events will be sent to. Can be null.
*/
public ColorSelector(Context context, ColorSelectionListener colorSelectionListener) {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
View view = LayoutInflater.from(context).inflate(R.layout.dialog_color_selector,null);
mHueView = view.findViewById(R.id.color_selector_hue_view);
mLuminosityIntensityView = view.findViewById(R.id.color_selector_rectangle_view);
mAlphaView = view.findViewById(R.id.color_selector_alpha_view);
mColorView = view.findViewById(R.id.color_selector_color_view);
mTextView = view.findViewById(R.id.color_selector_hex_edit);
runColor(Color.RED);
mHueView.setHueSelectionListener(this);
mLuminosityIntensityView.setRectSelectionListener(this);
mAlphaView.setAlphaSelectionListener(this);
mTextView.addTextChangedListener(this);
mTextColors = mTextView.getTextColors();
builder.setView(view);
builder.setPositiveButton(android.R.string.ok,(dialog,which)->{
if (mColorSelectionListener != null) {
mColorSelectionListener.onColorSelected(Color.HSVToColor(mAlphaSelected, mHsvSelected));
}
});
builder.setNegativeButton(android.R.string.cancel, ((dialog, which) -> {}));
mColorSelectionListener = colorSelectionListener;
mDialog = builder.create();
}
/**
* Shows the color selector with the default (red) color selected.
*/
public void show() {
runColor(Color.RED);
dispatchColorChange();
mDialog.show();
}
/**
* Shows the color selector with the desired ARGB color selected
* @param previousColor the desired ARGB color
*/
public void show(int previousColor) {
runColor(previousColor); // initialize
dispatchColorChange(); // set the hex text
mDialog.show();
}
@Override
public void onHueSelected(float hue) {
mHsvSelected[0] = mHueTemplate[0] = hue;
mLuminosityIntensityView.setColor(Color.HSVToColor(mHueTemplate), true);
dispatchColorChange();
}
@Override
public void onLuminosityIntensityChanged(float luminosity, float intensity) {
mHsvSelected[1] = intensity;
mHsvSelected[2] = luminosity;
dispatchColorChange();
}
@Override
public void onAlphaSelected(int alpha) {
mAlphaSelected = alpha;
dispatchColorChange();
}
/**
* Replaces the alpha value of the color passed in, and returns the result.
* @param color the color to replace the alpha of
* @param alpha the alpha to use
* @return the new color
*/
public static int setAlpha(int color, int alpha) {
return color & ALPHA_MASK | ((alpha & 0xFF) << 24);
}
//IUO: called on all color changes
protected void dispatchColorChange() {
int color = Color.HSVToColor(mAlphaSelected, mHsvSelected);
mColorView.setColor(color);
mWatch = false;
mTextView.setText(String.format("%08X",color));
}
//IUO: sets all Views to render the desired color. Used for initilaization and HEX color input
protected void runColor(int color) {
Color.RGBToHSV(Color.red(color), Color.green(color), Color.blue(color), mHsvSelected);
mHueTemplate[0] = mHsvSelected[0];
mHueView.setHue(mHsvSelected[0]);
mLuminosityIntensityView.setColor(Color.HSVToColor(mHueTemplate), false);
mLuminosityIntensityView.setLuminosityIntensity(mHsvSelected[2], mHsvSelected[1]);
mAlphaSelected = Color.alpha(color);
mAlphaView.setAlpha(mAlphaSelected);
mColorView.setColor(color);
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {}
@Override
public void afterTextChanged(Editable s) {
if(mWatch) {
try {
int color = Integer.parseInt(s.toString(), 16);
mTextView.setTextColor(mTextColors);
runColor(color);
}catch (NumberFormatException exception) {
mTextView.setTextColor(Color.RED);
}
}else{
mWatch = true;
}
}
}

View file

@ -0,0 +1,47 @@
package net.kdt.pojavlaunch.colorselector;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.Nullable;
import top.defaults.checkerboarddrawable.CheckerboardDrawable;
public class ColorSideBySideView extends View {
private final Paint mPaint;
private final CheckerboardDrawable mCheckerboardDrawable = CheckerboardDrawable.create();
private int mColor;
private int mAlphaColor;
private float mWidth;
private float mHeight;
private float mHalfHeight;
public ColorSideBySideView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint();
}
public void setColor(int color) {
mColor = ColorSelector.setAlpha(color, 0xff);
mAlphaColor = color;
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
mCheckerboardDrawable.draw(canvas);
mPaint.setColor(mColor);
canvas.drawRect(0,0,mWidth, mHalfHeight, mPaint);
mPaint.setColor(mAlphaColor);
canvas.drawRect(0,mHalfHeight,mWidth,mHeight, mPaint);
}
@Override
protected void onSizeChanged(int w, int h, int old_w, int old_h) {
mHalfHeight = h/2f;
mWidth = w;
mHeight = h;
}
}

View file

@ -0,0 +1,5 @@
package net.kdt.pojavlaunch.colorselector;
public interface HueSelectionListener {
void onHueSelected(float hue);
}

View file

@ -0,0 +1,83 @@
package net.kdt.pojavlaunch.colorselector;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import androidx.annotation.Nullable;
import net.kdt.pojavlaunch.Tools;
public class HueView extends View {
private final Paint blackPaint = new Paint();
private Bitmap mGamma;
private HueSelectionListener mHueSelectionListener;
private float mSelectionHue;
private float mWidthHueRatio;
private float mHueWidthRatio;
private float mWidth;
private float mHeight;
private float mHeightThird;
public HueView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
blackPaint.setColor(Color.BLACK);
blackPaint.setStrokeWidth(Tools.dpToPx(3));
}
public void setHueSelectionListener(HueSelectionListener listener) {
mHueSelectionListener = listener;
}
public void setHue(float hue) {
mSelectionHue = hue;
invalidate();
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) {
mSelectionHue = event.getX() * mWidthHueRatio;
invalidate();
if(mHueSelectionListener != null) mHueSelectionListener.onHueSelected(mSelectionHue);
return true;
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(mGamma, 0, 0 ,null);
float linePos = mSelectionHue * mHueWidthRatio;
canvas.drawLine(linePos, 0 ,linePos, mHeightThird, blackPaint);
canvas.drawLine(linePos, mHeightThird * 2 ,linePos, mHeight, blackPaint);
}
@Override
protected void onSizeChanged(int w, int h, int old_w, int old_h) {
mWidth = w;
mHeight = h;
mHeightThird = mHeight / 3;
regenerateGammaBitmap();
}
protected void regenerateGammaBitmap() {
if(mGamma != null)
mGamma.recycle();
mGamma = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
Paint paint = new Paint();
Canvas canvas = new Canvas(mGamma);
mWidthHueRatio = 360/ mWidth;
mHueWidthRatio = mWidth / 360;
float[] hsvFiller = new float[] {0, 1, 1};
for(float i = 0; i < mWidth; i++) {
hsvFiller[0] = i * mWidthHueRatio;
paint.setColor(Color.HSVToColor(hsvFiller));
canvas.drawLine(i,0,i, mHeight,paint);
}
}
}

View file

@ -0,0 +1,5 @@
package net.kdt.pojavlaunch.colorselector;
public interface RectangleSelectionListener {
void onLuminosityIntensityChanged(float luminosity, float intensity);
}

View file

@ -0,0 +1,113 @@
package net.kdt.pojavlaunch.colorselector;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.Shader;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import androidx.annotation.Nullable;
import net.kdt.pojavlaunch.Tools;
public class SVRectangleView extends View {
private final Paint mColorPaint = new Paint();
private final Paint mPointerPaint = new Paint();
private final float mPointerSize;
private Bitmap mSvRectangle;
private RectF mViewSize;
private float mHeightInverted;
private float mWidthInverted;
private float mFingerPosX;
private float mFingerPosY;
RectangleSelectionListener mRectSelectionListener;
public SVRectangleView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mColorPaint.setColor(Color.BLACK);
mColorPaint.setStyle(Paint.Style.FILL);
mPointerSize = Tools.dpToPx(6);
mPointerPaint.setColor(Color.BLACK);
mPointerPaint.setStrokeWidth(Tools.dpToPx(3));
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
mFingerPosX = x * mWidthInverted;
mFingerPosY = y * mHeightInverted;
if(mFingerPosX < 0) mFingerPosX = 0;
else if(mFingerPosX > 1) mFingerPosX = 1;
if(mFingerPosY < 0) mFingerPosY = 0;
else if(mFingerPosY > 1) mFingerPosY = 1;
if(mRectSelectionListener != null) mRectSelectionListener.onLuminosityIntensityChanged(mFingerPosY,mFingerPosX);
invalidate();
return true;
}
public void setLuminosityIntensity(float luminosity, float intensity) {
mFingerPosX = intensity;
mFingerPosY = luminosity;
invalidate();
}
public void setColor(int color, boolean invalidate) {
mColorPaint.setColor(color);
if(invalidate) invalidate();
}
public void setRectSelectionListener(RectangleSelectionListener listener) {
mRectSelectionListener = listener;
}
protected void drawPointer(Canvas canvas, float x, float y) {
canvas.drawLine(mPointerSize * 2 + x, y, mPointerSize + x, y, mPointerPaint);
canvas.drawLine(x - mPointerSize * 2, y, x - mPointerSize, y, mPointerPaint);
canvas.drawLine(x, mPointerSize * 2 + y, x, mPointerSize + y, mPointerPaint);
canvas.drawLine(x, y - mPointerSize * 2, x, y - mPointerSize, mPointerPaint);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawRect(mViewSize, mColorPaint);
canvas.drawBitmap(mSvRectangle, 0,0, null);
drawPointer(canvas, mViewSize.right * mFingerPosX, mViewSize.bottom * mFingerPosY);
}
@Override
protected void onSizeChanged(int w, int h, int old_w, int old_h) {
mViewSize = new RectF(0,0, w,h);
mWidthInverted = 1/mViewSize.right;
mHeightInverted = 1/mViewSize.bottom;
if(w > 0 && h > 0)
regenerateRectangle();
}
protected void regenerateRectangle() {
int w = getWidth();
int h = getHeight();
if(mSvRectangle != null)
mSvRectangle.recycle();
mSvRectangle = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
Paint rectPaint = new Paint();
Canvas canvas = new Canvas(mSvRectangle);
float h2f = h/2f;
float w2f = w/2f;
rectPaint.setShader(new LinearGradient(0,h2f, w, h2f, Color.WHITE,0, Shader.TileMode.CLAMP));
canvas.drawRect(mViewSize, rectPaint);
rectPaint.setShader(new LinearGradient(w2f,0, w2f, h, Color.BLACK,0, Shader.TileMode.CLAMP));
canvas.drawRect(mViewSize, rectPaint);
}
}

View file

@ -3,7 +3,6 @@ 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.*;
@ -14,8 +13,12 @@ import net.objecthunter.exp4j.function.Function;
import org.lwjgl.glfw.*;
import static net.kdt.pojavlaunch.LWJGLGLFWKeycode.GLFW_KEY_UNKNOWN;
import static net.kdt.pojavlaunch.LwjglGlfwKeycode.GLFW_KEY_UNKNOWN;
import static org.lwjgl.glfw.CallbackBridge.sendKeyPress;
import androidx.annotation.Keep;
@Keep
public class ControlData {
public static final int SPECIALBTN_KEYBOARD = -1;
@ -34,10 +37,9 @@ public class ControlData {
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();
buildExpressionBuilder();
buildConversionMap();
}
@ -174,7 +176,7 @@ public class ControlData {
public void execute(boolean isDown) {
for(int keycode : keycodes){
BaseMainActivity.sendKeyPress(keycode, 0, isDown);
sendKeyPress(keycode, 0, isDown);
}
}
@ -226,11 +228,9 @@ public class ControlData {
}
/**
* 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.
* Create a builder, keep a weak reference to it to use it with all views on first inflation
*/
private static void bypassExpressionBuilder(){
private static void buildExpressionBuilder(){
ExpressionBuilder expressionBuilder = new ExpressionBuilder("1 + 1")
.function(new Function("dp", 1) {
@Override
@ -245,11 +245,6 @@ public class ControlData {
}
});
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){}
}
/**
@ -257,10 +252,8 @@ public class ControlData {
* @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){}
if(builder.get() == null) buildExpressionBuilder();
builder.get().expression(stringExpression);
}
/**
@ -276,8 +269,8 @@ public class ControlData {
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("screen_width", "DUMMY_DATA" );
keyValueMap.put("screen_height", "DUMMY_DATA");
keyValueMap.put("margin", Integer.toString((int) Tools.dpToPx(2)));
keyValueMap.put("preferred_scale", Float.toString(LauncherPreferences.PREF_BUTTONSIZE));
@ -300,6 +293,8 @@ public class ControlData {
valueMap.put("bottom", Float.toString(CallbackBridge.physicalHeight - getHeight()));
valueMap.put("width", Float.toString(getWidth()));
valueMap.put("height", Float.toString(getHeight()));
valueMap.put("screen_width",Integer.toString(CallbackBridge.physicalWidth));
valueMap.put("screen_height",Integer.toString(CallbackBridge.physicalHeight));
return valueMap;
}

View file

@ -10,14 +10,16 @@ import static net.kdt.pojavlaunch.customcontrols.ControlDrawerData.Orientation.R
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.Keep;
@androidx.annotation.Keep
public class ControlDrawerData {
public ArrayList<ControlData> buttonProperties;
public ControlData properties;
public Orientation orientation;
@androidx.annotation.Keep
public enum Orientation {
DOWN,
LEFT,

View file

@ -16,8 +16,7 @@ import net.kdt.pojavlaunch.customcontrols.buttons.ControlSubButton;
import net.kdt.pojavlaunch.customcontrols.handleview.HandleView;
import net.kdt.pojavlaunch.prefs.*;
public class ControlLayout extends FrameLayout
{
public class ControlLayout extends FrameLayout {
protected CustomControls mLayout;
private boolean mModifiable;
private CustomControlsActivity mActivity;
@ -252,6 +251,13 @@ public class ControlLayout extends FrameLayout
return children;
}
public void refreshControlButtonPositions(){
for(ControlButton button : getButtonChildren()){
button.setDynamicX(button.getProperties().dynamicX);
button.setDynamicY(button.getProperties().dynamicY);
}
}
HashMap<View, ControlButton> mapTable = new HashMap<>();
//While this is called onTouch, this should only be called from a ControlButton.
public boolean onTouch(View v, MotionEvent ev) {

View file

@ -1,12 +1,13 @@
package net.kdt.pojavlaunch.customcontrols;
import android.content.*;
import com.google.gson.*;
import androidx.annotation.Keep;
import java.io.IOException;
import java.util.*;
import net.kdt.pojavlaunch.*;
import org.lwjgl.glfw.*;
@Keep
public class CustomControls {
public int version = -1;
public float scaledAt;
@ -33,22 +34,22 @@ public class CustomControls {
this.mControlDataList.add(new ControlData(ControlData.getSpecialButtons()[3])); // Secondary Mouse mControlDataList
this.mControlDataList.add(new ControlData(ControlData.getSpecialButtons()[4])); // Virtual mouse toggle
this.mControlDataList.add(new ControlData(ctx, R.string.control_debug, new int[]{LWJGLGLFWKeycode.GLFW_KEY_F3}, "${margin}", "${margin}", false));
this.mControlDataList.add(new ControlData(ctx, R.string.control_chat, new int[]{LWJGLGLFWKeycode.GLFW_KEY_T}, "${margin} * 2 + ${width}", "${margin}", false));
this.mControlDataList.add(new ControlData(ctx, R.string.control_listplayers, new int[]{LWJGLGLFWKeycode.GLFW_KEY_TAB}, "${margin} * 4 + ${width} * 3", "${margin}", false));
this.mControlDataList.add(new ControlData(ctx, R.string.control_thirdperson, new int[]{LWJGLGLFWKeycode.GLFW_KEY_F5}, "${margin}", "${height} + ${margin}", false));
this.mControlDataList.add(new ControlData(ctx, R.string.control_debug, new int[]{LwjglGlfwKeycode.GLFW_KEY_F3}, "${margin}", "${margin}", false));
this.mControlDataList.add(new ControlData(ctx, R.string.control_chat, new int[]{LwjglGlfwKeycode.GLFW_KEY_T}, "${margin} * 2 + ${width}", "${margin}", false));
this.mControlDataList.add(new ControlData(ctx, R.string.control_listplayers, new int[]{LwjglGlfwKeycode.GLFW_KEY_TAB}, "${margin} * 4 + ${width} * 3", "${margin}", false));
this.mControlDataList.add(new ControlData(ctx, R.string.control_thirdperson, new int[]{LwjglGlfwKeycode.GLFW_KEY_F5}, "${margin}", "${height} + ${margin}", false));
this.mControlDataList.add(new ControlData(ctx, R.string.control_up, new int[]{LWJGLGLFWKeycode.GLFW_KEY_W}, "${margin} * 2 + ${width}", "${bottom} - ${margin} * 3 - ${height} * 2", true));
this.mControlDataList.add(new ControlData(ctx, R.string.control_left, new int[]{LWJGLGLFWKeycode.GLFW_KEY_A}, "${margin}", "${bottom} - ${margin} * 2 - ${height}", true));
this.mControlDataList.add(new ControlData(ctx, R.string.control_down, new int[]{LWJGLGLFWKeycode.GLFW_KEY_S}, "${margin} * 2 + ${width}", "${bottom} - ${margin}", true));
this.mControlDataList.add(new ControlData(ctx, R.string.control_right, new int[]{LWJGLGLFWKeycode.GLFW_KEY_D}, "${margin} * 3 + ${width} * 2", "${bottom} - ${margin} * 2 - ${height}", true));
this.mControlDataList.add(new ControlData(ctx, R.string.control_up, new int[]{LwjglGlfwKeycode.GLFW_KEY_W}, "${margin} * 2 + ${width}", "${bottom} - ${margin} * 3 - ${height} * 2", true));
this.mControlDataList.add(new ControlData(ctx, R.string.control_left, new int[]{LwjglGlfwKeycode.GLFW_KEY_A}, "${margin}", "${bottom} - ${margin} * 2 - ${height}", true));
this.mControlDataList.add(new ControlData(ctx, R.string.control_down, new int[]{LwjglGlfwKeycode.GLFW_KEY_S}, "${margin} * 2 + ${width}", "${bottom} - ${margin}", true));
this.mControlDataList.add(new ControlData(ctx, R.string.control_right, new int[]{LwjglGlfwKeycode.GLFW_KEY_D}, "${margin} * 3 + ${width} * 2", "${bottom} - ${margin} * 2 - ${height}", true));
this.mControlDataList.add(new ControlData(ctx, R.string.control_inventory, new int[]{LWJGLGLFWKeycode.GLFW_KEY_E}, "${margin} * 3 + ${width} * 2", "${bottom} - ${margin}", true));
this.mControlDataList.add(new ControlData(ctx, R.string.control_inventory, new int[]{LwjglGlfwKeycode.GLFW_KEY_E}, "${margin} * 3 + ${width} * 2", "${bottom} - ${margin}", true));
ControlData shiftData = new ControlData(ctx, R.string.control_shift, new int[]{LWJGLGLFWKeycode.GLFW_KEY_LEFT_SHIFT}, "${margin} * 2 + ${width}", "${screen_height} - ${margin} * 2 - ${height} * 2", true);
ControlData shiftData = new ControlData(ctx, R.string.control_shift, new int[]{LwjglGlfwKeycode.GLFW_KEY_LEFT_SHIFT}, "${margin} * 2 + ${width}", "${screen_height} - ${margin} * 2 - ${height} * 2", true);
shiftData.isToggle = true;
this.mControlDataList.add(shiftData);
this.mControlDataList.add(new ControlData(ctx, R.string.control_jump, new int[]{LWJGLGLFWKeycode.GLFW_KEY_SPACE}, "${right} - ${margin} * 2 - ${width}", "${bottom} - ${margin} * 2 - ${height}", true));
this.mControlDataList.add(new ControlData(ctx, R.string.control_jump, new int[]{LwjglGlfwKeycode.GLFW_KEY_SPACE}, "${right} - ${margin} * 2 - ${width}", "${bottom} - ${margin} * 2 - ${height}", true));
//The default controls are conform to the V2
version = 4;
@ -56,7 +57,7 @@ public class CustomControls {
public void save(String path) throws IOException {
//Current version is the V2.4 so the version as to be marked as 4 !
//Current version is the V2.5 so the version as to be marked as 4 !
version = 4;
Tools.write(path, Tools.GLOBAL_GSON.toJson(this));

View file

@ -2,7 +2,7 @@ package net.kdt.pojavlaunch.customcontrols;
import com.google.gson.JsonSyntaxException;
import net.kdt.pojavlaunch.LWJGLGLFWKeycode;
import net.kdt.pojavlaunch.LwjglGlfwKeycode;
import net.kdt.pojavlaunch.Tools;
import org.json.JSONArray;
@ -83,10 +83,10 @@ public class LayoutConverter {
for(int i = 0; i < layoutMainArray.length(); i++) {
JSONObject button = layoutMainArray.getJSONObject(i);
ControlData n_button = new ControlData();
int[] keycodes = new int[] {LWJGLGLFWKeycode.GLFW_KEY_UNKNOWN,
LWJGLGLFWKeycode.GLFW_KEY_UNKNOWN,
LWJGLGLFWKeycode.GLFW_KEY_UNKNOWN,
LWJGLGLFWKeycode.GLFW_KEY_UNKNOWN};
int[] keycodes = new int[] {LwjglGlfwKeycode.GLFW_KEY_UNKNOWN,
LwjglGlfwKeycode.GLFW_KEY_UNKNOWN,
LwjglGlfwKeycode.GLFW_KEY_UNKNOWN,
LwjglGlfwKeycode.GLFW_KEY_UNKNOWN};
n_button.isDynamicBtn = button.getBoolean("isDynamicBtn");
n_button.dynamicX = button.getString("dynamicX");
n_button.dynamicY = button.getString("dynamicY");
@ -116,9 +116,9 @@ public class LayoutConverter {
}
if(button.getBoolean("isRound")) { n_button.cornerRadius = 35f; }
int next_idx = 0;
if(button.getBoolean("holdShift")) { keycodes[next_idx] = LWJGLGLFWKeycode.GLFW_KEY_LEFT_SHIFT; next_idx++; }
if(button.getBoolean("holdCtrl")) { keycodes[next_idx] = LWJGLGLFWKeycode.GLFW_KEY_LEFT_CONTROL; next_idx++; }
if(button.getBoolean("holdAlt")) { keycodes[next_idx] = LWJGLGLFWKeycode.GLFW_KEY_LEFT_ALT; next_idx++; }
if(button.getBoolean("holdShift")) { keycodes[next_idx] = LwjglGlfwKeycode.GLFW_KEY_LEFT_SHIFT; next_idx++; }
if(button.getBoolean("holdCtrl")) { keycodes[next_idx] = LwjglGlfwKeycode.GLFW_KEY_LEFT_CONTROL; next_idx++; }
if(button.getBoolean("holdAlt")) { keycodes[next_idx] = LwjglGlfwKeycode.GLFW_KEY_LEFT_ALT; next_idx++; }
keycodes[next_idx] = button.getInt("keycode");
n_button.keycodes = keycodes;
empty.mControlDataList.add(n_button);

View file

@ -16,28 +16,28 @@ import net.kdt.pojavlaunch.customcontrols.ControlData;
import net.kdt.pojavlaunch.customcontrols.ControlLayout;
import net.kdt.pojavlaunch.customcontrols.handleview.*;
import net.kdt.pojavlaunch.*;
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
import org.lwjgl.glfw.*;
import static net.kdt.pojavlaunch.BaseMainActivity.sendMouseButton;
import static net.kdt.pojavlaunch.LWJGLGLFWKeycode.GLFW_KEY_UNKNOWN;
import static net.kdt.pojavlaunch.LwjglGlfwKeycode.GLFW_KEY_UNKNOWN;
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_BUTTONSIZE;
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_CONTROL_BOTTOM_OFFSET;
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_CONTROL_LEFT_OFFSET;
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_CONTROL_RIGHT_OFFSET;
import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_CONTROL_TOP_OFFSET;
import static org.lwjgl.glfw.CallbackBridge.sendKeyPress;
import static org.lwjgl.glfw.CallbackBridge.sendMouseButton;
@SuppressLint("ViewConstructor")
public class ControlButton extends androidx.appcompat.widget.AppCompatButton implements OnLongClickListener
{
private final Paint mRectPaint = new Paint();;
public class ControlButton extends androidx.appcompat.widget.AppCompatButton implements OnLongClickListener {
private final Paint mRectPaint = new Paint();
protected GestureDetector mGestureDetector;
protected ControlData mProperties;
protected SelectionEndHandleView mHandleView;
protected boolean mModifiable = false;
protected boolean mCanTriggerLongClick = true;
protected boolean isToggled = false;
protected boolean isPointerOutOfBounds = false;
protected boolean mIsToggled = false;
protected boolean mIsPointerOutOfBounds = false;
public ControlButton(ControlLayout layout, ControlData properties) {
super(layout.getContext());
@ -152,16 +152,57 @@ public class ControlButton extends androidx.appcompat.widget.AppCompatButton imp
@Override
public void setX(float x) {
// We have to account for control offset preference
if(x + (mProperties.getWidth()/2f) > CallbackBridge.physicalWidth/2f){
x -= PREF_CONTROL_RIGHT_OFFSET;
}else{
x += PREF_CONTROL_LEFT_OFFSET;
}
super.setX(x);
setModified(true);
}
@Override
public void setY(float y) {
// We have to account for control offset preference
if(y - PREF_CONTROL_TOP_OFFSET + (mProperties.getHeight()/2f) > CallbackBridge.physicalHeight/2f){
y -= PREF_CONTROL_BOTTOM_OFFSET;
}else{
y += PREF_CONTROL_TOP_OFFSET;
}
super.setY(y);
setModified(true);
}
@Override
public float getX() {
float x = super.getX();
// We have to account for control offset preference
if(x + (mProperties.getWidth()/2f) > (CallbackBridge.physicalWidth)/2f){
x += PREF_CONTROL_RIGHT_OFFSET;
}else{
x -= PREF_CONTROL_LEFT_OFFSET;
}
return x;
}
@Override
public float getY(){
// We have to account for control offset preference
float y = super.getY();
if(y + (mProperties.getHeight()/2f) > CallbackBridge.physicalHeight/2f){
y += PREF_CONTROL_BOTTOM_OFFSET;
}else{
y -= PREF_CONTROL_TOP_OFFSET;
}
return y;
}
/**
* Apply the dynamic equation on the x axis.
* @param dynamicX The equation to compute the position from
@ -213,7 +254,7 @@ public class ControlButton extends androidx.appcompat.widget.AppCompatButton imp
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (isToggled || (!mProperties.isToggle && isActivated()))
if (mIsToggled || (!mProperties.isToggle && isActivated()))
canvas.drawRoundRect(0, 0, getWidth(), getHeight(), mProperties.cornerRadius, mProperties.cornerRadius, mRectPaint);
}
@ -251,33 +292,33 @@ public class ControlButton extends androidx.appcompat.widget.AppCompatButton imp
case MotionEvent.ACTION_MOVE:
//Send the event to be taken as a mouse action
if(mProperties.passThruEnabled && CallbackBridge.isGrabbing()){
MinecraftGLView v = ((ControlLayout) this.getParent()).findViewById(R.id.main_game_render_view);
MinecraftGLSurface v = ((ControlLayout) this.getParent()).findViewById(R.id.main_game_render_view);
if (v != null) v.dispatchTouchEvent(event);
}
//If out of bounds
if(event.getX() < getLeft() || event.getX() > getRight() ||
event.getY() < getTop() || event.getY() > getBottom()){
if(mProperties.isSwipeable && !isPointerOutOfBounds){
if(mProperties.isSwipeable && !mIsPointerOutOfBounds){
//Remove keys
if(!triggerToggle()) {
sendKeyPresses(false);
}
}
isPointerOutOfBounds = true;
mIsPointerOutOfBounds = true;
((ControlLayout) getParent()).onTouch(this, event);
break;
}
//Else if we now are in bounds
if(isPointerOutOfBounds) {
if(mIsPointerOutOfBounds) {
((ControlLayout) getParent()).onTouch(this, event);
//RE-press the button
if(mProperties.isSwipeable && !mProperties.isToggle){
sendKeyPresses(true);
}
}
isPointerOutOfBounds = false;
mIsPointerOutOfBounds = false;
break;
case MotionEvent.ACTION_DOWN: // 0
@ -291,11 +332,11 @@ public class ControlButton extends androidx.appcompat.widget.AppCompatButton imp
case MotionEvent.ACTION_CANCEL: // 3
case MotionEvent.ACTION_POINTER_UP: // 6
if(mProperties.passThruEnabled){
MinecraftGLView v = ((ControlLayout) this.getParent()).findViewById(R.id.main_game_render_view);
MinecraftGLSurface v = ((ControlLayout) this.getParent()).findViewById(R.id.main_game_render_view);
if (v != null) v.dispatchTouchEvent(event);
}
if(isPointerOutOfBounds) ((ControlLayout) getParent()).onTouch(this, event);
isPointerOutOfBounds = false;
if(mIsPointerOutOfBounds) ((ControlLayout) getParent()).onTouch(this, event);
mIsPointerOutOfBounds = false;
if(!triggerToggle()) {
sendKeyPresses(false);
@ -350,7 +391,7 @@ public class ControlButton extends androidx.appcompat.widget.AppCompatButton imp
float MIN_DISTANCE = Tools.dpToPx(8);
if(button == this) return false;
if(com.google.android.material.math.MathUtils.dist(
if(net.kdt.pojavlaunch.utils.MathUtils.dist(
button.getX() + button.getWidth()/2f,
button.getY() + button.getHeight()/2f,
getX() + getWidth()/2f,
@ -455,9 +496,9 @@ public class ControlButton extends androidx.appcompat.widget.AppCompatButton imp
public boolean triggerToggle(){
//returns true a the toggle system is triggered
if(mProperties.isToggle){
isToggled = !isToggled;
mIsToggled = !mIsToggled;
invalidate();
sendKeyPresses(isToggled);
sendKeyPresses(mIsToggled);
return true;
}
return false;
@ -467,7 +508,7 @@ public class ControlButton extends androidx.appcompat.widget.AppCompatButton imp
setActivated(isDown);
for(int keycode : mProperties.keycodes){
if(keycode >= GLFW_KEY_UNKNOWN){
MainActivity.sendKeyPress(keycode, CallbackBridge.getCurrentMods(), isDown);
sendKeyPress(keycode, CallbackBridge.getCurrentMods(), isDown);
CallbackBridge.setModifiers(keycode, isDown);
}else{
sendSpecialKey(keycode, isDown);
@ -490,21 +531,15 @@ public class ControlButton extends androidx.appcompat.widget.AppCompatButton imp
break;
case ControlData.SPECIALBTN_MOUSEPRI:
sendMouseButton(LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_LEFT, isDown);
sendMouseButton(LwjglGlfwKeycode.GLFW_MOUSE_BUTTON_LEFT, isDown);
break;
case ControlData.SPECIALBTN_MOUSEMID:
sendMouseButton(LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_MIDDLE, isDown);
sendMouseButton(LwjglGlfwKeycode.GLFW_MOUSE_BUTTON_MIDDLE, isDown);
break;
case ControlData.SPECIALBTN_MOUSESEC:
if (CallbackBridge.isGrabbing()) {
sendMouseButton(LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_RIGHT, isDown);
} else {
CallbackBridge.putMouseEventWithCoords(LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_RIGHT, isDown , CallbackBridge.mouseX, CallbackBridge.mouseY);
BaseMainActivity.setRightOverride(isDown);
}
sendMouseButton(LwjglGlfwKeycode.GLFW_MOUSE_BUTTON_RIGHT, isDown);
break;
case ControlData.SPECIALBTN_SCROLLDOWN:

View file

@ -2,7 +2,6 @@ package net.kdt.pojavlaunch.customcontrols.buttons;
import android.annotation.SuppressLint;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import net.kdt.pojavlaunch.Tools;
@ -20,7 +19,7 @@ public class ControlDrawer extends ControlButton {
public ArrayList<ControlSubButton> buttons;
public ControlDrawerData drawerData;
public ControlLayout mLayout;
public ControlLayout layout;
public boolean areButtonsVisible;
@ -28,14 +27,14 @@ public class ControlDrawer extends ControlButton {
super(layout, drawerData.properties);
buttons = new ArrayList<>(drawerData.buttonProperties.size());
mLayout = layout;
this.layout = layout;
this.drawerData = drawerData;
areButtonsVisible = layout.getModifiable();
}
public void addButton(ControlData properties){
addButton(new ControlSubButton(mLayout, properties, this));
addButton(new ControlSubButton(layout, properties, this));
}
public void addButton(ControlSubButton button){

View file

@ -1,25 +1,28 @@
package net.kdt.pojavlaunch.customcontrols.gamepad;
import android.os.Handler;
import android.os.Looper;
import android.content.Context;
import android.view.Choreographer;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.Toast;
import androidx.core.content.res.ResourcesCompat;
import androidx.core.math.MathUtils;
import net.kdt.pojavlaunch.BaseMainActivity;
import net.kdt.pojavlaunch.LWJGLGLFWKeycode;
import net.kdt.pojavlaunch.MainActivity;
import net.kdt.pojavlaunch.LwjglGlfwKeycode;
import net.kdt.pojavlaunch.R;
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
import net.kdt.pojavlaunch.utils.MCOptionUtils;
import org.lwjgl.glfw.CallbackBridge;
import static net.kdt.pojavlaunch.Tools.currentDisplayMetrics;
import static net.kdt.pojavlaunch.customcontrols.gamepad.GamepadJoystick.DIRECTION_EAST;
import static net.kdt.pojavlaunch.customcontrols.gamepad.GamepadJoystick.DIRECTION_NONE;
import static net.kdt.pojavlaunch.customcontrols.gamepad.GamepadJoystick.DIRECTION_NORTH;
@ -30,40 +33,69 @@ import static net.kdt.pojavlaunch.customcontrols.gamepad.GamepadJoystick.DIRECTI
import static net.kdt.pojavlaunch.customcontrols.gamepad.GamepadJoystick.DIRECTION_SOUTH_WEST;
import static net.kdt.pojavlaunch.customcontrols.gamepad.GamepadJoystick.DIRECTION_WEST;
import static net.kdt.pojavlaunch.customcontrols.gamepad.GamepadJoystick.isJoystickEvent;
import static net.kdt.pojavlaunch.utils.MCOptionUtils.getMcScale;
import static org.lwjgl.glfw.CallbackBridge.sendKeyPress;
import static org.lwjgl.glfw.CallbackBridge.sendMouseButton;
public class Gamepad {
private final BaseMainActivity gameActivity;
private final ImageView pointerView;
/* Resolution scaler option, allow downsizing a window */
private final float mScaleFactor = LauncherPreferences.DEFAULT_PREF.getInt("resolutionRatio",100)/100f;
/* Mouse positions, scaled by the scaleFactor */
private float mMouse_x, mMouse_y;
/* Sensitivity, adjusted according to screen size */
private final double mSensitivityFactor = (1.4 * (1080f/ currentDisplayMetrics.heightPixels));
private final GamepadDpad gamepadDpad = new GamepadDpad();
private final ImageView mPointerImageView;
private final GamepadJoystick leftJoystick;
private int currentJoystickDirection = DIRECTION_NONE;
private final GamepadDpad mGamepadDpad = new GamepadDpad();
private final GamepadJoystick rightJoystick;
private float lastHorizontalValue = 0.0f;
private float lastVerticalValue = 0.0f;
private final GamepadJoystick mLeftJoystick;
private int mCurrentJoystickDirection = DIRECTION_NONE;
private final double mouseMaxAcceleration = 2f;
private final GamepadJoystick mRightJoystick;
private float mLastHorizontalValue = 0.0f;
private float mLastVerticalValue = 0.0f;
private double mouseMagnitude;
private double mouseAngle;
private double mouseSensitivity = 19;
private final double MOUSE_MAX_ACCELERATION = 2f;
private final GamepadMap gameMap = GamepadMap.getDefaultGameMap();
private final GamepadMap menuMap = GamepadMap.getDefaultMenuMap();
private GamepadMap currentMap = gameMap;
private double mMouseMagnitude;
private double mMouseAngle;
private double mMouseSensitivity = 19;
private boolean lastGrabbingState = true;
private final boolean mModifierDigitalTriggers;
private final GamepadMap mGameMap = GamepadMap.getDefaultGameMap();
private final GamepadMap mMenuMap = GamepadMap.getDefaultMenuMap();
private GamepadMap mCurrentMap = mGameMap;
private boolean mLastGrabbingState = true;
//private final boolean mModifierDigitalTriggers;
private final boolean mModifierAnalogTriggers;
private boolean mModifierSwappedAxis = true; //Triggers and right stick axis are swapped.
private final Handler inputHandler = new Handler(Looper.getMainLooper());
private final Runnable switchStateRunnable;
/* Choreographer with time to compute delta on ticking */
private final Choreographer mScreenChoreographer;
private long mLastFrameTime;
public Gamepad(BaseMainActivity gameActivity, InputDevice inputDevice){
//Toast.makeText(gameActivity.getApplicationContext(),"GAMEPAD CREATED", Toast.LENGTH_LONG).show();
/* Listen for change in gui scale */
private MCOptionUtils.MCOptionListener mGuiScaleListener = () -> notifyGUISizeChange(getMcScale());
public Gamepad(View contextView, InputDevice inputDevice){
mScreenChoreographer = Choreographer.getInstance();
Choreographer.FrameCallback frameCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
updateGrabbingState();
tick(frameTimeNanos);
mScreenChoreographer.postFrameCallback(this);
}
};
mScreenChoreographer.postFrameCallback(frameCallback);
mLastFrameTime = System.nanoTime();
/* Add the listener for the cross hair */
MCOptionUtils.addMCOptionListener(mGuiScaleListener);
Toast.makeText(contextView.getContext(),"GAMEPAD CREATED", Toast.LENGTH_LONG).show();
for(InputDevice.MotionRange range : inputDevice.getMotionRanges()){
if(range.getAxis() == MotionEvent.AXIS_RTRIGGER
|| range.getAxis() == MotionEvent.AXIS_LTRIGGER
@ -72,96 +104,34 @@ public class Gamepad {
mModifierSwappedAxis = false;
break;
}
}
leftJoystick = new GamepadJoystick(MotionEvent.AXIS_X, MotionEvent.AXIS_Y, inputDevice);
mLeftJoystick = new GamepadJoystick(MotionEvent.AXIS_X, MotionEvent.AXIS_Y, inputDevice);
if(!mModifierSwappedAxis)
rightJoystick = new GamepadJoystick(MotionEvent.AXIS_Z, MotionEvent.AXIS_RZ, inputDevice);
mRightJoystick = new GamepadJoystick(MotionEvent.AXIS_Z, MotionEvent.AXIS_RZ, inputDevice);
else
rightJoystick = new GamepadJoystick(MotionEvent.AXIS_RX, MotionEvent.AXIS_RY, inputDevice);
mRightJoystick = new GamepadJoystick(MotionEvent.AXIS_RX, MotionEvent.AXIS_RY, inputDevice);
mModifierDigitalTriggers = inputDevice.hasKeys(KeyEvent.KEYCODE_BUTTON_R2)[0];
//mModifierDigitalTriggers = inputDevice.hasKeys(KeyEvent.KEYCODE_BUTTON_R2)[0];
mModifierAnalogTriggers = supportAnalogTriggers(inputDevice);
this.gameActivity = gameActivity;
pointerView = this.gameActivity.findViewById(R.id.console_pointer);
pointerView.getDrawable().setFilterBitmap(false);
notifyGUISizeChange(gameActivity.getMcScale());
Context ctx = contextView.getContext();
mPointerImageView = new ImageView(contextView.getContext());
mPointerImageView.setImageDrawable(ResourcesCompat.getDrawable(ctx.getResources(), R.drawable.ic_gamepad_pointer, ctx.getTheme()));
mPointerImageView.getDrawable().setFilterBitmap(false);
Runnable handlerRunnable = new Runnable() {
int size = (int) ((22 * getMcScale()) / mScaleFactor);
mPointerImageView.setLayoutParams(new FrameLayout.LayoutParams(size, size));
@Override
public void run() {
updateGrabbingState();
tick();
inputHandler.postDelayed(this, 16);
}
};
inputHandler.postDelayed(handlerRunnable, 16);
//Initialize runnables to be used by the input system, avoiding generating one each time is better memory.
switchStateRunnable = () -> {
currentMap.resetPressedState();
if(lastGrabbingState){
currentMap = gameMap;
pointerView.setVisibility(View.INVISIBLE);
mouseSensitivity = 18;
return;
}
currentMap = menuMap;
sendDirectionalKeycode(currentJoystickDirection, false, gameMap); // removing what we were doing
gameActivity.mouse_x = CallbackBridge.windowWidth/2;
gameActivity.mouse_y = CallbackBridge.windowHeight/2;
CallbackBridge.sendCursorPos(gameActivity.mouse_x, gameActivity.mouse_y);
placePointerView(CallbackBridge.physicalWidth/2, CallbackBridge.physicalHeight/2);
pointerView.setVisibility(View.VISIBLE);
//sensitivity in menu is MC and HARDWARE resolution dependent
mouseSensitivity = 19 * gameActivity.scaleFactor / gameActivity.sensitivityFactor;
};
mMouse_x = CallbackBridge.windowWidth/2;
mMouse_y = CallbackBridge.windowHeight/2;
CallbackBridge.sendCursorPos(mMouse_x, mMouse_y);
placePointerView(CallbackBridge.physicalWidth/2, CallbackBridge.physicalHeight/2);
((ViewGroup)contextView.getParent()).addView(mPointerImageView);
}
private void tick(){
//update mouse position
if(lastHorizontalValue != 0 || lastVerticalValue != 0){
GamepadJoystick currentJoystick = lastGrabbingState ? leftJoystick : rightJoystick;
double acceleration = (mouseMagnitude - currentJoystick.getDeadzone()) / (1 - currentJoystick.getDeadzone());
acceleration = Math.pow(acceleration, mouseMaxAcceleration);
if(acceleration > 1) acceleration = 1;
CallbackBridge.mouseX += Math.cos(mouseAngle) * acceleration * mouseSensitivity;
CallbackBridge.mouseY -= Math.sin(mouseAngle) * acceleration * mouseSensitivity;
if(!lastGrabbingState){
CallbackBridge.mouseX = MathUtils.clamp(CallbackBridge.mouseX, 0, CallbackBridge.windowWidth);
CallbackBridge.mouseY = MathUtils.clamp(CallbackBridge.mouseY, 0, CallbackBridge.windowHeight);
placePointerView((int) (CallbackBridge.mouseX /gameActivity.scaleFactor), (int) (CallbackBridge.mouseY/gameActivity.scaleFactor));
}
gameActivity.mouse_x = CallbackBridge.mouseX;
gameActivity.mouse_y = CallbackBridge.mouseY;
//Send the mouse to the game
CallbackBridge.sendCursorPos(CallbackBridge.mouseX, CallbackBridge.mouseY);
}
}
private void updateGrabbingState() {
boolean lastGrabbingValue = lastGrabbingState;
lastGrabbingState = CallbackBridge.isGrabbing();
if(lastGrabbingValue != lastGrabbingState){
gameActivity.runOnUiThread(switchStateRunnable);
}
}
public void update(KeyEvent event){
sendButton(event);
}
@ -170,32 +140,239 @@ public class Gamepad {
updateDirectionalJoystick(event);
updateMouseJoystick(event);
updateAnalogTriggers(event);
sendButton(gamepadDpad.convertEvent(event));
int[] dpadEvent = mGamepadDpad.convertEvent(event);
sendButton(dpadEvent[0], dpadEvent[1]);
}
private void updateMouseJoystick(MotionEvent event){
GamepadJoystick currentJoystick = lastGrabbingState ? rightJoystick : leftJoystick;
lastHorizontalValue = currentJoystick.getHorizontalAxis(event);
lastVerticalValue = currentJoystick.getVerticalAxis(event);
public void notifyGUISizeChange(int newSize){
//Change the pointer size to match UI
int size = (int) ((22 * newSize) / mScaleFactor);
mPointerImageView.post(() -> mPointerImageView.setLayoutParams(new FrameLayout.LayoutParams(size, size)));
mouseMagnitude = currentJoystick.getMagnitude(event);
mouseAngle = currentJoystick.getAngleRadian(event);
}
private void updateDirectionalJoystick(MotionEvent event){
GamepadJoystick currentJoystick = lastGrabbingState ? leftJoystick : rightJoystick;
public void sendButton(KeyEvent event){
sendButton(event.getKeyCode(), event.getAction());
}
int lastJoystickDirection = currentJoystickDirection;
currentJoystickDirection = currentJoystick.getHeightDirection(event);
public void sendButton(int keycode, int action){
boolean isDown = action == KeyEvent.ACTION_DOWN;
switch (keycode){
case KeyEvent.KEYCODE_BUTTON_A:
getCurrentMap().BUTTON_A.update(isDown);
break;
case KeyEvent.KEYCODE_BUTTON_B:
getCurrentMap().BUTTON_B.update(isDown);
break;
case KeyEvent.KEYCODE_BUTTON_X:
getCurrentMap().BUTTON_X.update(isDown);
break;
case KeyEvent.KEYCODE_BUTTON_Y:
getCurrentMap().BUTTON_Y.update(isDown);
break;
if(currentJoystickDirection != lastJoystickDirection){
sendDirectionalKeycode(lastJoystickDirection, false, getCurrentMap());
sendDirectionalKeycode(currentJoystickDirection, true, getCurrentMap());
//Shoulders
case KeyEvent.KEYCODE_BUTTON_L1:
getCurrentMap().SHOULDER_LEFT.update(isDown);
break;
case KeyEvent.KEYCODE_BUTTON_R1:
getCurrentMap().SHOULDER_RIGHT.update(isDown);
break;
//Triggers
case KeyEvent.KEYCODE_BUTTON_L2:
if(mModifierAnalogTriggers) break;
getCurrentMap().TRIGGER_LEFT.update(isDown);
break;
case KeyEvent.KEYCODE_BUTTON_R2:
if(mModifierAnalogTriggers) break;
getCurrentMap().TRIGGER_RIGHT.update(isDown);
break;
//L3 || R3
case KeyEvent.KEYCODE_BUTTON_THUMBL:
getCurrentMap().THUMBSTICK_LEFT.update(isDown);
break;
case KeyEvent.KEYCODE_BUTTON_THUMBR:
getCurrentMap().THUMBSTICK_RIGHT.update(isDown);
break;
//DPAD
case KeyEvent.KEYCODE_DPAD_UP:
getCurrentMap().DPAD_UP.update(isDown);
break;
case KeyEvent.KEYCODE_DPAD_DOWN:
getCurrentMap().DPAD_DOWN.update(isDown);
break;
case KeyEvent.KEYCODE_DPAD_LEFT:
getCurrentMap().DPAD_LEFT.update(isDown);
break;
case KeyEvent.KEYCODE_DPAD_RIGHT:
getCurrentMap().DPAD_RIGHT.update(isDown);
break;
case KeyEvent.KEYCODE_DPAD_CENTER:
getCurrentMap().DPAD_RIGHT.update(false);
getCurrentMap().DPAD_LEFT.update(false);
getCurrentMap().DPAD_UP.update(false);
getCurrentMap().DPAD_DOWN.update(false);
break;
//Start/select
case KeyEvent.KEYCODE_BUTTON_START:
getCurrentMap().BUTTON_START.update(isDown);
break;
case KeyEvent.KEYCODE_BUTTON_SELECT:
getCurrentMap().BUTTON_SELECT.update(isDown);
break;
default:
sendKeyPress(LwjglGlfwKeycode.GLFW_KEY_SPACE, CallbackBridge.getCurrentMods(), isDown);
break;
}
}
public static void sendInput(int[] keycodes, boolean isDown){
for(int keycode : keycodes){
switch (keycode){
case GamepadMap.MOUSE_SCROLL_DOWN:
if(isDown) CallbackBridge.sendScroll(0, -1);
break;
case GamepadMap.MOUSE_SCROLL_UP:
if(isDown) CallbackBridge.sendScroll(0, 1);
break;
case LwjglGlfwKeycode.GLFW_MOUSE_BUTTON_RIGHT:
sendMouseButton(LwjglGlfwKeycode.GLFW_MOUSE_BUTTON_RIGHT, isDown);
break;
case LwjglGlfwKeycode.GLFW_MOUSE_BUTTON_LEFT:
sendMouseButton(LwjglGlfwKeycode.GLFW_MOUSE_BUTTON_LEFT, isDown);
break;
default:
sendKeyPress(keycode, CallbackBridge.getCurrentMods(), isDown);
break;
}
CallbackBridge.setModifiers(keycode, isDown);
}
}
public static boolean isGamepadEvent(MotionEvent event){
return isJoystickEvent(event);
}
public static boolean isGamepadEvent(KeyEvent event){
return ((event.getDevice().getSources() & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD
&& GamepadDpad.isDpadEvent(event) );
}
/**
* Send the new mouse position, computing the delta
* @param frameTimeNanos The time to render the frame, used to compute mouse delta
*/
private void tick(long frameTimeNanos){
//update mouse position
if(mLastHorizontalValue != 0 || mLastVerticalValue != 0){
GamepadJoystick currentJoystick = mLastGrabbingState ? mLeftJoystick : mRightJoystick;
double acceleration = (mMouseMagnitude - currentJoystick.getDeadzone()) / (1 - currentJoystick.getDeadzone());
acceleration = Math.pow(acceleration, MOUSE_MAX_ACCELERATION);
if(acceleration > 1) acceleration = 1;
// Compute delta since last tick time
float deltaX = (float) (Math.cos(mMouseAngle) * acceleration * mMouseSensitivity);
float deltaY = (float) (Math.sin(mMouseAngle) * acceleration * mMouseSensitivity);
float deltaTimeScale = ((frameTimeNanos - mLastFrameTime) / 16666666f); // Scale of 1 = 60Hz
deltaX *= deltaTimeScale;
deltaY *= deltaTimeScale;
CallbackBridge.mouseX += deltaX;
CallbackBridge.mouseY -= deltaY;
if(!mLastGrabbingState){
CallbackBridge.mouseX = MathUtils.clamp(CallbackBridge.mouseX, 0, CallbackBridge.windowWidth);
CallbackBridge.mouseY = MathUtils.clamp(CallbackBridge.mouseY, 0, CallbackBridge.windowHeight);
placePointerView((int) (CallbackBridge.mouseX / mScaleFactor), (int) (CallbackBridge.mouseY/ mScaleFactor));
}
mMouse_x = CallbackBridge.mouseX;
mMouse_y = CallbackBridge.mouseY;
//Send the mouse to the game
CallbackBridge.sendCursorPos(CallbackBridge.mouseX, CallbackBridge.mouseY);
}
// Update last nano time
mLastFrameTime = frameTimeNanos;
}
/** Update the grabbing state, and change the currentMap, mouse position and sensibility */
private void updateGrabbingState() {
boolean lastGrabbingValue = mLastGrabbingState;
mLastGrabbingState = CallbackBridge.isGrabbing();
if(lastGrabbingValue == mLastGrabbingState) return;
// Switch grabbing state then
mCurrentMap.resetPressedState();
if(mLastGrabbingState){
mCurrentMap = mGameMap;
mPointerImageView.setVisibility(View.INVISIBLE);
mMouseSensitivity = 18;
return;
}
mCurrentMap = mMenuMap;
sendDirectionalKeycode(mCurrentJoystickDirection, false, mGameMap); // removing what we were doing
mMouse_x = CallbackBridge.windowWidth/2;
mMouse_y = CallbackBridge.windowHeight/2;
CallbackBridge.sendCursorPos(mMouse_x, mMouse_y);
placePointerView(CallbackBridge.physicalWidth/2, CallbackBridge.physicalHeight/2);
mPointerImageView.setVisibility(View.VISIBLE);
// Sensitivity in menu is MC and HARDWARE resolution dependent
mMouseSensitivity = 19 * mScaleFactor / mSensitivityFactor;
}
private void updateMouseJoystick(MotionEvent event){
GamepadJoystick currentJoystick = mLastGrabbingState ? mRightJoystick : mLeftJoystick;
float horizontalValue = currentJoystick.getHorizontalAxis(event);
float verticalValue = currentJoystick.getVerticalAxis(event);
if(horizontalValue != mLastHorizontalValue || verticalValue != mLastVerticalValue){
mLastHorizontalValue = horizontalValue;
mLastVerticalValue = verticalValue;
mMouseMagnitude = currentJoystick.getMagnitude(event);
mMouseAngle = currentJoystick.getAngleRadian(event);
tick(System.nanoTime());
return;
}
mLastHorizontalValue = horizontalValue;
mLastVerticalValue = verticalValue;
mMouseMagnitude = currentJoystick.getMagnitude(event);
mMouseAngle = currentJoystick.getAngleRadian(event);
}
private void updateDirectionalJoystick(MotionEvent event){
GamepadJoystick currentJoystick = mLastGrabbingState ? mLeftJoystick : mRightJoystick;
int lastJoystickDirection = mCurrentJoystickDirection;
mCurrentJoystickDirection = currentJoystick.getHeightDirection(event);
if(mCurrentJoystickDirection == lastJoystickDirection) return;
sendDirectionalKeycode(lastJoystickDirection, false, getCurrentMap());
sendDirectionalKeycode(mCurrentJoystickDirection, true, getCurrentMap());
}
private void updateAnalogTriggers(MotionEvent event){
if(!mModifierDigitalTriggers){
if(mModifierAnalogTriggers){
getCurrentMap().TRIGGER_LEFT.update(
(event.getAxisValue(MotionEvent.AXIS_LTRIGGER) > 0.5)
|| (event.getAxisValue(MotionEvent.AXIS_BRAKE) > 0.5)
@ -207,14 +384,27 @@ public class Gamepad {
}
}
public void notifyGUISizeChange(int newSize){
//Change the pointer size to match UI
int size = (int) ((22 * newSize) / gameActivity.scaleFactor);
gameActivity.runOnUiThread(() -> pointerView.setLayoutParams(new FrameLayout.LayoutParams(size, size)));
/**
* Detect if a gamepad supports analog triggers
* @param inputDevice The input device with all the MotionRange
* @return Whether the gamepad supports analog triggers
*/
private boolean supportAnalogTriggers(InputDevice inputDevice){
for(InputDevice.MotionRange motionRange : inputDevice.getMotionRanges()){
int axis = motionRange.getAxis();
if( axis == MotionEvent.AXIS_BRAKE || axis == MotionEvent.AXIS_GAS ||
axis == MotionEvent.AXIS_LTRIGGER || axis == MotionEvent.AXIS_RTRIGGER ||
(mModifierSwappedAxis && axis == MotionEvent.AXIS_Z) ||
(mModifierSwappedAxis && axis == MotionEvent.AXIS_RZ)){
return true;
}
}
return false;
}
private GamepadMap getCurrentMap(){
return currentMap;
return mCurrentMap;
}
private static void sendDirectionalKeycode(int direction, boolean isDown, GamepadMap map){
@ -251,119 +441,7 @@ public class Gamepad {
}
private void placePointerView(int x, int y){
pointerView.setX(x - pointerView.getWidth()/2);
pointerView.setY(y - pointerView.getHeight()/2);
}
public void sendButton(KeyEvent event){
int keycode = event.getKeyCode();
switch (keycode){
case KeyEvent.KEYCODE_BUTTON_A:
getCurrentMap().BUTTON_A.update(event);
break;
case KeyEvent.KEYCODE_BUTTON_B:
getCurrentMap().BUTTON_B.update(event);
break;
case KeyEvent.KEYCODE_BUTTON_X:
getCurrentMap().BUTTON_X.update(event);
break;
case KeyEvent.KEYCODE_BUTTON_Y:
getCurrentMap().BUTTON_Y.update(event);
break;
//Shoulders
case KeyEvent.KEYCODE_BUTTON_L1:
getCurrentMap().SHOULDER_LEFT.update(event);
break;
case KeyEvent.KEYCODE_BUTTON_R1:
getCurrentMap().SHOULDER_RIGHT.update(event);
break;
//Triggers
case KeyEvent.KEYCODE_BUTTON_L2:
getCurrentMap().TRIGGER_LEFT.update(event);
break;
case KeyEvent.KEYCODE_BUTTON_R2:
getCurrentMap().TRIGGER_RIGHT.update(event);
break;
//L3 || R3
case KeyEvent.KEYCODE_BUTTON_THUMBL:
getCurrentMap().THUMBSTICK_LEFT.update(event);
break;
case KeyEvent.KEYCODE_BUTTON_THUMBR:
getCurrentMap().THUMBSTICK_RIGHT.update(event);
break;
//DPAD
case KeyEvent.KEYCODE_DPAD_UP:
getCurrentMap().DPAD_UP.update(event);
break;
case KeyEvent.KEYCODE_DPAD_DOWN:
getCurrentMap().DPAD_DOWN.update(event);
break;
case KeyEvent.KEYCODE_DPAD_LEFT:
getCurrentMap().DPAD_LEFT.update(event);
break;
case KeyEvent.KEYCODE_DPAD_RIGHT:
getCurrentMap().DPAD_RIGHT.update(event);
break;
case KeyEvent.KEYCODE_DPAD_CENTER:
getCurrentMap().DPAD_RIGHT.update(false);
getCurrentMap().DPAD_LEFT.update(false);
getCurrentMap().DPAD_UP.update(false);
getCurrentMap().DPAD_DOWN.update(false);
break;
//Start/select
case KeyEvent.KEYCODE_BUTTON_START:
getCurrentMap().BUTTON_START.update(event);
break;
case KeyEvent.KEYCODE_BUTTON_SELECT:
getCurrentMap().BUTTON_SELECT.update(event);
break;
default:
MainActivity.sendKeyPress(LWJGLGLFWKeycode.GLFW_KEY_SPACE, CallbackBridge.getCurrentMods(), event.getAction() == KeyEvent.ACTION_DOWN);
break;
}
}
public static void sendInput(int[] keycodes, boolean isDown){
for(int keycode : keycodes){
switch (keycode){
case GamepadMap.MOUSE_SCROLL_DOWN:
if(isDown) CallbackBridge.sendScroll(0, -1);
break;
case GamepadMap.MOUSE_SCROLL_UP:
if(isDown) CallbackBridge.sendScroll(0, 1);
break;
case LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_RIGHT:
MainActivity.sendMouseButton(LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_RIGHT, isDown);
break;
case LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_LEFT:
MainActivity.sendMouseButton(LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_LEFT, isDown);
break;
default:
MainActivity.sendKeyPress(keycode, CallbackBridge.getCurrentMods(), isDown);
break;
}
CallbackBridge.setModifiers(keycode, isDown);
}
}
public static boolean isGamepadEvent(MotionEvent event){
return isJoystickEvent(event);
}
public static boolean isGamepadEvent(KeyEvent event){
return ((event.getSource() & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD
|| GamepadDpad.isDpadEvent(event) );
mPointerImageView.setX(x - mPointerImageView.getWidth()/2);
mPointerImageView.setY(y - mPointerImageView.getHeight()/2);
}
}

View file

@ -2,15 +2,15 @@ package net.kdt.pojavlaunch.customcontrols.gamepad;
import android.view.KeyEvent;
/**
* Simple button able to store its state and some properties
*/
public class GamepadButton {
/*
Just a simple button, that auto deal with the great habit from android to just SPAAAMS input events
*/
public int[] keycodes;
public boolean isToggleable = false;
private boolean isDown = false;
private boolean isToggled = false;
private boolean mIsDown = false;
private boolean mIsToggled = false;
public void update(KeyEvent event){
boolean isKeyDown = (event.getAction() == KeyEvent.ACTION_DOWN);
@ -18,29 +18,29 @@ public class GamepadButton {
}
public void update(boolean isKeyDown){
if(isKeyDown != isDown){
isDown = isKeyDown;
if(isKeyDown != mIsDown){
mIsDown = isKeyDown;
if(isToggleable){
if(isKeyDown){
isToggled = !isToggled;
Gamepad.sendInput(keycodes, isToggled);
mIsToggled = !mIsToggled;
Gamepad.sendInput(keycodes, mIsToggled);
}
return;
}
Gamepad.sendInput(keycodes, isDown);
Gamepad.sendInput(keycodes, mIsDown);
}
}
public void resetButtonState(){
if(isDown || isToggled){
if(mIsDown || mIsToggled){
Gamepad.sendInput(keycodes, false);
}
isDown = false;
isToggled = false;
mIsDown = false;
mIsToggled = false;
}
public boolean isDown(){
return isToggleable ? isToggled : isDown;
return isToggleable ? mIsToggled : mIsDown;
}
}

View file

@ -4,43 +4,25 @@ import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
import net.kdt.pojavlaunch.LWJGLGLFWKeycode;
import java.lang.reflect.Field;
import static android.view.InputDevice.KEYBOARD_TYPE_ALPHABETIC;
import static android.view.InputDevice.KEYBOARD_TYPE_NON_ALPHABETIC;
import static android.view.InputDevice.SOURCE_DPAD;
import static android.view.InputDevice.SOURCE_GAMEPAD;
import static android.view.KeyEvent.KEYCODE_DPAD_CENTER;
import static android.view.KeyEvent.KEYCODE_DPAD_DOWN;
import static android.view.KeyEvent.KEYCODE_DPAD_LEFT;
import static android.view.KeyEvent.KEYCODE_DPAD_RIGHT;
import static android.view.KeyEvent.KEYCODE_DPAD_UP;
/*
Reflection is used to avoid memory churning, and only has an negative impact at start
*/
public class GamepadDpad {
private int mLastKeycode = KEYCODE_DPAD_CENTER;
private int lastKeycode = KEYCODE_DPAD_CENTER;
private KeyEvent dummyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, lastKeycode);
private Field eventCodeField;
private Field eventActionField;
{
try {
eventCodeField = dummyEvent.getClass().getDeclaredField("mKeyCode");
eventCodeField.setAccessible(true);
eventActionField = dummyEvent.getClass().getDeclaredField("mAction");
eventActionField.setAccessible(true);
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
public KeyEvent convertEvent(MotionEvent event){
/**
* Convert the event to a 2 int array: keycode and keyAction, similar to a keyEvent
* @param event The motion to convert
* @return int[0] keycode, int[1] keyAction
*/
public int[] convertEvent(MotionEvent event){
// Use the hat axis value to find the D-pad direction
float xaxis = event.getAxisValue(MotionEvent.AXIS_HAT_X);
float yaxis = event.getAxisValue(MotionEvent.AXIS_HAT_Y);
@ -49,44 +31,25 @@ public class GamepadDpad {
// Check if the AXIS_HAT_X value is -1 or 1, and set the D-pad
// LEFT and RIGHT direction accordingly.
if (Float.compare(xaxis, -1.0f) == 0) {
lastKeycode = KEYCODE_DPAD_LEFT;
mLastKeycode = KEYCODE_DPAD_LEFT;
} else if (Float.compare(xaxis, 1.0f) == 0) {
lastKeycode = KEYCODE_DPAD_RIGHT;
mLastKeycode = KEYCODE_DPAD_RIGHT;
}
// Check if the AXIS_HAT_Y value is -1 or 1, and set the D-pad
// UP and DOWN direction accordingly.
else if (Float.compare(yaxis, -1.0f) == 0) {
lastKeycode = KEYCODE_DPAD_UP;
mLastKeycode = KEYCODE_DPAD_UP;
} else if (Float.compare(yaxis, 1.0f) == 0) {
lastKeycode = KEYCODE_DPAD_DOWN;
mLastKeycode = KEYCODE_DPAD_DOWN;
}else {
//No keycode change
action = KeyEvent.ACTION_UP;
}
setDummyEventKeycode(lastKeycode);
setDummyEventAction(action);
dummyEvent.setSource(SOURCE_DPAD);
return dummyEvent;
return new int[]{mLastKeycode, action};
}
private void setDummyEventKeycode(int fakeKeycode){
try {
eventCodeField.setInt(dummyEvent, fakeKeycode);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
private void setDummyEventAction(int action){
try {
eventActionField.setInt(dummyEvent, action);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
public static boolean isDpadEvent(MotionEvent event) {
// Check that input comes from a device with directional pads.
// And... also the joystick since it declares sometimes as a joystick.
@ -94,6 +57,7 @@ public class GamepadDpad {
}
public static boolean isDpadEvent(KeyEvent event){
return ((event.getSource() & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) && (event.getDevice().getKeyboardType() == KEYBOARD_TYPE_NON_ALPHABETIC);
//return ((event.getSource() & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) && (event.getDevice().getKeyboardType() == KEYBOARD_TYPE_NON_ALPHABETIC);
return event.isFromSource(SOURCE_GAMEPAD) && event.getDevice().getKeyboardType() != KEYBOARD_TYPE_ALPHABETIC;
}
}

View file

@ -4,7 +4,7 @@ import android.view.InputDevice;
import android.view.MotionEvent;
import com.google.android.material.math.MathUtils;
import net.kdt.pojavlaunch.utils.MathUtils;
public class GamepadJoystick {
@ -20,17 +20,14 @@ public class GamepadJoystick {
public static final int DIRECTION_SOUTH = 6;
public static final int DIRECTION_SOUTH_EAST = 7;
private final InputDevice device;
private final int verticalAxis;
private final int horizontalAxis;
private final InputDevice mInputDevice;
private final int mVerticalAxis;
private final int mHorizontalAxis;
public GamepadJoystick(int horizontalAxis, int verticalAxis, InputDevice device){
this.verticalAxis = verticalAxis;
this.horizontalAxis = horizontalAxis;
this.device = device;
this.mVerticalAxis = verticalAxis;
this.mHorizontalAxis = horizontalAxis;
this.mInputDevice = device;
}
public double getAngleRadian(MotionEvent event){
@ -48,29 +45,18 @@ public class GamepadJoystick {
}
public double getMagnitude(MotionEvent event){
float x = Math.abs(event.getAxisValue(horizontalAxis));
float y = Math.abs(event.getAxisValue(verticalAxis));
float x = Math.abs(event.getAxisValue(mHorizontalAxis));
float y = Math.abs(event.getAxisValue(mVerticalAxis));
return MathUtils.dist(0,0, x, y);
}
public float getVerticalAxis(MotionEvent event){
return applyDeadzone(event, verticalAxis);
return applyDeadzone(event, mVerticalAxis);
}
public float getHorizontalAxis(MotionEvent event){
return applyDeadzone(event, horizontalAxis);
}
private float applyDeadzone(MotionEvent event, int axis){
//This piece of code also modifies the value
//to make it seem like there was no deadzone in the first place
double magnitude = getMagnitude(event);
float deadzone = getDeadzone();
if (magnitude < deadzone) return 0;
return (float) ( (event.getAxisValue(axis) / magnitude) * ((magnitude - deadzone) / (1 - deadzone)) );
return applyDeadzone(event, mHorizontalAxis);
}
public static boolean isJoystickEvent(MotionEvent event){
@ -91,9 +77,20 @@ public class GamepadJoystick {
*/
public float getDeadzone() {
try{
return Math.max(device.getMotionRange(horizontalAxis).getFlat() * 1.9f, 0.2f);
return Math.max(mInputDevice.getMotionRange(mHorizontalAxis).getFlat() * 1.9f, 0.2f);
}catch (Exception e){
return 0.2f;
}
}
private float applyDeadzone(MotionEvent event, int axis){
//This piece of code also modifies the value
//to make it seem like there was no deadzone in the first place
double magnitude = getMagnitude(event);
float deadzone = getDeadzone();
if (magnitude < deadzone) return 0;
return (float) ( (event.getAxisValue(axis) / magnitude) * ((magnitude - deadzone) / (1 - deadzone)) );
}
}

View file

@ -1,6 +1,6 @@
package net.kdt.pojavlaunch.customcontrols.gamepad;
import net.kdt.pojavlaunch.LWJGLGLFWKeycode;
import net.kdt.pojavlaunch.LwjglGlfwKeycode;
public class GamepadMap {
@ -76,33 +76,33 @@ public class GamepadMap {
public static GamepadMap getDefaultGameMap(){
GamepadMap gameMap = new GamepadMap();
gameMap.BUTTON_A.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_SPACE};
gameMap.BUTTON_B.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_Q};
gameMap.BUTTON_X.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_E};
gameMap.BUTTON_Y.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_F};
gameMap.BUTTON_A.keycodes = new int[]{LwjglGlfwKeycode.GLFW_KEY_SPACE};
gameMap.BUTTON_B.keycodes = new int[]{LwjglGlfwKeycode.GLFW_KEY_Q};
gameMap.BUTTON_X.keycodes = new int[]{LwjglGlfwKeycode.GLFW_KEY_E};
gameMap.BUTTON_Y.keycodes = new int[]{LwjglGlfwKeycode.GLFW_KEY_F};
gameMap.DIRECTION_FORWARD = new int[]{LWJGLGLFWKeycode.GLFW_KEY_W};
gameMap.DIRECTION_BACKWARD = new int[]{LWJGLGLFWKeycode.GLFW_KEY_S};
gameMap.DIRECTION_RIGHT = new int[]{LWJGLGLFWKeycode.GLFW_KEY_D};
gameMap.DIRECTION_LEFT = new int[]{LWJGLGLFWKeycode.GLFW_KEY_A};
gameMap.DIRECTION_FORWARD = new int[]{LwjglGlfwKeycode.GLFW_KEY_W};
gameMap.DIRECTION_BACKWARD = new int[]{LwjglGlfwKeycode.GLFW_KEY_S};
gameMap.DIRECTION_RIGHT = new int[]{LwjglGlfwKeycode.GLFW_KEY_D};
gameMap.DIRECTION_LEFT = new int[]{LwjglGlfwKeycode.GLFW_KEY_A};
gameMap.DPAD_UP.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_LEFT_SHIFT};
gameMap.DPAD_DOWN.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_O}; //For mods ?
gameMap.DPAD_RIGHT.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_K}; //For mods ?
gameMap.DPAD_LEFT.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_J}; //For mods ?
gameMap.DPAD_UP.keycodes = new int[]{LwjglGlfwKeycode.GLFW_KEY_LEFT_SHIFT};
gameMap.DPAD_DOWN.keycodes = new int[]{LwjglGlfwKeycode.GLFW_KEY_O}; //For mods ?
gameMap.DPAD_RIGHT.keycodes = new int[]{LwjglGlfwKeycode.GLFW_KEY_K}; //For mods ?
gameMap.DPAD_LEFT.keycodes = new int[]{LwjglGlfwKeycode.GLFW_KEY_J}; //For mods ?
gameMap.SHOULDER_LEFT.keycodes = new int[]{GamepadMap.MOUSE_SCROLL_UP};
gameMap.SHOULDER_RIGHT.keycodes = new int[]{GamepadMap.MOUSE_SCROLL_DOWN};
gameMap.TRIGGER_LEFT.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_RIGHT};
gameMap.TRIGGER_RIGHT.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_LEFT};
gameMap.TRIGGER_LEFT.keycodes = new int[]{LwjglGlfwKeycode.GLFW_MOUSE_BUTTON_RIGHT};
gameMap.TRIGGER_RIGHT.keycodes = new int[]{LwjglGlfwKeycode.GLFW_MOUSE_BUTTON_LEFT};
gameMap.THUMBSTICK_LEFT.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_LEFT_CONTROL};
gameMap.THUMBSTICK_RIGHT.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_LEFT_SHIFT};
gameMap.THUMBSTICK_LEFT.keycodes = new int[]{LwjglGlfwKeycode.GLFW_KEY_LEFT_CONTROL};
gameMap.THUMBSTICK_RIGHT.keycodes = new int[]{LwjglGlfwKeycode.GLFW_KEY_LEFT_SHIFT};
gameMap.THUMBSTICK_RIGHT.isToggleable = true;
gameMap.BUTTON_START.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_ESCAPE};
gameMap.BUTTON_SELECT.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_TAB};
gameMap.BUTTON_START.keycodes = new int[]{LwjglGlfwKeycode.GLFW_KEY_ESCAPE};
gameMap.BUTTON_SELECT.keycodes = new int[]{LwjglGlfwKeycode.GLFW_KEY_TAB};
return gameMap;
}
@ -113,10 +113,10 @@ public class GamepadMap {
public static GamepadMap getDefaultMenuMap(){
GamepadMap menuMap = new GamepadMap();
menuMap.BUTTON_A.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_LEFT};
menuMap.BUTTON_B.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_ESCAPE};
menuMap.BUTTON_X.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_RIGHT};
menuMap.BUTTON_Y.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_LEFT_SHIFT, LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_RIGHT}; //Oops, doesn't work since left shift isn't properly applied.
menuMap.BUTTON_A.keycodes = new int[]{LwjglGlfwKeycode.GLFW_MOUSE_BUTTON_LEFT};
menuMap.BUTTON_B.keycodes = new int[]{LwjglGlfwKeycode.GLFW_KEY_ESCAPE};
menuMap.BUTTON_X.keycodes = new int[]{LwjglGlfwKeycode.GLFW_MOUSE_BUTTON_RIGHT};
menuMap.BUTTON_Y.keycodes = new int[]{LwjglGlfwKeycode.GLFW_KEY_LEFT_SHIFT, LwjglGlfwKeycode.GLFW_MOUSE_BUTTON_RIGHT}; //Oops, doesn't work since left shift isn't properly applied.
menuMap.DIRECTION_FORWARD = new int[]{GamepadMap.MOUSE_SCROLL_UP, GamepadMap.MOUSE_SCROLL_UP, GamepadMap.MOUSE_SCROLL_UP, GamepadMap.MOUSE_SCROLL_UP, GamepadMap.MOUSE_SCROLL_UP};
menuMap.DIRECTION_BACKWARD = new int[]{GamepadMap.MOUSE_SCROLL_DOWN, GamepadMap.MOUSE_SCROLL_DOWN, GamepadMap.MOUSE_SCROLL_DOWN, GamepadMap.MOUSE_SCROLL_DOWN, GamepadMap.MOUSE_SCROLL_DOWN};
@ -124,9 +124,9 @@ public class GamepadMap {
menuMap.DIRECTION_LEFT = new int[]{};
menuMap.DPAD_UP.keycodes = new int[]{};
menuMap.DPAD_DOWN.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_O}; //For mods ?
menuMap.DPAD_RIGHT.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_K}; //For mods ?
menuMap.DPAD_LEFT.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_J}; //For mods ?
menuMap.DPAD_DOWN.keycodes = new int[]{LwjglGlfwKeycode.GLFW_KEY_O}; //For mods ?
menuMap.DPAD_RIGHT.keycodes = new int[]{LwjglGlfwKeycode.GLFW_KEY_K}; //For mods ?
menuMap.DPAD_LEFT.keycodes = new int[]{LwjglGlfwKeycode.GLFW_KEY_J}; //For mods ?
menuMap.SHOULDER_LEFT.keycodes = new int[]{GamepadMap.MOUSE_SCROLL_UP};
menuMap.SHOULDER_RIGHT.keycodes = new int[]{GamepadMap.MOUSE_SCROLL_DOWN};
@ -137,7 +137,7 @@ public class GamepadMap {
menuMap.THUMBSTICK_LEFT.keycodes = new int[]{};
menuMap.THUMBSTICK_RIGHT.keycodes = new int[]{};
menuMap.BUTTON_START.keycodes = new int[]{LWJGLGLFWKeycode.GLFW_KEY_ESCAPE};
menuMap.BUTTON_START.keycodes = new int[]{LwjglGlfwKeycode.GLFW_KEY_ESCAPE};
menuMap.BUTTON_SELECT.keycodes = new int[]{};
return menuMap;

View file

@ -35,19 +35,17 @@ import net.kdt.pojavlaunch.customcontrols.buttons.ControlSubButton;
import androidx.appcompat.app.*;
import com.rarepebble.colorpicker.ColorPickerView;
public class ActionPopupWindow extends PinnedPopupWindow implements OnClickListener {
private TextView mEditTextView;
private TextView mDeleteTextView;
private TextView mCloneTextView;
private final ControlButton editedButton;
private final ControlButton mEditedButton;
public ActionPopupWindow(HandleView handleView, ControlButton button){
super(handleView);
this.editedButton = button;
this.mEditedButton = button;
}
@Override
@ -97,18 +95,18 @@ public class ActionPopupWindow extends PinnedPopupWindow implements OnClickListe
public void onClick(final View view) {
if (view == mEditTextView) {
if(editedButton instanceof ControlSubButton){
new EditControlSubButtonPopup((ControlSubButton) editedButton);
if(mEditedButton instanceof ControlSubButton){
new EditControlSubButtonPopup((ControlSubButton) mEditedButton);
return;
}
if(editedButton instanceof ControlDrawer){
new EditControlDrawerPopup((ControlDrawer) editedButton);
if(mEditedButton instanceof ControlDrawer){
new EditControlDrawerPopup((ControlDrawer) mEditedButton);
return;
}
if(editedButton instanceof ControlButton){
new EditControlButtonPopup((ControlButton) editedButton);
if(mEditedButton instanceof ControlButton){
new EditControlButtonPopup((ControlButton) mEditedButton);
return;
}
@ -121,18 +119,18 @@ public class ActionPopupWindow extends PinnedPopupWindow implements OnClickListe
alertBuilder.setPositiveButton(R.string.global_remove, (p1, p2) -> {
ControlLayout layout = ((ControlLayout) mHandleView.mView.getParent());
if(editedButton instanceof ControlSubButton){
layout.removeControlSubButton((ControlSubButton) editedButton);
if(mEditedButton instanceof ControlSubButton){
layout.removeControlSubButton((ControlSubButton) mEditedButton);
return;
}
if(editedButton instanceof ControlDrawer){
layout.removeControlDrawer((ControlDrawer) editedButton);
if(mEditedButton instanceof ControlDrawer){
layout.removeControlDrawer((ControlDrawer) mEditedButton);
return;
}
if(editedButton instanceof ControlButton){
layout.removeControlButton((ControlButton) editedButton);
if(mEditedButton instanceof ControlButton){
layout.removeControlButton((ControlButton) mEditedButton);
}
layout.removeControlButton(mHandleView.mView);
@ -140,18 +138,18 @@ public class ActionPopupWindow extends PinnedPopupWindow implements OnClickListe
alertBuilder.setNegativeButton(android.R.string.cancel, null);
alertBuilder.show();
}else if(view == mCloneTextView) {
if(editedButton instanceof ControlDrawer){
ControlDrawerData cloneData = new ControlDrawerData(((ControlDrawer)editedButton).getDrawerData());
if(mEditedButton instanceof ControlDrawer){
ControlDrawerData cloneData = new ControlDrawerData(((ControlDrawer) mEditedButton).getDrawerData());
cloneData.properties.dynamicX = "0.5 * ${screen_width}";
cloneData.properties.dynamicY = "0.5 * ${screen_height}";
((ControlLayout) mHandleView.mView.getParent()).addDrawer(cloneData);
}else if(editedButton instanceof ControlSubButton){
ControlData cloneData = new ControlData(editedButton.getProperties());
}else if(mEditedButton instanceof ControlSubButton){
ControlData cloneData = new ControlData(mEditedButton.getProperties());
cloneData.dynamicX = "0.5 * ${screen_width}";
cloneData.dynamicY = "0.5 * ${screen_height}";
((ControlLayout) mHandleView.mView.getParent()).addSubButton(((ControlSubButton) editedButton).parentDrawer, cloneData);
((ControlLayout) mHandleView.mView.getParent()).addSubButton(((ControlSubButton) mEditedButton).parentDrawer, cloneData);
}else{
ControlData cloneData = new ControlData(editedButton.getProperties());
ControlData cloneData = new ControlData(mEditedButton.getProperties());
cloneData.dynamicX = "0.5 * ${screen_width}";
cloneData.dynamicY = "0.5 * ${screen_height}";
((ControlLayout) mHandleView.mView.getParent()).addControlButton(cloneData);
@ -177,23 +175,6 @@ public class ActionPopupWindow extends PinnedPopupWindow implements OnClickListe
return positionY;
}
public static void showColorPicker(Context ctx,String title, boolean showAlpha, ImageView v){
int startColor = ((ColorDrawable)v.getDrawable()).getColor();
ColorPickerView picker = new ColorPickerView(ctx);
picker.setColor(startColor);
picker.showAlpha(showAlpha);
AlertDialog.Builder dialog = new AlertDialog.Builder(ctx);
dialog.setTitle(title);
dialog.setView(picker);
dialog.setNegativeButton(android.R.string.cancel, null);
dialog.setPositiveButton(android.R.string.ok, (dialogInterface, i) -> v.setImageDrawable(new ColorDrawable(picker.getColor())));
dialog.show();
}
public static void setPercentageText(TextView textView, int progress){
textView.setText(progress + " %");
}

View file

@ -1,6 +1,5 @@
package net.kdt.pojavlaunch.customcontrols.handleview;
import android.app.Dialog;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
@ -8,9 +7,9 @@ import android.view.LayoutInflater;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.Spinner;
import android.widget.TextView;
@ -20,6 +19,7 @@ import androidx.appcompat.app.AlertDialog;
import net.kdt.pojavlaunch.EfficientAndroidLWJGLKeycode;
import net.kdt.pojavlaunch.R;
import net.kdt.pojavlaunch.Tools;
import net.kdt.pojavlaunch.colorselector.ColorSelector;
import net.kdt.pojavlaunch.customcontrols.buttons.ControlButton;
import net.kdt.pojavlaunch.customcontrols.ControlData;
@ -29,104 +29,106 @@ import static net.kdt.pojavlaunch.customcontrols.handleview.ActionPopupWindow.se
public class EditControlButtonPopup {
protected Dialog dialog;
protected View v;
protected AlertDialog.Builder builder;
protected AlertDialog mDialog;
protected View mRootView;
protected AlertDialog.Builder mBuilder;
protected EditText editName;
protected Spinner[] spinnersKeycode;
protected EditText mNameEditText;
protected Spinner[] mKeycodeSpinners;
protected CheckBox checkToggle;
protected CheckBox checkPassThrough;
protected CheckBox checkBoxSwipeable;
protected CheckBox checkDynamicPosition;
protected CheckBox mToggleCheckbox;
protected CheckBox mPassthroughCheckbox;
protected CheckBox mSwipeableCheckbox;
protected CheckBox mDynamicPositionCheckbox;
protected EditText editWidth;
protected EditText editHeight;
protected EditText editDynamicX;
protected EditText editDynamicY;
protected EditText mWidthEditText;
protected EditText mHeightEditText;
protected EditText mDynamicXEditText;
protected EditText mDynamicYEditText;
protected SeekBar seekBarOpacity;
protected SeekBar seekBarCornerRadius;
protected SeekBar seekBarStrokeWidth;
protected SeekBar mOpacitySeekbar;
protected SeekBar mCornerRadiusSeekbar;
protected SeekBar mStrokeWidthSeekbar;
protected ImageButton buttonBackgroundColor;
protected ImageButton buttonStrokeColor;
protected ImageButton mBackgroundColorButton;
protected ImageButton mStrokeColorButton;
protected TextView textOpacity;
protected TextView textCornerRadius;
protected TextView textStrokeWidth;
protected TextView textStrokeColor;
protected TextView mOpacityTextView;
protected TextView mCornerRadiusTextView;
protected TextView mStrokeWidthTextView;
protected TextView mStrokeColorTextView;
protected final ControlButton button;
protected final ControlData properties;
protected ColorSelector mColorSelector;
protected ImageView mEditingView;
protected ArrayAdapter<String> adapter;
protected String[] specialArr;
protected final ControlButton mControlButton;
protected final ControlData mProperties;
protected ArrayAdapter<String> mAdapter;
protected String[] mSpecialArray;
public EditControlButtonPopup(ControlButton button){
this.button = button;
this.properties = button.getProperties();
this.mControlButton = button;
this.mProperties = button.getProperties();
initializeEditDialog(button.getContext());
//Create the finalized dialog
dialog = builder.create();
dialog.setOnShowListener(dialogInterface -> setEditDialogValues());
mDialog = mBuilder.create();
mDialog.setOnShowListener(dialogInterface -> setEditDialogValues());
dialog.show();
mDialog.show();
}
protected void initializeEditDialog(Context ctx){
//Create the editing dialog
LayoutInflater layoutInflater = (LayoutInflater) ctx.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = layoutInflater.inflate(R.layout.control_button_setting,null);
mRootView = layoutInflater.inflate(R.layout.dialog_control_button_setting,null);
builder = new AlertDialog.Builder(ctx);
builder.setTitle(ctx.getResources().getString(R.string.customctrl_edit, properties.name));
builder.setView(v);
mBuilder = new AlertDialog.Builder(ctx);
mBuilder.setTitle(ctx.getResources().getString(R.string.customctrl_edit, mProperties.name));
mBuilder.setView(mRootView);
//Linking a lot of stuff
editName = v.findViewById(R.id.editName_editText);
mNameEditText = mRootView.findViewById(R.id.editName_editText);
spinnersKeycode = new Spinner[]{
v.findViewById(R.id.editMapping_spinner_1),
v.findViewById(R.id.editMapping_spinner_2),
v.findViewById(R.id.editMapping_spinner_3),
v.findViewById(R.id.editMapping_spinner_4)
mKeycodeSpinners = new Spinner[]{
mRootView.findViewById(R.id.editMapping_spinner_1),
mRootView.findViewById(R.id.editMapping_spinner_2),
mRootView.findViewById(R.id.editMapping_spinner_3),
mRootView.findViewById(R.id.editMapping_spinner_4)
};
checkToggle = v.findViewById(R.id.checkboxToggle);
checkPassThrough = v.findViewById(R.id.checkboxPassThrough);
checkBoxSwipeable = v.findViewById(R.id.checkboxSwipeable);
mToggleCheckbox = mRootView.findViewById(R.id.checkboxToggle);
mPassthroughCheckbox = mRootView.findViewById(R.id.checkboxPassThrough);
mSwipeableCheckbox = mRootView.findViewById(R.id.checkboxSwipeable);
editWidth = v.findViewById(R.id.editSize_editTextX);
editHeight = v.findViewById(R.id.editSize_editTextY);
mWidthEditText = mRootView.findViewById(R.id.editSize_editTextX);
mHeightEditText = mRootView.findViewById(R.id.editSize_editTextY);
editDynamicX = v.findViewById(R.id.editDynamicPositionX_editText);
editDynamicY = v.findViewById(R.id.editDynamicPositionY_editText);
mDynamicXEditText = mRootView.findViewById(R.id.editDynamicPositionX_editText);
mDynamicYEditText = mRootView.findViewById(R.id.editDynamicPositionY_editText);
seekBarOpacity = v.findViewById(R.id.editButtonOpacity_seekbar);
seekBarCornerRadius = v.findViewById(R.id.editCornerRadius_seekbar);
seekBarStrokeWidth = v.findViewById(R.id.editStrokeWidth_seekbar);
mOpacitySeekbar = mRootView.findViewById(R.id.editButtonOpacity_seekbar);
mCornerRadiusSeekbar = mRootView.findViewById(R.id.editCornerRadius_seekbar);
mStrokeWidthSeekbar = mRootView.findViewById(R.id.editStrokeWidth_seekbar);
SeekBar.OnSeekBarChangeListener changeListener = new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
if(seekBar.equals(seekBarCornerRadius)) {
setPercentageText(textCornerRadius, i);
if(seekBar.equals(mCornerRadiusSeekbar)) {
setPercentageText(mCornerRadiusTextView, i);
return;
}
if(seekBar.equals(seekBarOpacity)) {
setPercentageText(textOpacity, i);
if(seekBar.equals(mOpacitySeekbar)) {
setPercentageText(mOpacityTextView, i);
return;
}
if(seekBar.equals(seekBarStrokeWidth)) {
setPercentageText(textStrokeWidth, i);
textStrokeColor.setVisibility(i == 0 ? View.GONE : View.VISIBLE);
if(seekBar.equals(mStrokeWidthSeekbar)) {
setPercentageText(mStrokeWidthTextView, i);
mStrokeColorTextView.setVisibility(i == 0 ? View.GONE : View.VISIBLE);
}
}
@ -137,43 +139,45 @@ public class EditControlButtonPopup {
};
//Add listeners, too bad I don't need all the methods
seekBarOpacity.setOnSeekBarChangeListener(changeListener);
seekBarCornerRadius.setOnSeekBarChangeListener(changeListener);
seekBarStrokeWidth.setOnSeekBarChangeListener(changeListener);
mOpacitySeekbar.setOnSeekBarChangeListener(changeListener);
mCornerRadiusSeekbar.setOnSeekBarChangeListener(changeListener);
mStrokeWidthSeekbar.setOnSeekBarChangeListener(changeListener);
buttonBackgroundColor = v.findViewById(R.id.editBackgroundColor_imageButton);
buttonStrokeColor = v.findViewById(R.id.editStrokeColor_imageButton);
mBackgroundColorButton = mRootView.findViewById(R.id.editBackgroundColor_imageButton);
mStrokeColorButton = mRootView.findViewById(R.id.editStrokeColor_imageButton);
textOpacity = v.findViewById(R.id.editButtonOpacity_textView_percent);
textCornerRadius = v.findViewById(R.id.editCornerRadius_textView_percent);
textStrokeWidth = v.findViewById(R.id.editStrokeWidth_textView_percent);
textStrokeColor = v.findViewById(R.id.editStrokeColor_textView);
mOpacityTextView = mRootView.findViewById(R.id.editButtonOpacity_textView_percent);
mCornerRadiusTextView = mRootView.findViewById(R.id.editCornerRadius_textView_percent);
mStrokeWidthTextView = mRootView.findViewById(R.id.editStrokeWidth_textView_percent);
mStrokeColorTextView = mRootView.findViewById(R.id.editStrokeColor_textView);
checkDynamicPosition = v.findViewById(R.id.checkboxDynamicPosition);
checkDynamicPosition.setOnCheckedChangeListener((btn, checked) -> {
editDynamicX.setEnabled(checked);
editDynamicY.setEnabled(checked);
mDynamicPositionCheckbox = mRootView.findViewById(R.id.checkboxDynamicPosition);
mDynamicPositionCheckbox.setOnCheckedChangeListener((btn, checked) -> {
mDynamicXEditText.setEnabled(checked);
mDynamicYEditText.setEnabled(checked);
});
//Initialize adapter for keycodes
adapter = new ArrayAdapter<>(ctx, android.R.layout.simple_spinner_item);
mAdapter = new ArrayAdapter<>(ctx, android.R.layout.simple_spinner_item);
String[] oldSpecialArr = ControlData.buildSpecialButtonArray();
specialArr = new String[oldSpecialArr.length];
for (int i = 0; i < specialArr.length; i++) {
specialArr[i] = "SPECIAL_" + oldSpecialArr[specialArr.length - i - 1];
mSpecialArray = new String[oldSpecialArr.length];
for (int i = 0; i < mSpecialArray.length; i++) {
mSpecialArray[i] = "SPECIAL_" + oldSpecialArr[mSpecialArray.length - i - 1];
}
adapter.addAll(specialArr);
adapter.addAll(EfficientAndroidLWJGLKeycode.generateKeyName());
adapter.setDropDownViewResource(android.R.layout.simple_list_item_single_choice);
mAdapter.addAll(mSpecialArray);
mAdapter.addAll(EfficientAndroidLWJGLKeycode.generateKeyName());
mAdapter.setDropDownViewResource(android.R.layout.simple_list_item_single_choice);
for (Spinner spinner : spinnersKeycode) {
spinner.setAdapter(adapter);
for (Spinner spinner : mKeycodeSpinners) {
spinner.setAdapter(mAdapter);
}
mColorSelector = new ColorSelector(ctx,color -> mEditingView.setImageDrawable(new ColorDrawable(color)));
//Set color imageButton behavior
buttonBackgroundColor.setOnClickListener(view -> ActionPopupWindow.showColorPicker(ctx, "Edit background color", true, buttonBackgroundColor));
buttonStrokeColor.setOnClickListener(view -> ActionPopupWindow.showColorPicker(ctx, "Edit stroke color", false, buttonStrokeColor));
mBackgroundColorButton.setOnClickListener(view -> showColorEditor((ImageView) view));
mStrokeColorButton.setOnClickListener(view -> showColorEditor((ImageView) view));
//Set dialog buttons behavior
@ -186,36 +190,41 @@ public class EditControlButtonPopup {
setupCheckerboards();
}
protected void showColorEditor(ImageView imgView) {
mEditingView = imgView;
mColorSelector.show(((ColorDrawable)(imgView.getDrawable())).getColor());
}
protected void setupDialogButtons(){
//Set dialog buttons behavior
builder.setPositiveButton(android.R.string.ok, (dialogInterface1, i) -> {
if(!hasPropertiesErrors(dialog.getContext())){
mBuilder.setPositiveButton(android.R.string.ok, (dialogInterface1, i) -> {
if(!hasPropertiesErrors(mDialog.getContext())){
saveProperties();
}
});
builder.setNegativeButton(android.R.string.cancel, null);
mBuilder.setNegativeButton(android.R.string.cancel, null);
}
protected void hideUselessViews(){
(v.findViewById(R.id.editOrientation_textView)).setVisibility(View.GONE);
(mRootView.findViewById(R.id.editOrientation_textView)).setVisibility(View.GONE);
(v.findViewById(R.id.editDynamicPositionX_textView)).setVisibility(View.GONE);
(v.findViewById(R.id.editDynamicPositionY_textView)).setVisibility(View.GONE);
editDynamicX.setVisibility(View.GONE);
editDynamicY.setVisibility(View.GONE);
(mRootView.findViewById(R.id.editDynamicPositionX_textView)).setVisibility(View.GONE);
(mRootView.findViewById(R.id.editDynamicPositionY_textView)).setVisibility(View.GONE);
mDynamicXEditText.setVisibility(View.GONE);
mDynamicYEditText.setVisibility(View.GONE);
//Hide the color choice if the width is 0.
textStrokeColor.setVisibility(properties.strokeWidth == 0 ? View.GONE : View.VISIBLE);
mStrokeColorTextView.setVisibility(mProperties.strokeWidth == 0 ? View.GONE : View.VISIBLE);
}
protected void defineDynamicCheckChange(){
checkDynamicPosition.setOnCheckedChangeListener((compoundButton, b) -> {
mDynamicPositionCheckbox.setOnCheckedChangeListener((compoundButton, b) -> {
int visibility = b ? View.VISIBLE : View.GONE;
(v.findViewById(R.id.editDynamicPositionX_textView)).setVisibility(visibility);
(v.findViewById(R.id.editDynamicPositionY_textView)).setVisibility(visibility);
editDynamicX.setVisibility(visibility);
editDynamicY.setVisibility(visibility);
(mRootView.findViewById(R.id.editDynamicPositionX_textView)).setVisibility(visibility);
(mRootView.findViewById(R.id.editDynamicPositionY_textView)).setVisibility(visibility);
mDynamicXEditText.setVisibility(visibility);
mDynamicYEditText.setVisibility(visibility);
});
}
@ -227,65 +236,65 @@ public class EditControlButtonPopup {
.size((int) Tools.dpToPx(20))
.build();
buttonBackgroundColor.setBackground(drawable);
buttonStrokeColor.setBackground(drawable);
mBackgroundColorButton.setBackground(drawable);
mStrokeColorButton.setBackground(drawable);
}
protected void setEditDialogValues(){
editName.setText(properties.name);
mNameEditText.setText(mProperties.name);
checkToggle.setChecked(properties.isToggle);
checkPassThrough.setChecked(properties.passThruEnabled);
checkBoxSwipeable.setChecked(properties.isSwipeable);
mToggleCheckbox.setChecked(mProperties.isToggle);
mPassthroughCheckbox.setChecked(mProperties.passThruEnabled);
mSwipeableCheckbox.setChecked(mProperties.isSwipeable);
editWidth.setText(Float.toString(properties.getWidth()));
editHeight.setText(Float.toString(properties.getHeight()));
mWidthEditText.setText(Float.toString(mProperties.getWidth()));
mHeightEditText.setText(Float.toString(mProperties.getHeight()));
editDynamicX.setEnabled(properties.isDynamicBtn);
editDynamicY.setEnabled(properties.isDynamicBtn);
editDynamicX.setText(properties.dynamicX);
mDynamicXEditText.setEnabled(mProperties.isDynamicBtn);
mDynamicYEditText.setEnabled(mProperties.isDynamicBtn);
mDynamicXEditText.setText(mProperties.dynamicX);
editDynamicY.setText(properties.dynamicY);
mDynamicYEditText.setText(mProperties.dynamicY);
seekBarOpacity.setProgress((int) (properties.opacity*100));
seekBarStrokeWidth.setProgress(properties.strokeWidth);
seekBarCornerRadius.setProgress((int)properties.cornerRadius);
mOpacitySeekbar.setProgress((int) (mProperties.opacity*100));
mStrokeWidthSeekbar.setProgress(mProperties.strokeWidth);
mCornerRadiusSeekbar.setProgress((int) mProperties.cornerRadius);
buttonBackgroundColor.setImageDrawable(new ColorDrawable(properties.bgColor));
buttonStrokeColor.setImageDrawable(new ColorDrawable(properties.strokeColor));
mBackgroundColorButton.setImageDrawable(new ColorDrawable(mProperties.bgColor));
mStrokeColorButton.setImageDrawable(new ColorDrawable(mProperties.strokeColor));
setPercentageText(textCornerRadius,seekBarCornerRadius.getProgress());
setPercentageText(textOpacity,seekBarOpacity.getProgress());
setPercentageText(textStrokeWidth,seekBarStrokeWidth.getProgress());
setPercentageText(mCornerRadiusTextView, mCornerRadiusSeekbar.getProgress());
setPercentageText(mOpacityTextView, mOpacitySeekbar.getProgress());
setPercentageText(mStrokeWidthTextView, mStrokeWidthSeekbar.getProgress());
checkDynamicPosition.setChecked(properties.isDynamicBtn);
mDynamicPositionCheckbox.setChecked(mProperties.isDynamicBtn);
for(int i=0; i< properties.keycodes.length; i++){
if (properties.keycodes[i] < 0) {
spinnersKeycode[i].setSelection(properties.keycodes[i] + specialArr.length);
for(int i = 0; i< mProperties.keycodes.length; i++){
if (mProperties.keycodes[i] < 0) {
mKeycodeSpinners[i].setSelection(mProperties.keycodes[i] + mSpecialArray.length);
} else {
spinnersKeycode[i].setSelection(EfficientAndroidLWJGLKeycode.getIndexByValue(properties.keycodes[i]) + specialArr.length);
mKeycodeSpinners[i].setSelection(EfficientAndroidLWJGLKeycode.getIndexByValue(mProperties.keycodes[i]) + mSpecialArray.length);
}
}
}
protected boolean hasPropertiesErrors(Context ctx){
if (editName.getText().toString().isEmpty()) {
editName.setError(ctx.getResources().getString(R.string.global_error_field_empty));
if (mNameEditText.getText().toString().isEmpty()) {
mNameEditText.setError(ctx.getResources().getString(R.string.global_error_field_empty));
return true;
}
if (properties.isDynamicBtn) {
if (mProperties.isDynamicBtn) {
int errorAt = 0;
try {
properties.insertDynamicPos(editDynamicX.getText().toString());
mProperties.insertDynamicPos(mDynamicXEditText.getText().toString());
errorAt = 1;
properties.insertDynamicPos(editDynamicY.getText().toString());
mProperties.insertDynamicPos(mDynamicYEditText.getText().toString());
} catch (Throwable th) {
(errorAt == 0 ? editDynamicX : editDynamicY).setError(th.getMessage());
(errorAt == 0 ? mDynamicXEditText : mDynamicYEditText).setError(th.getMessage());
return true;
}
@ -296,36 +305,36 @@ public class EditControlButtonPopup {
protected void saveProperties(){
//This method assumes there are no error.
properties.name = editName.getText().toString();
mProperties.name = mNameEditText.getText().toString();
//Keycodes
for(int i=0; i<spinnersKeycode.length; ++i){
if (spinnersKeycode[i].getSelectedItemPosition() < specialArr.length) {
properties.keycodes[i] = spinnersKeycode[i].getSelectedItemPosition() - specialArr.length;
for(int i = 0; i< mKeycodeSpinners.length; ++i){
if (mKeycodeSpinners[i].getSelectedItemPosition() < mSpecialArray.length) {
mProperties.keycodes[i] = mKeycodeSpinners[i].getSelectedItemPosition() - mSpecialArray.length;
} else {
properties.keycodes[i] = EfficientAndroidLWJGLKeycode.getValueByIndex(spinnersKeycode[i].getSelectedItemPosition() - specialArr.length);
mProperties.keycodes[i] = EfficientAndroidLWJGLKeycode.getValueByIndex(mKeycodeSpinners[i].getSelectedItemPosition() - mSpecialArray.length);
}
}
properties.opacity = seekBarOpacity.getProgress()/100f;
properties.strokeWidth = seekBarStrokeWidth.getProgress();
properties.cornerRadius = seekBarCornerRadius.getProgress();
mProperties.opacity = mOpacitySeekbar.getProgress()/100f;
mProperties.strokeWidth = mStrokeWidthSeekbar.getProgress();
mProperties.cornerRadius = mCornerRadiusSeekbar.getProgress();
properties.bgColor = ((ColorDrawable)buttonBackgroundColor.getDrawable()).getColor();
properties.strokeColor = ((ColorDrawable) buttonStrokeColor.getDrawable()).getColor();
mProperties.bgColor = ((ColorDrawable) mBackgroundColorButton.getDrawable()).getColor();
mProperties.strokeColor = ((ColorDrawable) mStrokeColorButton.getDrawable()).getColor();
properties.isToggle = checkToggle.isChecked();
properties.passThruEnabled = checkPassThrough.isChecked();
properties.isSwipeable = checkBoxSwipeable.isChecked();
mProperties.isToggle = mToggleCheckbox.isChecked();
mProperties.passThruEnabled = mPassthroughCheckbox.isChecked();
mProperties.isSwipeable = mSwipeableCheckbox.isChecked();
properties.setWidth(Float.parseFloat(editWidth.getText().toString()));
properties.setHeight(Float.parseFloat(editHeight.getText().toString()));
mProperties.setWidth(Float.parseFloat(mWidthEditText.getText().toString()));
mProperties.setHeight(Float.parseFloat(mHeightEditText.getText().toString()));
properties.isDynamicBtn = checkDynamicPosition.isChecked();
if(!editDynamicX.getText().toString().isEmpty()) properties.dynamicX = editDynamicX.getText().toString();
if(!editDynamicY.getText().toString().isEmpty()) properties.dynamicY = editDynamicY.getText().toString();
mProperties.isDynamicBtn = mDynamicPositionCheckbox.isChecked();
if(!mDynamicXEditText.getText().toString().isEmpty()) mProperties.dynamicX = mDynamicXEditText.getText().toString();
if(!mDynamicYEditText.getText().toString().isEmpty()) mProperties.dynamicY = mDynamicYEditText.getText().toString();
button.updateProperties();
mControlButton.updateProperties();
}
}

View file

@ -5,6 +5,7 @@ import android.content.DialogInterface;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import android.widget.Toast;
import net.kdt.pojavlaunch.R;
import net.kdt.pojavlaunch.customcontrols.ControlData;
@ -13,63 +14,77 @@ import net.kdt.pojavlaunch.customcontrols.ControlDrawerData;
import net.kdt.pojavlaunch.customcontrols.ControlLayout;
public class EditControlDrawerPopup extends EditControlButtonPopup{
private Spinner spinnerOrientation;
private Spinner mOrientationSpinner;
private ControlDrawer drawer;
private ControlDrawerData drawerData;
private final ControlDrawer mDrawer;
private final ControlDrawerData mDrawerData;
public EditControlDrawerPopup(ControlDrawer editedButton) {
super(editedButton);
drawer = editedButton;
drawerData = editedButton.getDrawerData();
mDrawer = editedButton;
mDrawerData = editedButton.getDrawerData();
}
@Override
protected void hideUselessViews() {
(v.findViewById(R.id.editMapping_textView)).setVisibility(View.GONE);
checkPassThrough.setVisibility(View.GONE);
checkToggle.setVisibility(View.GONE);
checkBoxSwipeable.setVisibility(View.GONE);
(mRootView.findViewById(R.id.editMapping_textView)).setVisibility(View.GONE);
mPassthroughCheckbox.setVisibility(View.GONE);
mToggleCheckbox.setVisibility(View.GONE);
mSwipeableCheckbox.setVisibility(View.GONE);
(mRootView.findViewById(R.id.editDynamicPositionX_textView)).setVisibility(View.GONE);
(mRootView.findViewById(R.id.editDynamicPositionY_textView)).setVisibility(View.GONE);
mDynamicXEditText.setVisibility(View.GONE);
mDynamicYEditText.setVisibility(View.GONE);
}
@Override
protected void initializeEditDialog(Context ctx) {
super.initializeEditDialog(ctx);
spinnerOrientation = v.findViewById(R.id.editOrientation_spinner);
mOrientationSpinner = mRootView.findViewById(R.id.editOrientation_spinner);
ArrayAdapter<ControlDrawerData.Orientation> adapter = new ArrayAdapter<>(ctx, android.R.layout.simple_spinner_item);
adapter.addAll(ControlDrawerData.getOrientations());
adapter.setDropDownViewResource(android.R.layout.simple_list_item_single_choice);
spinnerOrientation.setAdapter(adapter);
mOrientationSpinner.setAdapter(adapter);
}
@Override
protected void setEditDialogValues() {
super.setEditDialogValues();
spinnerOrientation.setSelection(ControlDrawerData.orientationToInt(drawerData.orientation));
mOrientationSpinner.setSelection(ControlDrawerData.orientationToInt(mDrawerData.orientation));
//Using the dialog to replace the button behavior allows us not to dismiss the window
mDialog.getButton(DialogInterface.BUTTON_NEUTRAL).setOnClickListener(v -> {
ControlLayout layout = (ControlLayout) mDrawer.getParent();
ControlData controlData = new ControlData(mDrawerData.properties);
controlData.name = "new";
layout.addSubButton(mDrawer, controlData);
Context ctx = mDialog.getContext();
Toast.makeText(ctx, ctx.getString(R.string.customctrl_add_subbutton_message,
mDrawer.getDrawerData().buttonProperties.size()), Toast.LENGTH_SHORT).show();
});
}
@Override
protected void setupDialogButtons() {
super.setupDialogButtons();
builder.setNeutralButton(v.getResources().getString(R.string.customctrl_addsubbutton), (dialogInterface, i) -> {
ControlLayout layout = (ControlLayout) drawer.getParent();
ControlData controlData = new ControlData(drawerData.properties);
controlData.name = "new";
layout.addSubButton(drawer, controlData);
});
mBuilder.setNeutralButton(mRootView.getResources().getString(R.string.customctrl_addsubbutton), null);
}
@Override
protected void saveProperties() {
drawerData.orientation = ControlDrawerData.intToOrientation(spinnerOrientation.getSelectedItemPosition());
mDrawerData.orientation = ControlDrawerData.intToOrientation(mOrientationSpinner.getSelectedItemPosition());
super.saveProperties();
}
}

View file

@ -14,15 +14,15 @@ public class EditControlSubButtonPopup extends EditControlButtonPopup{
@Override
protected void hideUselessViews() {
(v.findViewById(R.id.editSize_textView)).setVisibility(View.GONE);
(v.findViewById(R.id.editOrientation_textView)).setVisibility(View.GONE);
(mRootView.findViewById(R.id.editSize_textView)).setVisibility(View.GONE);
(mRootView.findViewById(R.id.editOrientation_textView)).setVisibility(View.GONE);
checkDynamicPosition.setVisibility(View.GONE);
mDynamicPositionCheckbox.setVisibility(View.GONE);
(v.findViewById(R.id.editDynamicPositionX_textView)).setVisibility(View.GONE);
editDynamicX.setVisibility(View.GONE);
(mRootView.findViewById(R.id.editDynamicPositionX_textView)).setVisibility(View.GONE);
mDynamicXEditText.setVisibility(View.GONE);
(v.findViewById(R.id.editDynamicPositionY_textView)).setVisibility(View.GONE);
editDynamicY.setVisibility(View.GONE);
(mRootView.findViewById(R.id.editDynamicPositionY_textView)).setVisibility(View.GONE);
mDynamicYEditText.setVisibility(View.GONE);
}
}

View file

@ -29,10 +29,8 @@ import net.kdt.pojavlaunch.*;
import net.kdt.pojavlaunch.customcontrols.buttons.ControlButton;
public abstract class HandleView extends View implements ViewPositionListener, View.OnLongClickListener
{
public abstract class HandleView extends View implements ViewPositionListener, View.OnLongClickListener {
protected Drawable mDrawable;
protected Drawable mDrawableLtr;
protected Drawable mDrawableRtl;
private final PopupWindow mContainer;
// Position with respect to the parent TextView
@ -65,12 +63,19 @@ public abstract class HandleView extends View implements ViewPositionListener, V
// int mWindowPosX, mWindowPosY;
private PositionListener mPositionListener;
public PositionListener getPositionListener() {
if (mPositionListener == null) {
mPositionListener = new PositionListener(mView);
}
return mPositionListener;
}
// Touch-up filter: number of previous positions remembered
private static final int HISTORY_SIZE = 5;
private static final int TOUCH_UP_FILTER_DELAY_AFTER = 150;
private static final int TOUCH_UP_FILTER_DELAY_BEFORE = 350;
private final long[] mPreviousOffsetsTimes = new long[HISTORY_SIZE];
private final int[] mPreviousOffsets = new int[HISTORY_SIZE];
private int mPreviousOffsetIndex = 0;
private int mNumberPreviousOffsets = 0;
// Addition
private float mDownX, mDownY;
public HandleView(ControlButton view) {
super(view.getContext());
@ -86,8 +91,7 @@ public abstract class HandleView extends View implements ViewPositionListener, V
mContainer.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL);
mContainer.setContentView(this);
mDrawableLtr = view.getContext().getDrawable(R.drawable.text_select_handle_left_material);
mDrawableRtl = view.getContext().getDrawable(R.drawable.text_select_handle_right_material);
mDrawableRtl = view.getContext().getDrawable(R.drawable.ic_view_handle);
mMinSize = view.getContext().getResources().getDimensionPixelSize(R.dimen.text_handle_min_size);
setOnLongClickListener(this);
@ -99,11 +103,103 @@ public abstract class HandleView extends View implements ViewPositionListener, V
mIdealVerticalOffset = 0.7f * handleHeight;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getPreferredWidth(), getPreferredHeight());
}
@Override
protected void onDraw(Canvas c) {
final int drawWidth = mDrawable.getIntrinsicWidth();
final int left = getHorizontalOffset();
mDrawable.setBounds(left, 0, left + drawWidth, mDrawable.getIntrinsicHeight());
mDrawable.draw(c);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
ViewGroup.LayoutParams params = mView.getLayoutParams();
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
startTouchUpFilter(getCurrentCursorOffset());
mTouchToWindowOffsetX = ev.getRawX() - mPositionX;
mTouchToWindowOffsetY = ev.getRawY() - mPositionY;
final PositionListener positionListener = getPositionListener();
mLastParentX = positionListener.getPositionX();
mLastParentY = positionListener.getPositionY();
mIsDragging = true;
// MOD: Addition
mDownX = ev.getRawX();
mDownY = ev.getRawY();
mDownWidth = params.width;
mDownHeight = params.height;
break;
}
case MotionEvent.ACTION_MOVE: {
final float rawX = ev.getRawX();
final float rawY = ev.getRawY();
// Vertical hysteresis: vertical down movement tends to snap to ideal offset
final float previousVerticalOffset = mTouchToWindowOffsetY - mLastParentY;
final float currentVerticalOffset = rawY - mPositionY - mLastParentY;
float newVerticalOffset;
if (previousVerticalOffset < mIdealVerticalOffset) {
newVerticalOffset = Math.min(currentVerticalOffset, mIdealVerticalOffset);
newVerticalOffset = Math.max(newVerticalOffset, previousVerticalOffset);
} else {
newVerticalOffset = Math.max(currentVerticalOffset, mIdealVerticalOffset);
newVerticalOffset = Math.min(newVerticalOffset, previousVerticalOffset);
}
mTouchToWindowOffsetY = newVerticalOffset + mLastParentY;
final float newPosX = rawX - mTouchToWindowOffsetX + mHotspotX;
final float newPosY = rawY - mTouchToWindowOffsetY + mTouchOffsetY;
int newWidth = (int) (mDownWidth + (rawX - mDownX));
int newHeight = (int) (mDownHeight + (rawY - mDownY));
// mDownX = rawX;
// mDownY = rawY;
params.width = Math.max(50, newWidth);
params.height = Math.max(50, newHeight);
mView.setLayoutParams(params);
updatePosition(newPosX, newPosY);
// break;
return true;
}
case MotionEvent.ACTION_UP:
filterOnTouchUp();
mIsDragging = false;
break;
case MotionEvent.ACTION_CANCEL:
mIsDragging = false;
break;
}
return true;
}
public PositionListener getPositionListener() {
if (mPositionListener == null) {
mPositionListener = new PositionListener(mView);
}
return mPositionListener;
}
protected void updateDrawable() {
// final int offset = getCurrentCursorOffset();
final boolean isRtlCharAtOffset = true; // mView.getLayout().isRtlCharAt(offset);
mDrawable = isRtlCharAtOffset ? mDrawableRtl : mDrawableLtr;
mDrawable = mDrawableRtl; //isRtlCharAtOffset ? mDrawableRtl : mDrawableLtr;
mHotspotX = getHotspotX(mDrawable, isRtlCharAtOffset);
mHorizontalGravity = getHorizontalGravity(isRtlCharAtOffset);
}
@ -111,14 +207,7 @@ public abstract class HandleView extends View implements ViewPositionListener, V
protected abstract int getHotspotX(Drawable drawable, boolean isRtlRun);
protected abstract int getHorizontalGravity(boolean isRtlRun);
// Touch-up filter: number of previous positions remembered
private static final int HISTORY_SIZE = 5;
private static final int TOUCH_UP_FILTER_DELAY_AFTER = 150;
private static final int TOUCH_UP_FILTER_DELAY_BEFORE = 350;
private final long[] mPreviousOffsetsTimes = new long[HISTORY_SIZE];
private final int[] mPreviousOffsets = new int[HISTORY_SIZE];
private int mPreviousOffsetIndex = 0;
private int mNumberPreviousOffsets = 0;
private void startTouchUpFilter(int offset) {
mNumberPreviousOffsets = 0;
@ -152,11 +241,6 @@ public abstract class HandleView extends View implements ViewPositionListener, V
return mNumberPreviousOffsets > 1;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getPreferredWidth(), getPreferredHeight());
}
private int getPreferredWidth() {
return Math.max(mDrawable.getIntrinsicWidth(), mMinSize);
}
@ -283,15 +367,6 @@ public abstract class HandleView extends View implements ViewPositionListener, V
}
}
@Override
protected void onDraw(Canvas c) {
final int drawWidth = mDrawable.getIntrinsicWidth();
final int left = getHorizontalOffset();
mDrawable.setBounds(left, 0, left + drawWidth, mDrawable.getIntrinsicHeight());
mDrawable.draw(c);
}
private int getHorizontalOffset() {
final int width = getPreferredWidth();
final int drawWidth = mDrawable.getIntrinsicWidth();
@ -315,81 +390,6 @@ public abstract class HandleView extends View implements ViewPositionListener, V
return 0;
}
// Addition
private float mDownX, mDownY;
@Override
public boolean onTouchEvent(MotionEvent ev) {
ViewGroup.LayoutParams params = mView.getLayoutParams();
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
startTouchUpFilter(getCurrentCursorOffset());
mTouchToWindowOffsetX = ev.getRawX() - mPositionX;
mTouchToWindowOffsetY = ev.getRawY() - mPositionY;
final PositionListener positionListener = getPositionListener();
mLastParentX = positionListener.getPositionX();
mLastParentY = positionListener.getPositionY();
mIsDragging = true;
// MOD: Addition
mDownX = ev.getRawX();
mDownY = ev.getRawY();
mDownWidth = params.width;
mDownHeight = params.height;
break;
}
case MotionEvent.ACTION_MOVE: {
final float rawX = ev.getRawX();
final float rawY = ev.getRawY();
// Vertical hysteresis: vertical down movement tends to snap to ideal offset
final float previousVerticalOffset = mTouchToWindowOffsetY - mLastParentY;
final float currentVerticalOffset = rawY - mPositionY - mLastParentY;
float newVerticalOffset;
if (previousVerticalOffset < mIdealVerticalOffset) {
newVerticalOffset = Math.min(currentVerticalOffset, mIdealVerticalOffset);
newVerticalOffset = Math.max(newVerticalOffset, previousVerticalOffset);
} else {
newVerticalOffset = Math.max(currentVerticalOffset, mIdealVerticalOffset);
newVerticalOffset = Math.min(newVerticalOffset, previousVerticalOffset);
}
mTouchToWindowOffsetY = newVerticalOffset + mLastParentY;
final float newPosX = rawX - mTouchToWindowOffsetX + mHotspotX;
final float newPosY = rawY - mTouchToWindowOffsetY + mTouchOffsetY;
int newWidth = (int) (mDownWidth + (rawX - mDownX));
int newHeight = (int) (mDownHeight + (rawY - mDownY));
// mDownX = rawX;
// mDownY = rawY;
params.width = Math.max(50, newWidth);
params.height = Math.max(50, newHeight);
mView.setLayoutParams(params);
updatePosition(newPosX, newPosY);
// break;
return true;
}
case MotionEvent.ACTION_UP:
filterOnTouchUp();
mIsDragging = false;
break;
case MotionEvent.ACTION_CANCEL:
mIsDragging = false;
break;
}
return true;
}
public boolean isDragging() {
return mIsDragging;
}

View file

@ -25,8 +25,7 @@ import android.view.*;
import net.kdt.pojavlaunch.customcontrols.buttons.ControlButton;
public class SelectionEndHandleView extends HandleView
{
public class SelectionEndHandleView extends HandleView {
public SelectionEndHandleView(ControlButton view) {
super(view);
}

View file

@ -20,5 +20,5 @@
package net.kdt.pojavlaunch.customcontrols.handleview;
public interface ViewPositionListener {
public void updatePosition(int parentPositionX, int parentPositionY, boolean parentPositionChanged, boolean parentScrolled);
void updatePosition(int parentPositionX, int parentPositionY, boolean parentPositionChanged, boolean parentScrolled);
}

View file

@ -0,0 +1,23 @@
package net.kdt.pojavlaunch.customcontrols.keyboard;
import net.kdt.pojavlaunch.AWTInputBridge;
import net.kdt.pojavlaunch.AWTInputEvent;
/** Send chars via the AWT Bridgee */
public class AwtCharSender implements CharacterSenderStrategy {
@Override
public void sendBackspace() {
AWTInputBridge.sendKey(' ', AWTInputEvent.VK_BACK_SPACE);
}
@Override
public void sendEnter() {
AWTInputBridge.sendKey(' ', AWTInputEvent.VK_ENTER);
}
@Override
public void sendChar(char character) {
AWTInputBridge.sendChar(character);
}
}

View file

@ -0,0 +1,14 @@
package net.kdt.pojavlaunch.customcontrols.keyboard;
/** Simple interface for sending chars through whatever bridge will be necessary */
public interface CharacterSenderStrategy {
/** Called when there is a character to delete, may be called multiple times in a row */
void sendBackspace();
/** Called when we want to send enter specifically */
void sendEnter();
/** Called when there is a character to send, may be called multiple times in a row */
void sendChar(char character);
}

View file

@ -0,0 +1,25 @@
package net.kdt.pojavlaunch.customcontrols.keyboard;
import static org.lwjgl.glfw.CallbackBridge.sendKeyPress;
import net.kdt.pojavlaunch.LwjglGlfwKeycode;
import org.lwjgl.glfw.CallbackBridge;
/** Sends keys via the CallBackBridge */
public class LwjglCharSender implements CharacterSenderStrategy {
@Override
public void sendBackspace() {
CallbackBridge.sendKeycode(LwjglGlfwKeycode.GLFW_KEY_BACKSPACE, '\u0008', 0, 0, true);
}
@Override
public void sendEnter() {
sendKeyPress(LwjglGlfwKeycode.GLFW_KEY_ENTER);
}
@Override
public void sendChar(char character) {
CallbackBridge.sendChar(character, 0);
}
}

View file

@ -1,21 +1,21 @@
package net.kdt.pojavlaunch.customcontrols;
package net.kdt.pojavlaunch.customcontrols.keyboard;
import static android.content.Context.INPUT_METHOD_SERVICE;
import static org.lwjgl.glfw.CallbackBridge.sendKeyPress;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Configuration;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.inputmethod.InputMethodManager;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import net.kdt.pojavlaunch.BaseMainActivity;
import net.kdt.pojavlaunch.LWJGLGLFWKeycode;
import net.kdt.pojavlaunch.LwjglGlfwKeycode;
import net.kdt.pojavlaunch.R;
import org.lwjgl.glfw.CallbackBridge;
@ -36,7 +36,8 @@ public class TouchCharInput extends androidx.appcompat.widget.AppCompatEditText
}
private boolean isDoingInternalChanges = false;
private boolean mIsDoingInternalChanges = false;
private CharacterSenderStrategy mCharacterSender;
/**
* We take the new chars, and send them to the game.
@ -46,14 +47,16 @@ 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)return;
if(mIsDoingInternalChanges)return;
if(mCharacterSender != null){
for(int i=0; i < lengthBefore; ++i){
mCharacterSender.sendBackspace();
}
for(int i=0; i< lengthBefore; ++i){
CallbackBridge.sendKeycode(LWJGLGLFWKeycode.GLFW_KEY_BACKSPACE, '\u0008', 0, 0, true);
}
for(int i=start, count = 0; count < lengthAfter; ++i){
CallbackBridge.sendChar(text.charAt(i), 0);
++count;
for(int i=start, count = 0; count < lengthAfter; ++i){
mCharacterSender.sendChar(text.charAt(i));
++count;
}
}
//Reset the keyboard state
@ -113,25 +116,15 @@ public class TouchCharInput extends androidx.appcompat.widget.AppCompatEditText
*/
@SuppressLint("SetTextI18n")
public void clear(){
isDoingInternalChanges = true;
mIsDoingInternalChanges = true;
//Braille space, doesn't trigger keyboard auto-complete
//replacing directly the text without though setText avoids notifying changes
setText(" ");
setSelection(getText().length());
isDoingInternalChanges = false;
mIsDoingInternalChanges = false;
}
/**
* Send the enter key.
*/
private void sendEnter(){
BaseMainActivity.sendKeyPress(LWJGLGLFWKeycode.GLFW_KEY_ENTER);
clear();
}
/**
* Regain ability to exist, take focus and have some text being input
*/
/** Regain ability to exist, take focus and have some text being input */
public void enable(){
setEnabled(true);
setFocusable(true);
@ -140,9 +133,7 @@ public class TouchCharInput extends androidx.appcompat.widget.AppCompatEditText
}
/**
* Lose ability to exist, take focus and have some text being input
*/
/** Lose ability to exist, take focus and have some text being input */
public void disable(){
clear();
setVisibility(GONE);
@ -150,10 +141,18 @@ public class TouchCharInput extends androidx.appcompat.widget.AppCompatEditText
setEnabled(false);
}
/** Send the enter key. */
private void sendEnter(){
mCharacterSender.sendEnter();
clear();
}
/**
* This function deals with anything that has to be executed when the constructor is called
*/
/** Just sets the char sender that should be used. */
public void setCharacterSender(CharacterSenderStrategy characterSender){
mCharacterSender = characterSender;
}
/** This function deals with anything that has to be executed when the constructor is called */
private void setup(){
setOnEditorActionListener((textView, i, keyEvent) -> {
sendEnter();

View file

@ -0,0 +1,12 @@
package net.kdt.pojavlaunch.extra;
public class ExtraConstants {
/* ExtraCore constant: a HashMap for converting values such as latest-snapshot or latest-release to actual game version names */
public static final String RELEASE_TABLE = "release_table";
/* ExtraCore constant: an ArrayList of Strings, where each String is a Minecraft version name */
public static final String VERSION_LIST= "lac_version_list";
/* ExtraCore constant: Serpent's back button tracking thing */
public static final String BACK_PREFERENCE = "back_preference";
/* ExtraCore constant: The OPENGL version that should be exposed */
public static final String OPEN_GL_VERSION = "open_gl_version";
}

View file

@ -0,0 +1,142 @@
package net.kdt.pojavlaunch.extra;
import java.lang.ref.WeakReference;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
* Class providing callback across all of a program
* to allow easy thread safe implementations of UI update without context leak
* It is also perfectly engineered to make it unpleasant to use.
*
* This class uses a singleton pattern to simplify access to it
*/
public final class ExtraCore {
// No unwanted instantiation
private ExtraCore(){}
// Singleton instance
private static ExtraCore sExtraCoreSingleton = null;
// Store the key-value pair
private final Map<String, Object> mValueMap = new ConcurrentHashMap<>();
// Store what each ExtraListener listen to
private final Map<String, ConcurrentLinkedQueue<WeakReference<ExtraListener>>> mListenerMap = new ConcurrentHashMap<>();
// All public methods will pass through this one
private static ExtraCore getInstance(){
if(sExtraCoreSingleton == null){
synchronized(ExtraCore.class){
if(sExtraCoreSingleton == null){
sExtraCoreSingleton = new ExtraCore();
}
}
}
return sExtraCoreSingleton;
}
/**
* Set the value associated to a key and trigger all listeners
* @param key The key
* @param value The value
*/
public static void setValue(String key, Object value){
if(value == null || key == null) return; // null values create an NPE on insertion
getInstance().mValueMap.put(key, value);
ConcurrentLinkedQueue<WeakReference<ExtraListener>> extraListenerList = getInstance().mListenerMap.get(key);
if(extraListenerList == null) return; //No listeners
for(WeakReference<ExtraListener> listener : extraListenerList){
if(listener.get() == null){
extraListenerList.remove(listener);
continue;
}
//Notify the listener about a state change and remove it if asked for
if(listener.get().onValueSet(key, value)){
ExtraCore.removeExtraListenerFromValue(key, listener.get());
}
}
}
/** @return The value behind the key */
public static Object getValue(String key){
return getInstance().mValueMap.get(key);
}
/** Remove the key and its value from the valueMap */
public static void removeValue(String key){
getInstance().mValueMap.remove(key);
}
/** Remove all values */
public static void removeAllValues(){
getInstance().mValueMap.clear();
}
/**
* Link an ExtraListener to a value
* @param key The value key to look for
* @param listener The ExtraListener to link
*/
public static void addExtraListener(String key, ExtraListener listener){
ConcurrentLinkedQueue<WeakReference<ExtraListener>> listenerList = getInstance().mListenerMap.get(key);
// Look for new sets
if(listenerList == null){
listenerList = new ConcurrentLinkedQueue<>();
getInstance().mListenerMap.put(key, listenerList);
}
// This is kinda naive, I should look for duplicates
listenerList.add(new WeakReference<>(listener));
}
/**
* Unlink an ExtraListener from a value.
* Unlink null references found along the way
* @param key The value key to ignore now
* @param listener The ExtraListener to unlink
*/
public static void removeExtraListenerFromValue(String key, ExtraListener listener){
ConcurrentLinkedQueue<WeakReference<ExtraListener>> listenerList = getInstance().mListenerMap.get(key);
// Look for new sets
if(listenerList == null){
listenerList = new ConcurrentLinkedQueue<>();
getInstance().mListenerMap.put(key, listenerList);
}
// Removes all occurrences of ExtraListener and all null references
for(WeakReference<ExtraListener> listenerWeakReference : listenerList){
ExtraListener actualListener = listenerWeakReference.get();
if(actualListener == null || actualListener == listener){
listenerList.remove(listenerWeakReference);
}
}
}
/**
* Unlink all ExtraListeners from a value
* @param key The key to which ExtraListener are linked
*/
public static void removeAllExtraListenersFromValue(String key){
ConcurrentLinkedQueue<WeakReference<ExtraListener>> listenerList = getInstance().mListenerMap.get(key);
// Look for new sets
if(listenerList == null){
listenerList = new ConcurrentLinkedQueue<>();
getInstance().mListenerMap.put(key, listenerList);
}
listenerList.clear();
}
/**
* Remove all ExtraListeners from listening to any value
*/
public static void removeAllExtraListeners(){
getInstance().mListenerMap.clear();
}
}

View file

@ -0,0 +1,19 @@
package net.kdt.pojavlaunch.extra;
import androidx.annotation.Nullable;
/**
* Listener class for the ExtraCore
* An ExtraListener can listen to a virtually unlimited amount of values
*/
public interface ExtraListener<T> {
/**
* Called upon a new value being set
* @param key The name of the value
* @param value The new value as a string
* @return Whether you consume the Listener (stop listening)
*/
boolean onValueSet(String key, @Nullable T value);
}

View file

@ -2,6 +2,7 @@ package net.kdt.pojavlaunch.fragments;
import android.os.*;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.view.*;
import android.widget.*;
@ -12,34 +13,33 @@ import android.graphics.*;
import androidx.fragment.app.Fragment;
public class ConsoleFragment extends Fragment
{
public TextView consoleView;
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
View view = inflater.inflate(R.layout.lmaintab_consolelog, container, false);
public class ConsoleFragment extends Fragment {
public TextView mConsoleView;
consoleView = (TextView) view.findViewById(R.id.lmaintabconsoleLogTextView);
consoleView.setTypeface(Typeface.MONOSPACE);
consoleView.setHint(this.getText(R.string.main_nolog));
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
View view = inflater.inflate(R.layout.fragment_console_log, container, false);
mConsoleView = (TextView) view.findViewById(R.id.lmaintabconsoleLogTextView);
mConsoleView.setTypeface(Typeface.MONOSPACE);
mConsoleView.setHint(this.getText(R.string.main_nolog));
return view;
}
@Override
public void onResume()
{
public void onResume() {
super.onResume();
consoleView = (TextView) getView().findViewById(R.id.lmaintabconsoleLogTextView);
mConsoleView = (TextView) getView().findViewById(R.id.lmaintabconsoleLogTextView);
}
public void putLog(String str) {
if (consoleView == null) {
consoleView = (TextView) getView().findViewById(R.id.lmaintabconsoleLogTextView);
if (mConsoleView == null) {
mConsoleView = (TextView) getView().findViewById(R.id.lmaintabconsoleLogTextView);
}
consoleView.append(str);
mConsoleView.append(str);
}
}

View file

@ -2,7 +2,10 @@ package net.kdt.pojavlaunch.fragments;
import android.os.*;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.util.Log;
import android.view.*;
import android.widget.*;
import java.io.*;
@ -11,80 +14,77 @@ import net.kdt.pojavlaunch.*;
import android.graphics.*;
import androidx.fragment.app.Fragment;
public class CrashFragment extends Fragment
{
public static String lastCrashFile = Tools.DIR_DATA + "/lastcrash.txt";
private String crashContent;
private TextView crashView;
public boolean resetCrashLog = false;
public static boolean isNewCrash(File crashLog) throws Exception {
String content = Tools.read(crashLog.getAbsolutePath());
return crashLog != null && content.startsWith("---- Minecraft Crash Report ----");
}
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
View view = inflater.inflate(R.layout.lmaintab_crashlog, container, false);
public class CrashFragment extends Fragment {
public static String LAST_CRASH_FILE = Tools.DIR_DATA + "/lastcrash.txt";
return view;
}
@Override
public void onActivityCreated(Bundle b)
{
super.onActivityCreated(b);
crashView = (TextView) getView().findViewById(R.id.lmaintabconsoleLogCrashTextView);
crashView.setTypeface(Typeface.MONOSPACE);
crashView.setHint(this.getText(R.string.main_nocrash));
private TextView mCrashView;
public boolean mResetCrashLog = false;
}
@Override
public void onResume()
{
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
return inflater.inflate(R.layout.fragment_crash_log, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mCrashView = (TextView) view.findViewById(R.id.lmaintabconsoleLogCrashTextView);
mCrashView.setTypeface(Typeface.MONOSPACE);
mCrashView.setHint(this.getText(R.string.main_nocrash));
}
@Override
public void onResume(){
super.onResume();
refreshCrashFile();
}
public void refreshCrashFile()
{
private void refreshCrashFile() {
// Default text
mCrashView.setText("");
setLastCrash("");
if(mResetCrashLog) return;
File crashLog = Tools.lastFileModified(Tools.DIR_HOME_CRASH);
String lastCrash = getLastCrash();
String crashContent;
try {
if(!resetCrashLog){
File crashLog = Tools.lastFileModified(Tools.DIR_HOME_CRASH);
String lastCrash = getLastCrash();
if (isNewCrash(crashLog)) {
crashContent = Tools.read(crashLog.getAbsolutePath());
Tools.write(crashLog.getAbsolutePath(), "\n" + crashContent);
setLastCrash(crashLog.getAbsolutePath());
crashView.setText(crashContent);
} else if(!lastCrash.isEmpty()) {
crashContent = Tools.read(lastCrash);
crashView.setText(crashContent);
} else throw new Exception();
} else throw new Exception();
} catch (Exception e) {
// Can't find crash or no NEW crashes
crashView.setText(""/*Log.getStackTraceString(e)*/);
setLastCrash("");
if (isNewCrash(crashLog)) {
crashContent = Tools.read(crashLog.getAbsolutePath());
Tools.write(crashLog.getAbsolutePath(), "\n" + crashContent);
setLastCrash(crashLog.getAbsolutePath());
mCrashView.setText(crashContent);
} else if(!lastCrash.isEmpty()) {
crashContent = Tools.read(lastCrash);
mCrashView.setText(crashContent);
}
}catch (IOException ioException){
Log.e("CrashFragment", ioException.toString());
}
}
public void setLastCrash(String newValue) {
private static boolean isNewCrash(@Nullable File crashLog) throws IOException {
if(crashLog == null) return false;
String content = Tools.read(crashLog.getAbsolutePath());
return content.startsWith("---- Minecraft Crash Report ----");
}
private void setLastCrash(String newValue) {
try {
Tools.write(lastCrashFile, newValue);
} catch (Throwable th) {
throw new RuntimeException(th);
Tools.write(LAST_CRASH_FILE, newValue);
} catch (IOException ioException) {
throw new RuntimeException(ioException);
}
}
public String getLastCrash() {
private String getLastCrash() {
try {
return Tools.read(lastCrashFile);
} catch (Throwable th) {
return Tools.read(LAST_CRASH_FILE);
} catch (IOException ioException) {
return "";
}
}

View file

@ -4,12 +4,12 @@ import android.content.Intent;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.fragment.app.Fragment;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
@ -25,74 +25,45 @@ import net.kdt.pojavlaunch.prefs.LauncherPreferences;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Locale;
import java.util.Objects;
public class LauncherFragment extends Fragment
{
private WebView webNews;
private View view;
private Thread validUrlSelectorThread;
private String validChangelog = "/changelog.html";
private Handler mainHandler = new Handler(Looper.getMainLooper());
private boolean interruptLoad = false;
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
view = inflater.inflate(R.layout.lmaintab_news, container, false);
return view;
}
public void selectValidUrl() {
String lang = LauncherPreferences.PREF_LANGUAGE;
if(lang.equals("default")) lang = Locale.getDefault().getLanguage();
final String localizedUrl = "/changelog-"+lang+".html";
if(!tryUrl(Tools.URL_HOME+localizedUrl)) return;
else {
mainHandler.post(()->{
interruptLoad = true;
validChangelog = localizedUrl;
webNews.loadUrl(Tools.URL_HOME+validChangelog);
});
}
}
public boolean tryUrl(String url) {
Log.i("ChangelogLocale","Trying localized url: "+url);
try {
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.connect();
Log.i("ChangelogLocale","Code: "+conn.getResponseCode());
return ("" + conn.getResponseCode()).startsWith("2");
}catch (IOException e) {
e.printStackTrace();
return false;
}
}
public class LauncherFragment extends Fragment {
private WebView mNewsWebview;
private View mRootView;
private Thread mValidUrlSelectorThread;
private String mValidChangelog = "/changelog.html";
private boolean mInterruptLoad = false;
@Override
public void onActivityCreated(Bundle p1)
{
super.onActivityCreated(p1);
mainHandler = new Handler(Looper.myLooper());
webNews = (WebView) getView().findViewById(R.id.lmaintabnewsNewsView);
webNews.setWebViewClient(new WebViewClient(){
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
mRootView = inflater.inflate(R.layout.fragment_news, container, false);
return mRootView;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
mNewsWebview = (WebView) getView().findViewById(R.id.lmaintabnewsNewsView);
mNewsWebview.setWebViewClient(new WebViewClient(){
// API < 23
@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
Log.i("WebNews",failingUrl + ": "+description);
if(webNews != null){
if(validUrlSelectorThread.isAlive()) validUrlSelectorThread.interrupt();
removeWebView();
//Change the background to match the other pages.
//We change it only when the webView is removed to avoid huge overdraw.
LauncherFragment.this.view.setBackgroundColor(Color.parseColor("#44000000"));
}
if(mNewsWebview == null) return;
if(mValidUrlSelectorThread.isAlive()) mValidUrlSelectorThread.interrupt();
removeWebView();
//Change the background to match the other pages.
//We change it only when the webView is removed to avoid huge overdraw.
LauncherFragment.this.mRootView.setBackgroundColor(Color.parseColor("#44000000"));
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if(!url.equals(Tools.URL_HOME + validChangelog)){
if(!url.equals(Tools.URL_HOME + mValidChangelog)){
Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
startActivity(i);
return true;
@ -104,17 +75,17 @@ public class LauncherFragment extends Fragment
@Override
public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
Log.i("WebNews",error.getDescription()+"");
if(webNews != null){
if(validUrlSelectorThread.isAlive()) validUrlSelectorThread.interrupt();
removeWebView();
LauncherFragment.this.view.setBackgroundColor(Color.parseColor("#44000000"));
}
if(mNewsWebview == null) return;
if(mValidUrlSelectorThread.isAlive()) mValidUrlSelectorThread.interrupt();
removeWebView();
LauncherFragment.this.mRootView.setBackgroundColor(Color.parseColor("#44000000"));
}
@RequiresApi(23)
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
if(!request.getUrl().toString().equals(Tools.URL_HOME + validChangelog)){
if(!request.getUrl().toString().equals(Tools.URL_HOME + mValidChangelog)){
Intent i = new Intent(Intent.ACTION_VIEW, request.getUrl());
startActivity(i);
return true;
@ -122,36 +93,64 @@ public class LauncherFragment extends Fragment
return false;
}
});
webNews.clearCache(true);
webNews.getSettings().setJavaScriptEnabled(true);
validUrlSelectorThread = new Thread(this::selectValidUrl);
validUrlSelectorThread.start();
if(!interruptLoad)webNews.loadUrl(Tools.URL_HOME + validChangelog);
mNewsWebview.clearCache(true);
mNewsWebview.getSettings().setJavaScriptEnabled(true);
mValidUrlSelectorThread = new Thread(this::selectValidUrl);
mValidUrlSelectorThread.start();
if(!mInterruptLoad) mNewsWebview.loadUrl(Tools.URL_HOME + mValidChangelog);
}
private void selectValidUrl() {
String lang = LauncherPreferences.PREF_LANGUAGE;
if(lang.equals("default")) lang = Locale.getDefault().getLanguage();
final String localizedUrl = "/changelog-" + lang + ".html";
if(!tryUrl(Tools.URL_HOME+localizedUrl)) return;
requireActivity().runOnUiThread(() -> {
mInterruptLoad = true;
mValidChangelog = localizedUrl;
mNewsWebview.loadUrl(Tools.URL_HOME + mValidChangelog);
});
}
private boolean tryUrl(String url) {
Log.i("ChangelogLocale","Trying localized url: "+url);
try {
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.connect();
Log.i("ChangelogLocale","Code: "+conn.getResponseCode());
return ("" + conn.getResponseCode()).startsWith("2");
}catch (IOException e) {
e.printStackTrace();
return false;
}
}
private void removeWebView() {
//Removing the parent which contain the webView crashes the viewPager.
//So I just try to "minimize" its impact on memory instead
webNews.clearHistory();
webNews.clearCache(true);
mNewsWebview.clearHistory();
mNewsWebview.clearCache(true);
// Loading a blank page is optional, but will ensure that the WebView isn't doing anything when you destroy it.
webNews.loadUrl("about:blank");
mNewsWebview.loadUrl("about:blank");
webNews.onPause();
webNews.removeAllViews();
webNews.destroyDrawingCache();
mNewsWebview.onPause();
mNewsWebview.removeAllViews();
mNewsWebview.destroyDrawingCache();
// make sure to call webNews.resumeTimers().
webNews.pauseTimers();
mNewsWebview.pauseTimers();
webNews.setVisibility(View.GONE);
mNewsWebview.setVisibility(View.GONE);
webNews.destroy();
mNewsWebview.destroy();
// Null out the reference so that you don't end up re-using it.
webNews = null;
mNewsWebview = null;
}

Some files were not shown because too many files have changed in this diff Show more