diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 7aeb60f57..9ce4cda62 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -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: diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index bfb049567..d0be8c533 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -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: diff --git a/README.md b/README.md index b049b019f..66a2e0875 100644 --- a/README.md +++ b/README.md @@ -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).
+- [GL4ES](https://github.com/PojavLauncherTeam/gl4es): [MIT License](https://github.com/ptitSeb/gl4es/blob/master/LICENSE).
- [OpenJDK](https://github.com/PojavLauncherTeam/openjdk-multiarch-jdk8u): [GNU GPLv2 License](https://openjdk.java.net/legal/gplv2+ce.html).
- [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.
+- [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. diff --git a/app_pojavlauncher/build.gradle b/app_pojavlauncher/build.gradle index c36dda831..eb37b29f5 100644 --- a/app_pojavlauncher/build.gradle +++ b/app_pojavlauncher/build.gradle @@ -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']) } diff --git a/app_pojavlauncher/proguard-rules.pro b/app_pojavlauncher/proguard-rules.pro index 1359ec662..c8a5fcc87 100644 --- a/app_pojavlauncher/proguard-rules.pro +++ b/app_pojavlauncher/proguard-rules.pro @@ -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** {*;} + + diff --git a/app_pojavlauncher/src/main/AndroidManifest.xml b/app_pojavlauncher/src/main/AndroidManifest.xml index 4d5134275..143d4e014 100644 --- a/app_pojavlauncher/src/main/AndroidManifest.xml +++ b/app_pojavlauncher/src/main/AndroidManifest.xml @@ -10,6 +10,7 @@ android:maxSdkVersion="28"/> + @@ -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"/> Population of the tabs to display is - * done through {@link Tab} instances. You create tabs via {@link #newTab()}. From there you can - * change the tab's label or icon via {@link Tab#setText(int)} and {@link Tab#setIcon(int)} - * respectively. To display the tab, you need to add it to the layout via one of the - * {@link #addTab(Tab)} methods. For example: - *
- * VerticalTabLayout tabLayout = ...;
- * tabLayout.addTab(tabLayout.newTab().setText("Tab 1"));
- * tabLayout.addTab(tabLayout.newTab().setText("Tab 2"));
- * tabLayout.addTab(tabLayout.newTab().setText("Tab 3"));
- * 
- * You should set a listener via {@link #setOnTabSelectedListener(OnTabSelectedListener)} to be - * notified when any tab's selection state has been changed. - * - *

You can also add items to VerticalTabLayout in your layout through the use of {@link TabItem}. - * An example usage is like so:

- * - *
- * <android.support.design.widget.TabLayout
- *         android:layout_height="wrap_content"
- *         android:layout_width="match_parent">
- *
- *     <android.support.design.widget.TabItem
- *             android:text="@string/tab_text"/>
- *
- *     <android.support.design.widget.TabItem
- *             android:icon="@drawable/ic_android"/>
- *
- * </android.support.design.widget.TabLayout>
- * 
- * - *

ViewPager integration

- *

- * If you're using a {@link ViewPager} together - * with this layout, you can call {@link #setupWithViewPager(ViewPager)} to link the two together. - * This layout will be automatically populated from the {@link PagerAdapter}'s page titles.

- * - *

- * This view also supports being used as part of a ViewPager's decor, and can be added - * directly to the ViewPager in a layout resource file like so:

- * - *
- * <android.support.v4.view.ViewPager
- *     android:layout_width="match_parent"
- *     android:layout_height="match_parent">
- *
- *     <android.support.design.widget.TabLayout
- *         android:layout_width="match_parent"
- *         android:layout_height="wrap_content"
- *         android:layout_gravity="top" />
- *
- * </android.support.v4.view.ViewPager>
- * 
- * - * @see Tabs - * - * @attr ref android.support.design.R.styleable#TabLayout_tabPadding - * @attr ref android.support.design.R.styleable#TabLayout_tabPaddingStart - * @attr ref android.support.design.R.styleable#TabLayout_tabPaddingTop - * @attr ref android.support.design.R.styleable#TabLayout_tabPaddingEnd - * @attr ref android.support.design.R.styleable#TabLayout_tabPaddingBottom - * @attr ref android.support.design.R.styleable#TabLayout_tabContentStart - * @attr ref android.support.design.R.styleable#TabLayout_tabBackground - * @attr ref android.support.design.R.styleable#TabLayout_tabMinWidth - * @attr ref android.support.design.R.styleable#TabLayout_tabMaxWidth - * @attr ref android.support.design.R.styleable#TabLayout_tabTextAppearance - */ -@ViewPager.DecorView -public class VerticalTabLayout extends LinearLayout { - - private static final int DEFAULT_HEIGHT_WITH_TEXT_ICON = 72; // dps - static final int DEFAULT_GAP_TEXT_ICON = 8; // dps - private static final int INVALID_WIDTH = -1; - private static final int DEFAULT_HEIGHT = 48; // dps - private static final int TAB_MIN_WIDTH_MARGIN = 56; //dps - static final int FIXED_WRAP_GUTTER_MIN = 16; //dps - static final int MOTION_NON_ADJACENT_OFFSET = 24; - - private static final int ANIMATION_DURATION = 300; - - private static final Pools.Pool sTabPool = new Pools.SynchronizedPool<>(16); - - /** - * Scrollable tabs display a subset of tabs at any given moment, and can contain longer tab - * labels and a larger number of tabs. They are best used for browsing contexts in touch - * interfaces when users don’t need to directly compare the tab labels. - * - * @see #setTabMode(int) - * @see #getTabMode() - */ - public static final int MODE_SCROLLABLE = 0; - - /** - * Fixed tabs display all tabs concurrently and are best used with content that benefits from - * quick pivots between tabs. The maximum number of tabs is limited by the view’s width. - * Fixed tabs have equal width, based on the widest tab label. - * - * @see #setTabMode(int) - * @see #getTabMode() - */ - public static final int MODE_FIXED = 1; - - /** - * @hide - */ - @RestrictTo(LIBRARY_GROUP) - @IntDef(value = {MODE_SCROLLABLE, MODE_FIXED}) - @Retention(RetentionPolicy.SOURCE) - public @interface Mode {} - - /** - * Gravity used to fill the {@link TabLayout} as much as possible. This option only takes effect - * when used with {@link #MODE_FIXED}. - * - * @see #setTabGravity(int) - * @see #getTabGravity() - */ - public static final int GRAVITY_FILL = 0; - - /** - * Gravity used to lay out the tabs in the center of the {@link TabLayout}. - * - * @see #setTabGravity(int) - * @see #getTabGravity() - */ - public static final int GRAVITY_CENTER = 1; - - /** - * @hide - */ - @RestrictTo(LIBRARY_GROUP) - @IntDef(flag = true, value = {GRAVITY_FILL, GRAVITY_CENTER}) - @Retention(RetentionPolicy.SOURCE) - public @interface TabGravity {} - - /** - * Callback interface invoked when a tab's selection state changes. - */ - public interface OnTabSelectedListener { - - /** - * Called when a tab enters the selected state. - * - * @param tab The tab that was selected - */ - public void onTabSelected(Tab tab); - - /** - * Called when a tab exits the selected state. - * - * @param tab The tab that was unselected - */ - public void onTabUnselected(Tab tab); - - /** - * Called when a tab that is already selected is chosen again by the user. Some applications - * may use this action to return to the top level of a category. - * - * @param tab The tab that was reselected. - */ - public void onTabReselected(Tab tab); - } - - private final ArrayList mTabs = new ArrayList<>(); - private Tab mSelectedTab; - - private final ScrollView mTopScrollView; - private final SlidingTabStrip mTabStrip; - - int mTabPaddingStart; - int mTabPaddingTop; - int mTabPaddingEnd; - int mTabPaddingBottom; - - int mTabTextAppearance; - ColorStateList mTabTextColors; - float mTabTextSize; - float mTabTextMultiLineSize; - - final int mTabBackgroundResId; - - int mTabMaxWidth = Integer.MAX_VALUE; - private final int mRequestedTabMinWidth; - private final int mRequestedTabMaxWidth; - private final int mScrollableTabMinWidth; - - private int mContentInsetStart; - - int mTabGravity; - int mMode; - - private OnTabSelectedListener mSelectedListener; - private final ArrayList mSelectedListeners = new ArrayList<>(); - private OnTabSelectedListener mCurrentVpSelectedListener; - - private ValueAnimator mScrollAnimator; - - ViewPager mViewPager; - private ViewPagerAdapter mPagerAdapter; - private DataSetObserver mPagerAdapterObserver; - private VerticalTabLayoutOnPageChangeListener mPageChangeListener; - private AdapterChangeListener mAdapterChangeListener; - private boolean mSetupViewPagerImplicitly; - - // Pool we use as a simple RecyclerBin - private final Pools.Pool mTabViewPool = new Pools.SimplePool<>(12); - - public VerticalTabLayout(Context context) { - this(context, null); - } - - public VerticalTabLayout(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public VerticalTabLayout(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - - //ndroidx.appcompat.widget.ThemeUtils.checkAppCompatTheme(context); - - setOrientation(VERTICAL); - - // Disable the Scroll Bar - // setVerticalScrollBarEnabled(false); - - LayoutParams scrollViewParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); - scrollViewParams.weight = 1f; - mTopScrollView = new ScrollView(context); - mTopScrollView.setLayoutParams(scrollViewParams); - - // Add the TabStrip - mTabStrip = new SlidingTabStrip(context); - mTopScrollView.addView(mTabStrip, 0, new ScrollView.LayoutParams( - LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT)); - super.addView(mTopScrollView); - - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabLayout, - defStyleAttr, R.style.Widget_Design_TabLayout); - - mTabStrip.setSelectedIndicatorHeight( - a.getDimensionPixelSize(R.styleable.TabLayout_tabIndicatorHeight, 0)); - mTabStrip.setSelectedIndicatorColor(a.getColor(R.styleable.TabLayout_tabIndicatorColor, 0)); - - mTabPaddingStart = mTabPaddingTop = mTabPaddingEnd = mTabPaddingBottom = a - .getDimensionPixelSize(R.styleable.TabLayout_tabPadding, 0); - mTabPaddingStart = a.getDimensionPixelSize(R.styleable.TabLayout_tabPaddingStart, - mTabPaddingStart); - mTabPaddingTop = a.getDimensionPixelSize(R.styleable.TabLayout_tabPaddingTop, - mTabPaddingTop); - mTabPaddingEnd = a.getDimensionPixelSize(R.styleable.TabLayout_tabPaddingEnd, - mTabPaddingEnd); - mTabPaddingBottom = a.getDimensionPixelSize(R.styleable.TabLayout_tabPaddingBottom, - mTabPaddingBottom); - - mTabTextAppearance = a.getResourceId(R.styleable.TabLayout_tabTextAppearance, - R.style.TextAppearance_Design_Tab); - - // Text colors/sizes come from the text appearance first - final TypedArray ta = context.obtainStyledAttributes(mTabTextAppearance, - androidx.appcompat.R.styleable.TextAppearance); - try { - mTabTextSize = ta.getDimensionPixelSize( - androidx.appcompat.R.styleable.TextAppearance_android_textSize, 0); - mTabTextColors = ta.getColorStateList( - androidx.appcompat.R.styleable.TextAppearance_android_textColor); - } finally { - ta.recycle(); - } - - if (a.hasValue(R.styleable.TabLayout_tabTextColor)) { - // If we have an explicit text color set, use it instead - mTabTextColors = a.getColorStateList(R.styleable.TabLayout_tabTextColor); - } - - if (a.hasValue(R.styleable.TabLayout_tabSelectedTextColor)) { - // We have an explicit selected text color set, so we need to make merge it with the - // current colors. This is exposed so that developers can use theme attributes to set - // this (theme attrs in ColorStateLists are Lollipop+) - final int selected = a.getColor(R.styleable.TabLayout_tabSelectedTextColor, 0); - mTabTextColors = createColorStateList(mTabTextColors.getDefaultColor(), selected); - } - - mRequestedTabMinWidth = a.getDimensionPixelSize(R.styleable.TabLayout_tabMinWidth, - INVALID_WIDTH); - mRequestedTabMaxWidth = a.getDimensionPixelSize(R.styleable.TabLayout_tabMaxWidth, - INVALID_WIDTH); - mTabBackgroundResId = a.getResourceId(R.styleable.TabLayout_tabBackground, 0); - mContentInsetStart = a.getDimensionPixelSize(R.styleable.TabLayout_tabContentStart, 0); - mMode = a.getInt(R.styleable.TabLayout_tabMode, MODE_FIXED); - mTabGravity = a.getInt(R.styleable.TabLayout_tabGravity, GRAVITY_FILL); - a.recycle(); - - // TODO add attr for these - final Resources res = getResources(); - mTabTextMultiLineSize = res.getDimensionPixelSize(R.dimen.design_tab_text_size_2line); - mScrollableTabMinWidth = res.getDimensionPixelSize(R.dimen.design_tab_scrollable_min_width); - - // Now apply the tab mode and gravity - applyModeAndGravity(); - } - - /** - * Sets the tab indicator's color for the currently selected tab. - * - * @param color color to use for the indicator - * - * @attr ref android.support.design.R.styleable#TabLayout_tabIndicatorColor - */ - public void setSelectedTabIndicatorColor(@ColorInt int color) { - mTabStrip.setSelectedIndicatorColor(color); - } - - /** - * Sets the tab indicator's height for the currently selected tab. - * - * @param height height to use for the indicator in pixels - * - * @attr ref android.support.design.R.styleable#TabLayout_tabIndicatorHeight - */ - public void setSelectedTabIndicatorHeight(int height) { - mTabStrip.setSelectedIndicatorHeight(height); - } - - /** - * Set the scroll position of the tabs. This is useful for when the tabs are being displayed as - * part of a scrolling container such as {@link ViewPager}. - *

- * Calling this method does not update the selected tab, it is only used for drawing purposes. - * - * @param position current scroll position - * @param positionOffset Value from [0, 1) indicating the offset from {@code position}. - * @param updateSelectedText Whether to update the text's selected state. - */ - public void setScrollPosition(int position, float positionOffset, boolean updateSelectedText) { - setScrollPosition(position, positionOffset, updateSelectedText, true); - } - - void setScrollPosition(int position, float positionOffset, boolean updateSelectedText, - boolean updateIndicatorPosition) { - final int roundedPosition = Math.round(position + positionOffset); - if (roundedPosition < 0 || roundedPosition >= mTabStrip.getChildCount()) { - return; - } - - // Set the indicator position, if enabled - if (updateIndicatorPosition) { - mTabStrip.setIndicatorPositionFromTabPosition(position, positionOffset); - } - - // Now update the scroll position, canceling any running animation - if (mScrollAnimator != null && mScrollAnimator.isRunning()) { - mScrollAnimator.cancel(); - } - mTopScrollView.scrollTo(calculateScrollXForTab(position, positionOffset), 0); - - // Update the 'selected state' view as we scroll, if enabled - if (updateSelectedText) { - setSelectedTabView(roundedPosition); - } - } - - private float getScrollPosition() { - return mTabStrip.getIndicatorPosition(); - } - - public void setLastTabAsBottom() { - final int position = mTabs.size() - 1; - final int selectedTabPosition = mSelectedTab != null ? mSelectedTab.getPosition() : 0; - final TabView view = (TabView) mTabStrip.getChildAt(position); - view.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); - mTabStrip.removeViewAt(position); - requestLayout(); - - mTabs.remove(position); - super.addView(view); - - final int newTabCount = mTabs.size(); - for (int i = position; i < newTabCount; i++) { - mTabs.get(i).setPosition(i); - } - - if (selectedTabPosition == position) { - selectTab(mTabs.isEmpty() ? null : mTabs.get(Math.max(0, position - 1))); - } - } - - /** - * Add a tab to this layout. The tab will be added at the end of the list. - * If this is the first tab to be added it will become the selected tab. - * - * @param tab Tab to add - */ - public void addTab(@NonNull Tab tab) { - addTab(tab, mTabs.isEmpty()); - } - - /** - * Add a tab to this layout. The tab will be inserted at position. - * If this is the first tab to be added it will become the selected tab. - * - * @param tab The tab to add - * @param position The new position of the tab - */ - public void addTab(@NonNull Tab tab, int position) { - addTab(tab, position, mTabs.isEmpty()); - } - - /** - * Add a tab to this layout. The tab will be added at the end of the list. - * - * @param tab Tab to add - * @param setSelected True if the added tab should become the selected tab. - */ - public void addTab(@NonNull Tab tab, boolean setSelected) { - addTab(tab, mTabs.size(), setSelected); - } - - /** - * Add a tab to this layout. The tab will be inserted at position. - * - * @param tab The tab to add - * @param position The new position of the tab - * @param setSelected True if the added tab should become the selected tab. - */ - public void addTab(@NonNull Tab tab, int position, boolean setSelected) { - if (tab.mParent != this) { - throw new IllegalArgumentException("Tab belongs to a different TabLayout."); - } - configureTab(tab, position); - addTabView(tab); - - if (setSelected) { - tab.select(); - } - } - - private void addTabFromItemView(@NonNull TabItem item) { - - final Tab tab = newTab(); - if (item.text != null) { - tab.setText(item.text); - } - if (item.icon != null) { - tab.setIcon(item.icon); - } - if (item.customLayout != 0) { - tab.setCustomView(item.customLayout); - } - if (!TextUtils.isEmpty(item.getContentDescription())) { - tab.setContentDescription(item.getContentDescription()); - } - addTab(tab); - } - - /** - * @deprecated Use {@link #addOnTabSelectedListener(OnTabSelectedListener)} and - * {@link #removeOnTabSelectedListener(OnTabSelectedListener)}. - */ - @Deprecated - public void setOnTabSelectedListener(@Nullable OnTabSelectedListener listener) { - // The logic in this method emulates what we had before support for multiple - // registered listeners. - if (mSelectedListener != null) { - removeOnTabSelectedListener(mSelectedListener); - } - // Update the deprecated field so that we can remove the passed listener the next - // time we're called - mSelectedListener = listener; - if (listener != null) { - addOnTabSelectedListener(listener); - } - } - - /** - * Add a {@link TabLayout.OnTabSelectedListener} that will be invoked when tab selection - * changes. - * - *

Components that add a listener should take care to remove it when finished via - * {@link #removeOnTabSelectedListener(OnTabSelectedListener)}.

- * - * @param listener listener to add - */ - public void addOnTabSelectedListener(@NonNull OnTabSelectedListener listener) { - if (!mSelectedListeners.contains(listener)) { - mSelectedListeners.add(listener); - } - } - - /** - * Remove the given {@link TabLayout.OnTabSelectedListener} that was previously added via - * {@link #addOnTabSelectedListener(OnTabSelectedListener)}. - * - * @param listener listener to remove - */ - public void removeOnTabSelectedListener(@NonNull OnTabSelectedListener listener) { - mSelectedListeners.remove(listener); - } - - /** - * Remove all previously added {@link TabLayout.OnTabSelectedListener}s. - */ - public void clearOnTabSelectedListeners() { - mSelectedListeners.clear(); - } - - /** - * Create and return a new {@link Tab}. You need to manually add this using - * {@link #addTab(Tab)} or a related method. - * - * @return A new Tab - * @see #addTab(Tab) - */ - @NonNull - public Tab newTab() { - Tab tab = sTabPool.acquire(); - if (tab == null) { - tab = new Tab(); - } - tab.mParent = this; - tab.mView = createTabView(tab); - return tab; - } - - /** - * Returns the number of tabs currently registered with the action bar. - * - * @return Tab count - */ - public int getTabCount() { - return mTabs.size(); - } - - /** - * Returns the tab at the specified index. - */ - @Nullable - public Tab getTabAt(int index) { - return (index < 0 || index >= getTabCount()) ? null : mTabs.get(index); - } - - /** - * Returns the position of the current selected tab. - * - * @return selected tab position, or {@code -1} if there isn't a selected tab. - */ - public int getSelectedTabPosition() { - return mSelectedTab != null ? mSelectedTab.getPosition() : -1; - } - - /** - * Remove a tab from the layout. If the removed tab was selected it will be deselected - * and another tab will be selected if present. - * - * @param tab The tab to remove - */ - public void removeTab(Tab tab) { - if (tab.mParent != this) { - throw new IllegalArgumentException("Tab does not belong to this TabLayout."); - } - - removeTabAt(tab.getPosition()); - } - - /** - * Remove a tab from the layout. If the removed tab was selected it will be deselected - * and another tab will be selected if present. - * - * @param position Position of the tab to remove - */ - public void removeTabAt(int position) { - final int selectedTabPosition = mSelectedTab != null ? mSelectedTab.getPosition() : 0; - removeTabViewAt(position); - - final Tab removedTab = mTabs.remove(position); - if (removedTab != null) { - removedTab.reset(); - sTabPool.release(removedTab); - } - - final int newTabCount = mTabs.size(); - for (int i = position; i < newTabCount; i++) { - mTabs.get(i).setPosition(i); - } - - if (selectedTabPosition == position) { - selectTab(mTabs.isEmpty() ? null : mTabs.get(Math.max(0, position - 1))); - } - } - - /** - * Remove all tabs from the action bar and deselect the current tab. - */ - public void removeAllTabs() { - // Remove all the views - for (int i = mTabStrip.getChildCount() - 1; i >= 0; i--) { - removeTabViewAt(i); - } - - for (final Iterator i = mTabs.iterator(); i.hasNext();) { - final Tab tab = i.next(); - i.remove(); - tab.reset(); - sTabPool.release(tab); - } - - mSelectedTab = null; - } - - /** - * Set the behavior mode for the Tabs in this layout. The valid input options are: - *
    - *
  • {@link #MODE_FIXED}: Fixed tabs display all tabs concurrently and are best used - * with content that benefits from quick pivots between tabs.
  • - *
  • {@link #MODE_SCROLLABLE}: Scrollable tabs display a subset of tabs at any given moment, - * and can contain longer tab labels and a larger number of tabs. They are best used for - * browsing contexts in touch interfaces when users don’t need to directly compare the tab - * labels. This mode is commonly used with a {@link ViewPager}.
  • - *
- * - * @param mode one of {@link #MODE_FIXED} or {@link #MODE_SCROLLABLE}. - * - * @attr ref android.support.design.R.styleable#TabLayout_tabMode - */ - public void setTabMode(@Mode int mode) { - if (mode != mMode) { - mMode = mode; - applyModeAndGravity(); - } - } - - /** - * Returns the current mode used by this {@link TabLayout}. - * - * @see #setTabMode(int) - */ - @Mode - public int getTabMode() { - return mMode; - } - - /** - * Set the gravity to use when laying out the tabs. - * - * @param gravity one of {@link #GRAVITY_CENTER} or {@link #GRAVITY_FILL}. - * - * @attr ref android.support.design.R.styleable#TabLayout_tabGravity - */ - public void setTabGravity(@TabGravity int gravity) { - if (mTabGravity != gravity) { - mTabGravity = gravity; - applyModeAndGravity(); - } - } - - /** - * The current gravity used for laying out tabs. - * - * @return one of {@link #GRAVITY_CENTER} or {@link #GRAVITY_FILL}. - */ - @TabGravity - public int getTabGravity() { - return mTabGravity; - } - - /** - * Sets the text colors for the different states (normal, selected) used for the tabs. - * - * @see #getTabTextColors() - */ - public void setTabTextColors(@Nullable ColorStateList textColor) { - if (mTabTextColors != textColor) { - mTabTextColors = textColor; - updateAllTabs(); - } - } - - /** - * Gets the text colors for the different states (normal, selected) used for the tabs. - */ - @Nullable - public ColorStateList getTabTextColors() { - return mTabTextColors; - } - - /** - * Sets the text colors for the different states (normal, selected) used for the tabs. - * - * @attr ref android.support.design.R.styleable#TabLayout_tabTextColor - * @attr ref android.support.design.R.styleable#TabLayout_tabSelectedTextColor - */ - public void setTabTextColors(int normalColor, int selectedColor) { - setTabTextColors(createColorStateList(normalColor, selectedColor)); - } - - /** - * The one-stop shop for setting up this {@link TabLayout} with a {@link ViewPager}. - * - *

This is the same as calling {@link #setupWithViewPager(ViewPager, boolean)} with - * auto-refresh enabled.

- * - * @param viewPager the ViewPager to link to, or {@code null} to clear any previous link - */ - public void setupWithViewPager(@Nullable ViewPager viewPager) { - setupWithViewPager(viewPager, true); - } - - /** - * The one-stop shop for setting up this {@link TabLayout} with a {@link ViewPager}. - * - *

This method will link the given ViewPager and this VerticalTabLayout together so that - * changes in one are automatically reflected in the other. This includes scroll state changes - * and clicks. The tabs displayed in this layout will be populated - * from the ViewPager adapter's page titles.

- * - *

If {@code autoRefresh} is {@code true}, any changes in the {@link PagerAdapter} will - * trigger this layout to re-populate itself from the adapter's titles.

- * - *

If the given ViewPager is non-null, it needs to already have a - * {@link PagerAdapter} set.

- * - * @param viewPager the ViewPager to link to, or {@code null} to clear any previous link - * @param autoRefresh whether this layout should refresh its contents if the given ViewPager's - * content changes - */ - public void setupWithViewPager(@Nullable final ViewPager viewPager, boolean autoRefresh) { - setupWithViewPager(viewPager, autoRefresh, false); - } - - private void setupWithViewPager(@Nullable final ViewPager viewPager, boolean autoRefresh, - boolean implicitSetup) { - if (mViewPager != null) { - // If we've already been setup with a ViewPager, remove us from it - if (mPageChangeListener != null) { - mViewPager.removeOnPageChangeListener(mPageChangeListener); - } - if (mAdapterChangeListener != null) { - mViewPager.removeOnAdapterChangeListener(mAdapterChangeListener); - } - } - - if (mCurrentVpSelectedListener != null) { - // If we already have a tab selected listener for the ViewPager, remove it - removeOnTabSelectedListener(mCurrentVpSelectedListener); - mCurrentVpSelectedListener = null; - } - - if (viewPager != null) { - mViewPager = viewPager; - - // Add our custom OnPageChangeListener to the ViewPager - if (mPageChangeListener == null) { - mPageChangeListener = new VerticalTabLayoutOnPageChangeListener(this); - } - mPageChangeListener.reset(); - viewPager.addOnPageChangeListener(mPageChangeListener); - - // Now we'll add a tab selected listener to set ViewPager's current item - mCurrentVpSelectedListener = new ViewPagerOnTabSelectedListener(viewPager); - addOnTabSelectedListener(mCurrentVpSelectedListener); - - final ViewPagerAdapter adapter = (VerticalTabLayout.ViewPagerAdapter) viewPager.getAdapter(); - if (adapter != null) { - // Now we'll populate ourselves from the pager adapter, adding an observer if - // autoRefresh is enabled - setPagerAdapter(adapter, autoRefresh); - } - - // Add a listener so that we're notified of any adapter changes - if (mAdapterChangeListener == null) { - mAdapterChangeListener = new AdapterChangeListener(); - } - mAdapterChangeListener.setAutoRefresh(autoRefresh); - viewPager.addOnAdapterChangeListener(mAdapterChangeListener); - - // Now update the scroll position to match the ViewPager's current item - setScrollPosition(viewPager.getCurrentItem(), 0f, true); - } else { - // We've been given a null ViewPager so we need to clear out the internal state, - // listeners and observers - mViewPager = null; - setPagerAdapter(null, false); - } - - mSetupViewPagerImplicitly = implicitSetup; - } - - /** - * @deprecated Use {@link #setupWithViewPager(ViewPager)} to link a VerticalTabLayout with a ViewPager - * together. When that method is used, the VerticalTabLayout will be automatically updated - * when the {@link PagerAdapter} is changed. - */ - @Deprecated - public void setTabsFromPagerAdapter(@Nullable final ViewPagerAdapter adapter) { - setPagerAdapter(adapter, false); - } - - @Override - public boolean shouldDelayChildPressedState() { - // Only delay the pressed state if the tabs can scroll - return getTabScrollRange() > 0; - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - - if (mViewPager == null) { - // If we don't have a ViewPager already, check if our parent is a ViewPager to - // setup with it automatically - final ViewParent vp = getParent(); - if (vp instanceof ViewPager) { - // If we have a ViewPager parent and we've been added as part of its decor, let's - // assume that we should automatically setup to display any titles - setupWithViewPager((ViewPager) vp, true, true); - } - } - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - - if (mSetupViewPagerImplicitly) { - // If we've been setup with a ViewPager implicitly, let's clear out any listeners, etc - setupWithViewPager(null); - mSetupViewPagerImplicitly = false; - } - } - - private int getTabScrollRange() { - return Math.max(0, mTabStrip.getWidth() - mTopScrollView.getWidth() - mTopScrollView.getPaddingLeft() - - mTopScrollView.getPaddingRight()); - } - - void setPagerAdapter(@Nullable final ViewPagerAdapter adapter, final boolean addObserver) { - if (mPagerAdapter != null && mPagerAdapterObserver != null) { - // If we already have a PagerAdapter, unregister our observer - mPagerAdapter.unregisterDataSetObserver(mPagerAdapterObserver); - } - - mPagerAdapter = adapter; - - if (addObserver && adapter != null) { - // Register our observer on the new adapter - if (mPagerAdapterObserver == null) { - mPagerAdapterObserver = new PagerAdapterObserver(); - } - adapter.registerDataSetObserver(mPagerAdapterObserver); - } - - // Finally make sure we reflect the new adapter - populateFromPagerAdapter(); - } - - void populateFromPagerAdapter() { - removeAllTabs(); - - if (mPagerAdapter != null) { - final int adapterCount = mPagerAdapter.getCount(); - for (int i = 0; i < adapterCount; i++) { - addTab(newTab().setText(mPagerAdapter.getPageTitle(i)), false); - if (mPagerAdapter.getIcon(i) != 0) { - getTabAt(i).setIcon(mPagerAdapter.getIcon(i)); - } - } - - // Make sure we reflect the currently set ViewPager item - if (mViewPager != null && adapterCount > 0) { - final int curItem = mViewPager.getCurrentItem(); - if (curItem != getSelectedTabPosition() && curItem < getTabCount()) { - selectTab(getTabAt(curItem)); - } - } - } - } - - private void updateAllTabs() { - for (int i = 0, z = mTabs.size(); i < z; i++) { - mTabs.get(i).updateView(); - } - } - - private TabView createTabView(@NonNull final Tab tab) { - TabView tabView = mTabViewPool != null ? mTabViewPool.acquire() : null; - if (tabView == null) { - tabView = new TabView(getContext()); - } - tabView.setTab(tab); - tabView.setFocusable(true); - tabView.setMinimumWidth(getTabMinWidth()); - return tabView; - } - - private void configureTab(Tab tab, int position) { - tab.setPosition(position); - mTabs.add(position, tab); - - final int count = mTabs.size(); - for (int i = position + 1; i < count; i++) { - mTabs.get(i).setPosition(i); - } - } - - private void addTabView(Tab tab) { - final TabView tabView = tab.mView; - mTabStrip.addView(tabView, tab.getPosition(), createLayoutParamsForTabs()); - } - - // @Override - public void addViewItem(View child) { - addViewInternal(child); - } - - // @Override - public void addViewItem(View child, int index) { - addViewInternal(child); - } - - // @Override - public void addViewItem(View child, ViewGroup.LayoutParams params) { - addViewInternal(child); - } - - // @Override - public void addViewItem(View child, int index, ViewGroup.LayoutParams params) { - addViewInternal(child); - } - - private void addViewInternal(final View child) { - if (child instanceof TabItem) { - addTabFromItemView((TabItem) child); - } else { - throw new IllegalArgumentException("Only TabItem instances can be added to TabLayout"); - } - } - - private LinearLayout.LayoutParams createLayoutParamsForTabs() { - final LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( - LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT); - updateTabViewLayoutParams(lp); - return lp; - } - - private void updateTabViewLayoutParams(LinearLayout.LayoutParams lp) { - if (mMode == MODE_FIXED && mTabGravity == GRAVITY_FILL) { - lp.width = 0; - lp.weight = 1; - } else { - lp.width = LinearLayout.LayoutParams.WRAP_CONTENT; - lp.weight = 0; - } - } - - int dpToPx(int dps) { - return Math.round(getResources().getDisplayMetrics().density * dps); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - // If we have a MeasureSpec which allows us to decide our height, try and use the default - // height - final int idealHeight = dpToPx(getDefaultHeight()) + mTopScrollView.getPaddingTop() + mTopScrollView.getPaddingBottom(); - - switch (MeasureSpec.getMode(heightMeasureSpec)) { - case MeasureSpec.AT_MOST: - heightMeasureSpec = MeasureSpec.makeMeasureSpec( - Math.min(idealHeight, MeasureSpec.getSize(heightMeasureSpec)), - MeasureSpec.EXACTLY); - break; - case 0 /* MeasureSpec.UNSPECIFIED */: - heightMeasureSpec = MeasureSpec.makeMeasureSpec(idealHeight, MeasureSpec.EXACTLY); - break; - } - - final int specWidth = MeasureSpec.getSize(widthMeasureSpec); - if (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED) { - // If we don't have an unspecified width spec, use the given size to calculate - // the max tab width - mTabMaxWidth = mRequestedTabMaxWidth > 0 - ? mRequestedTabMaxWidth - : specWidth - dpToPx(TAB_MIN_WIDTH_MARGIN); - } - - // Now super measure itself using the (possibly) modified height spec - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - - if (getChildCount() == 1) { - // If we're in fixed mode then we need to make the tab strip is the same width as us - // so we don't scroll - final View child = getChildAt(0); - boolean remeasure = false; - - switch (mMode) { - case MODE_SCROLLABLE: - // We only need to resize the child if it's smaller than us. This is similar - // to fillViewport - remeasure = child.getMeasuredWidth() < mTopScrollView.getMeasuredWidth(); - break; - case MODE_FIXED: - // Resize the child so that it doesn't scroll - remeasure = child.getMeasuredWidth() != mTopScrollView.getMeasuredWidth(); - break; - } - - if (remeasure) { - // Re-measure the child with a widthSpec set to be exactly our measure width - int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, mTopScrollView.getPaddingTop() - + mTopScrollView.getPaddingBottom(), child.getLayoutParams().height); - int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec( - mTopScrollView.getMeasuredWidth(), MeasureSpec.EXACTLY); - child.measure(childWidthMeasureSpec, childHeightMeasureSpec); - } - } - } - - private void removeTabViewAt(int position) { - final TabView view = (TabView) mTabStrip.getChildAt(position); - mTabStrip.removeViewAt(position); - if (view != null) { - view.reset(); - mTabViewPool.release(view); - } - requestLayout(); - } - - private void animateToTab(int newPosition) { - if (newPosition == Tab.INVALID_POSITION) { - return; - } - - if (getWindowToken() == null || !ViewCompat.isLaidOut(this) - || mTabStrip.childrenNeedLayout()) { - // If we don't have a window token, or we haven't been laid out yet just draw the new - // position now - setScrollPosition(newPosition, 0f, true); - return; - } - - final int startScrollX = mTopScrollView.getScrollX(); - final int targetScrollX = calculateScrollXForTab(newPosition, 0); - - if (startScrollX != targetScrollX) { - ensureScrollAnimator(); - - mScrollAnimator.setIntValues(startScrollX, targetScrollX); - mScrollAnimator.start(); - } - - // Now animate the indicator - mTabStrip.animateIndicatorToPosition(newPosition, ANIMATION_DURATION); - } - - private void ensureScrollAnimator() { - if (mScrollAnimator == null) { - mScrollAnimator = new ValueAnimator(); - mScrollAnimator.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR); - mScrollAnimator.setDuration(ANIMATION_DURATION); - mScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animator) { - scrollTo((int) animator.getAnimatedValue(), 0); - } - }); - } - } - - void setScrollAnimatorListener(Animator.AnimatorListener listener) { - ensureScrollAnimator(); - mScrollAnimator.addListener(listener); - } - - private void setSelectedTabView(int position) { - final int tabCount = mTabStrip.getChildCount(); - if (position < tabCount) { - for (int i = 0; i < tabCount; i++) { - final View child = mTabStrip.getChildAt(i); - child.setSelected(i == position); - } - } - } - - void selectTab(Tab tab) { - selectTab(tab, true); - } - - void selectTab(final Tab tab, boolean updateIndicator) { - final Tab currentTab = mSelectedTab; - - if (currentTab == tab) { - if (currentTab != null) { - dispatchTabReselected(tab); - animateToTab(tab.getPosition()); - } - } else { - final int newPosition = tab != null ? tab.getPosition() : Tab.INVALID_POSITION; - if (updateIndicator) { - if ((currentTab == null || currentTab.getPosition() == Tab.INVALID_POSITION) - && newPosition != Tab.INVALID_POSITION) { - // If we don't currently have a tab, just draw the indicator - setScrollPosition(newPosition, 0f, true); - } else { - animateToTab(newPosition); - } - if (newPosition != Tab.INVALID_POSITION) { - setSelectedTabView(newPosition); - } - } - if (currentTab != null) { - dispatchTabUnselected(currentTab); - } - mSelectedTab = tab; - if (tab != null) { - dispatchTabSelected(tab); - } - } - } - - private void dispatchTabSelected(@NonNull final Tab tab) { - for (int i = mSelectedListeners.size() - 1; i >= 0; i--) { - mSelectedListeners.get(i).onTabSelected(tab); - } - } - - private void dispatchTabUnselected(@NonNull final Tab tab) { - for (int i = mSelectedListeners.size() - 1; i >= 0; i--) { - mSelectedListeners.get(i).onTabUnselected(tab); - } - } - - private void dispatchTabReselected(@NonNull final Tab tab) { - for (int i = mSelectedListeners.size() - 1; i >= 0; i--) { - mSelectedListeners.get(i).onTabReselected(tab); - } - } - - private int calculateScrollXForTab(int position, float positionOffset) { - if (mMode == MODE_SCROLLABLE) { - final View selectedChild = mTabStrip.getChildAt(position); - final View nextChild = position + 1 < mTabStrip.getChildCount() - ? mTabStrip.getChildAt(position + 1) - : null; - final int selectedWidth = selectedChild != null ? selectedChild.getWidth() : 0; - final int nextWidth = nextChild != null ? nextChild.getWidth() : 0; - - // base scroll amount: places center of tab in center of parent - int scrollBase = selectedChild.getLeft() + (selectedWidth / 2) - (mTopScrollView.getWidth() / 2); - // offset amount: fraction of the distance between centers of tabs - int scrollOffset = (int) ((selectedWidth + nextWidth) * 0.5f * positionOffset); - - return (ViewCompat.getLayoutDirection(mTopScrollView) == ViewCompat.LAYOUT_DIRECTION_LTR) - ? scrollBase + scrollOffset - : scrollBase - scrollOffset; - } - return 0; - } - - private void applyModeAndGravity() { - int paddingStart = 0; - if (mMode == MODE_SCROLLABLE) { - // If we're scrollable, or fixed at start, inset using padding - paddingStart = Math.max(0, mContentInsetStart - mTabPaddingStart); - } - ViewCompat.setPaddingRelative(mTabStrip, paddingStart, 0, 0, 0); - - switch (mMode) { - case MODE_FIXED: - mTabStrip.setGravity(Gravity.CENTER_VERTICAL); - break; - case MODE_SCROLLABLE: - mTabStrip.setGravity(GravityCompat.START); - break; - } - - updateTabViews(true); - } - - void updateTabViews(final boolean requestLayout) { - for (int i = 0; i < mTabStrip.getChildCount(); i++) { - View child = mTabStrip.getChildAt(i); - child.setMinimumWidth(getTabMinWidth()); - updateTabViewLayoutParams((LinearLayout.LayoutParams) child.getLayoutParams()); - if (requestLayout) { - child.requestLayout(); - } - } - } - - /** - * A tab in this layout. Instances can be created via {@link #newTab()}. - */ - public static final class Tab { - - /** - * An invalid position for a tab. - * - * @see #getPosition() - */ - public static final int INVALID_POSITION = -1; - - private Object mTag; - private Drawable mIcon; - private CharSequence mText; - private CharSequence mContentDesc; - private int mPosition = INVALID_POSITION; - private View mCustomView; - - VerticalTabLayout mParent; - TabView mView; - - Tab() { - // Private constructor - } - - /** - * @return This Tab's tag object. - */ - @Nullable - public Object getTag() { - return mTag; - } - - /** - * Give this Tab an arbitrary object to hold for later use. - * - * @param tag Object to store - * @return The current instance for call chaining - */ - @NonNull - public Tab setTag(@Nullable Object tag) { - mTag = tag; - return this; - } - - - /** - * Returns the custom view used for this tab. - * - * @see #setCustomView(View) - * @see #setCustomView(int) - */ - @Nullable - public View getCustomView() { - return mCustomView; - } - - /** - * Set a custom view to be used for this tab. - *

- * If the provided view contains a {@link TextView} with an ID of - * {@link android.R.id#text1} then that will be updated with the value given - * to {@link #setText(CharSequence)}. Similarly, if this layout contains an - * {@link ImageView} with ID {@link android.R.id#icon} then it will be updated with - * the value given to {@link #setIcon(Drawable)}. - *

- * - * @param view Custom view to be used as a tab. - * @return The current instance for call chaining - */ - @NonNull - public Tab setCustomView(@Nullable View view) { - mCustomView = view; - updateView(); - return this; - } - - /** - * Set a custom view to be used for this tab. - *

- * If the inflated layout contains a {@link TextView} with an ID of - * {@link android.R.id#text1} then that will be updated with the value given - * to {@link #setText(CharSequence)}. Similarly, if this layout contains an - * {@link ImageView} with ID {@link android.R.id#icon} then it will be updated with - * the value given to {@link #setIcon(Drawable)}. - *

- * - * @param resId A layout resource to inflate and use as a custom tab view - * @return The current instance for call chaining - */ - @NonNull - public Tab setCustomView(@LayoutRes int resId) { - final LayoutInflater inflater = LayoutInflater.from(mView.getContext()); - return setCustomView(inflater.inflate(resId, mView, false)); - } - - /** - * Return the icon associated with this tab. - * - * @return The tab's icon - */ - @Nullable - public Drawable getIcon() { - return mIcon; - } - - /** - * Return the current position of this tab in the action bar. - * - * @return Current position, or {@link #INVALID_POSITION} if this tab is not currently in - * the action bar. - */ - public int getPosition() { - return mPosition; - } - - void setPosition(int position) { - mPosition = position; - } - - /** - * Return the text of this tab. - * - * @return The tab's text - */ - @Nullable - public CharSequence getText() { - return mText; - } - - /** - * Set the icon displayed on this tab. - * - * @param icon The drawable to use as an icon - * @return The current instance for call chaining - */ - @NonNull - public Tab setIcon(@Nullable Drawable icon) { - mIcon = icon; - updateView(); - return this; - } - - /** - * Set the icon displayed on this tab. - * - * @param resId A resource ID referring to the icon that should be displayed - * @return The current instance for call chaining - */ - @NonNull - public Tab setIcon(@DrawableRes int resId) { - if (mParent == null) { - throw new IllegalArgumentException("Tab not attached to a TabLayout"); - } - return setIcon(resId == 0 ? null : AppCompatResources.getDrawable(mParent.getContext(), resId)); - } - - /** - * Set the text displayed on this tab. Text may be truncated if there is not room to display - * the entire string. - * - * @param text The text to display - * @return The current instance for call chaining - */ - @NonNull - public Tab setText(@Nullable CharSequence text) { - mText = text; - updateView(); - return this; - } - - /** - * Set the text displayed on this tab. Text may be truncated if there is not room to display - * the entire string. - * - * @param resId A resource ID referring to the text that should be displayed - * @return The current instance for call chaining - */ - @NonNull - public Tab setText(@StringRes int resId) { - if (mParent == null) { - throw new IllegalArgumentException("Tab not attached to a TabLayout"); - } - return setText(mParent.getResources().getText(resId)); - } - - /** - * Select this tab. Only valid if the tab has been added to the action bar. - */ - public void select() { - if (mParent == null) { - throw new IllegalArgumentException("Tab not attached to a TabLayout"); - } - mParent.selectTab(this); - } - - /** - * Returns true if this tab is currently selected. - */ - public boolean isSelected() { - if (mParent == null) { - throw new IllegalArgumentException("Tab not attached to a TabLayout"); - } - return mParent.getSelectedTabPosition() == mPosition; - } - - /** - * Set a description of this tab's content for use in accessibility support. If no content - * description is provided the title will be used. - * - * @param resId A resource ID referring to the description text - * @return The current instance for call chaining - * @see #setContentDescription(CharSequence) - * @see #getContentDescription() - */ - @NonNull - public Tab setContentDescription(@StringRes int resId) { - if (mParent == null) { - throw new IllegalArgumentException("Tab not attached to a TabLayout"); - } - return setContentDescription(mParent.getResources().getText(resId)); - } - - /** - * Set a description of this tab's content for use in accessibility support. If no content - * description is provided the title will be used. - * - * @param contentDesc Description of this tab's content - * @return The current instance for call chaining - * @see #setContentDescription(int) - * @see #getContentDescription() - */ - @NonNull - public Tab setContentDescription(@Nullable CharSequence contentDesc) { - mContentDesc = contentDesc; - updateView(); - return this; - } - - /** - * Gets a brief description of this tab's content for use in accessibility support. - * - * @return Description of this tab's content - * @see #setContentDescription(CharSequence) - * @see #setContentDescription(int) - */ - @Nullable - public CharSequence getContentDescription() { - return mContentDesc; - } - - void updateView() { - if (mView != null) { - mView.update(); - } - } - - void reset() { - mParent = null; - mView = null; - mTag = null; - mIcon = null; - mText = null; - mContentDesc = null; - mPosition = INVALID_POSITION; - mCustomView = null; - } - } - - class TabView extends LinearLayout { - private Tab mTab; - private TextView mTextView; - private ImageView mIconView; - - private View mCustomView; - private TextView mCustomTextView; - private ImageView mCustomIconView; - - private int mDefaultMaxLines = 2; - - public TabView(Context context) { - super(context); - if (mTabBackgroundResId != 0) { - ViewCompat.setBackground( - this, AppCompatResources.getDrawable(context, mTabBackgroundResId)); - } - ViewCompat.setPaddingRelative(this, mTabPaddingStart, mTabPaddingTop, - mTabPaddingEnd, mTabPaddingBottom); - setGravity(Gravity.CENTER_VERTICAL); - setOrientation(HORIZONTAL); - setClickable(true); - ViewCompat.setPointerIcon(this, - PointerIconCompat.getSystemIcon(getContext(), PointerIconCompat.TYPE_HAND)); - } - - @Override - public boolean performClick() { - final boolean handled = super.performClick(); - - if (mTab != null) { - if (!handled) { - playSoundEffect(SoundEffectConstants.CLICK); - } - mTab.select(); - return true; - } else { - return handled; - } - } - - @Override - public void setSelected(final boolean selected) { - final boolean changed = isSelected() != selected; - - super.setSelected(selected); - - if (changed && selected && Build.VERSION.SDK_INT < 16) { - // Pre-JB we need to manually send the TYPE_VIEW_SELECTED event - sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); - } - - // Always dispatch this to the child views, regardless of whether the value has - // changed - if (mTextView != null) { - mTextView.setSelected(selected); - } - if (mIconView != null) { - mIconView.setSelected(selected); - } - if (mCustomView != null) { - mCustomView.setSelected(selected); - } - } - - @Override - public void onInitializeAccessibilityEvent(AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(event); - // This view masquerades as an action bar tab. - event.setClassName(ActionBar.Tab.class.getName()); - } - - @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); - // This view masquerades as an action bar tab. - info.setClassName(ActionBar.Tab.class.getName()); - } - - @Override - public void onMeasure(final int origWidthMeasureSpec, final int origHeightMeasureSpec) { - final int specWidthSize = MeasureSpec.getSize(origWidthMeasureSpec); - final int specWidthMode = MeasureSpec.getMode(origWidthMeasureSpec); - final int maxWidth = getTabMaxWidth(); - - final int widthMeasureSpec; - final int heightMeasureSpec = origHeightMeasureSpec; - - if (maxWidth > 0 && (specWidthMode == MeasureSpec.UNSPECIFIED - || specWidthSize > maxWidth)) { - // If we have a max width and a given spec which is either unspecified or - // larger than the max width, update the width spec using the same mode - widthMeasureSpec = MeasureSpec.makeMeasureSpec(mTabMaxWidth, MeasureSpec.AT_MOST); - } else { - // Else, use the original width spec - widthMeasureSpec = origWidthMeasureSpec; - } - - // Now lets measure - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - - // We need to switch the text size based on whether the text is spanning 2 lines or not - if (mTextView != null) { - // final Resources res = getResources(); - float textSize = mTabTextSize; - int maxLines = mDefaultMaxLines; - - if (mIconView != null && mIconView.getVisibility() == VISIBLE) { - // If the icon view is being displayed, we limit the text to 1 line - maxLines = 2; - } else if (mTextView != null && mTextView.getLineCount() > 1) { - // Otherwise when we have text which wraps we reduce the text size - textSize = mTabTextMultiLineSize; - } - - final float curTextSize = mTextView.getTextSize(); - final int curLineCount = mTextView.getLineCount(); - final int curMaxLines = TextViewCompat.getMaxLines(mTextView); - - if (textSize != curTextSize || (curMaxLines >= 0 && maxLines != curMaxLines)) { - // We've got a new text size and/or max lines... - boolean updateTextView = true; - - if (mMode == MODE_FIXED && textSize > curTextSize && curLineCount == 1) { - // If we're in fixed mode, going up in text size and currently have 1 line - // then it's very easy to get into an infinite recursion. - // To combat that we check to see if the change in text size - // will cause a line count change. If so, abort the size change and stick - // to the smaller size. - final Layout layout = mTextView.getLayout(); - if (layout == null || approximateLineWidth(layout, 0, textSize) - > getMeasuredWidth() - getPaddingLeft() - getPaddingRight()) { - updateTextView = false; - } - } - - if (updateTextView) { - mTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); - mTextView.setMaxLines(maxLines); - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } - } - } - } - - void setTab(@Nullable final Tab tab) { - if (tab != mTab) { - mTab = tab; - update(); - } - } - - void reset() { - setTab(null); - setSelected(false); - } - - final void update() { - final Tab tab = mTab; - final View custom = tab != null ? tab.getCustomView() : null; - if (custom != null) { - final ViewParent customParent = custom.getParent(); - if (customParent != this) { - if (customParent != null) { - ((ViewGroup) customParent).removeView(custom); - } - addView(custom); - } - mCustomView = custom; - if (mTextView != null) { - mTextView.setVisibility(GONE); - } - if (mIconView != null) { - mIconView.setVisibility(GONE); - mIconView.setImageDrawable(null); - } - - mCustomTextView = (TextView) custom.findViewById(android.R.id.text1); - if (mCustomTextView != null) { - mDefaultMaxLines = TextViewCompat.getMaxLines(mCustomTextView); - } - mCustomIconView = (ImageView) custom.findViewById(android.R.id.icon); - } else { - // We do not have a custom view. Remove one if it already exists - if (mCustomView != null) { - removeView(mCustomView); - mCustomView = null; - } - mCustomTextView = null; - mCustomIconView = null; - } - - if (mCustomView == null) { - // If there isn't a custom view, we'll us our own in-built layouts - if (mIconView == null) { - ImageView iconView = (ImageView) LayoutInflater.from(getContext()) - .inflate(R.layout.design_layout_tab_icon, this, false); - iconView.setLayoutParams(new LinearLayout.LayoutParams(dpToPx(25), - dpToPx(25))); - addView(iconView, 0); - mIconView = iconView; - } - if (mTextView == null) { - TextView textView = (TextView) LayoutInflater.from(getContext()) - .inflate(R.layout.design_layout_tab_text, this, false); - LinearLayout.LayoutParams textParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT); - textParams.gravity = Gravity.CENTER_VERTICAL; - textView.setLayoutParams(textParams); - addView(textView); - mTextView = textView; - mDefaultMaxLines = TextViewCompat.getMaxLines(mTextView); - } - TextViewCompat.setTextAppearance(mTextView, mTabTextAppearance); - if (mTabTextColors != null) { - mTextView.setTextColor(mTabTextColors); - } - updateTextAndIcon(mTextView, mIconView); - } else { - // Else, we'll see if there is a TextView or ImageView present and update them - if (mCustomTextView != null || mCustomIconView != null) { - updateTextAndIcon(mCustomTextView, mCustomIconView); - } - } - - // Finally update our selected state - setSelected(tab != null && tab.isSelected()); - } - - private void updateTextAndIcon(@Nullable final TextView textView, - @Nullable final ImageView iconView) { - final Drawable icon = mTab != null ? mTab.getIcon() : null; - final CharSequence text = mTab != null ? mTab.getText() : null; - final CharSequence contentDesc = mTab != null ? mTab.getContentDescription() : null; - - if (iconView != null) { - if (icon != null) { - iconView.setImageDrawable(icon); - iconView.setVisibility(VISIBLE); - setVisibility(VISIBLE); - } else { - iconView.setVisibility(GONE); - iconView.setImageDrawable(null); - } - iconView.setContentDescription(contentDesc); - } - - final boolean hasText = !TextUtils.isEmpty(text); - if (textView != null) { - if (hasText) { - textView.setText(text); - textView.setVisibility(VISIBLE); - setVisibility(VISIBLE); - } else { - textView.setVisibility(GONE); - textView.setText(null); - } - textView.setContentDescription(contentDesc); - } - - if (iconView != null) { - MarginLayoutParams lp = ((MarginLayoutParams) iconView.getLayoutParams()); - int bottomMargin = 0; - if (hasText && iconView.getVisibility() == VISIBLE) { - // If we're showing both text and icon, add some margin bottom to the icon - bottomMargin = dpToPx(DEFAULT_GAP_TEXT_ICON); - } - if (bottomMargin != lp.bottomMargin) { - lp.bottomMargin = bottomMargin; - iconView.requestLayout(); - } - } - TooltipCompat.setTooltipText(this, hasText ? null : contentDesc); - } - - public Tab getTab() { - return mTab; - } - - /** - * Approximates a given lines width with the new provided text size. - */ - private float approximateLineWidth(Layout layout, int line, float textSize) { - return layout.getLineWidth(line) * (textSize / layout.getPaint().getTextSize()); - } - } - - private class SlidingTabStrip extends LinearLayout { - private int mSelectedIndicatorHeight; - private final Paint mSelectedIndicatorPaint; - - int mSelectedPosition = -1; - float mSelectionOffset; - - private int mLayoutDirection = -1; - - private int mIndicatorTop = -1; - private int mIndicatorBottom = -1; - - private ValueAnimator mIndicatorAnimator; - - SlidingTabStrip(Context context) { - super(context); - setWillNotDraw(false); - - - // Easy way to get vertical tab view - setOrientation(VERTICAL); - - mSelectedIndicatorPaint = new Paint(); - } - - void setSelectedIndicatorColor(int color) { - if (mSelectedIndicatorPaint.getColor() != color) { - mSelectedIndicatorPaint.setColor(color); - ViewCompat.postInvalidateOnAnimation(this); - } - } - - void setSelectedIndicatorHeight(int height) { - if (mSelectedIndicatorHeight != height) { - mSelectedIndicatorHeight = height; - ViewCompat.postInvalidateOnAnimation(this); - } - } - - boolean childrenNeedLayout() { - for (int i = 0, z = getChildCount(); i < z; i++) { - final View child = getChildAt(i); - if (child.getWidth() <= 0) { - return true; - } - } - return false; - } - - void setIndicatorPositionFromTabPosition(int position, float positionOffset) { - if (mIndicatorAnimator != null && mIndicatorAnimator.isRunning()) { - mIndicatorAnimator.cancel(); - } - - mSelectedPosition = position; - mSelectionOffset = positionOffset; - updateIndicatorPosition(); - } - - float getIndicatorPosition() { - return mSelectedPosition + mSelectionOffset; - } - - @Override - public void onRtlPropertiesChanged(int layoutDirection) { - super.onRtlPropertiesChanged(layoutDirection); - - // Workaround for a bug before Android M where LinearLayout did not relayout itself when - // layout direction changed. - if (Build.VERSION.SDK_INT < 23 /* Build.VERSION_CODES.M */) { - //noinspection WrongConstant - if (mLayoutDirection != layoutDirection) { - requestLayout(); - mLayoutDirection = layoutDirection; - } - } - } - - @Override - protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - - if (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY) { - // ScrollView will first measure use with UNSPECIFIED, and then with - // EXACTLY. Ignore the first call since anything we do will be overwritten anyway - return; - } - - if (mMode == MODE_FIXED && mTabGravity == GRAVITY_CENTER) { - final int count = getChildCount(); - - // First we'll find the widest tab - int largestTabWidth = 0; - for (int i = 0, z = count; i < z; i++) { - View child = getChildAt(i); - if (child.getVisibility() == VISIBLE) { - largestTabWidth = Math.max(largestTabWidth, child.getMeasuredWidth()); - } - } - - if (largestTabWidth <= 0) { - // If we don't have a largest child yet, skip until the next measure pass - return; - } - - final int gutter = dpToPx(FIXED_WRAP_GUTTER_MIN); - boolean remeasure = false; - - if (largestTabWidth * count <= getMeasuredWidth() - gutter * 2) { - // If the tabs fit within our width minus gutters, we will set all tabs to have - // the same width - for (int i = 0; i < count; i++) { - final LinearLayout.LayoutParams lp = - (LayoutParams) getChildAt(i).getLayoutParams(); - if (lp.width != largestTabWidth || lp.weight != 0) { - lp.width = largestTabWidth; - lp.weight = 0; - remeasure = true; - } - } - } else { - // If the tabs will wrap to be larger than the width minus gutters, we need - // to switch to GRAVITY_FILL - mTabGravity = GRAVITY_FILL; - updateTabViews(false); - remeasure = true; - } - - if (remeasure) { - // Now re-measure after our changes - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } - } - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - super.onLayout(changed, l, t, r, b); - - if (mIndicatorAnimator != null && mIndicatorAnimator.isRunning()) { - // If we're currently running an animation, lets cancel it and start a - // new animation with the remaining duration - mIndicatorAnimator.cancel(); - final long duration = mIndicatorAnimator.getDuration(); - animateIndicatorToPosition(mSelectedPosition, - Math.round((1f - mIndicatorAnimator.getAnimatedFraction()) * duration)); - } else { - // If we've been layed out, update the indicator position - updateIndicatorPosition(); - } - } - - private void updateIndicatorPosition() { - final View selectedTitle = getChildAt(mSelectedPosition); - int top, bottom; - - if (selectedTitle != null && selectedTitle.getWidth() > 0) { - top = selectedTitle.getTop(); - bottom = selectedTitle.getBottom(); - - if (mSelectionOffset > 0f && mSelectedPosition < getChildCount() - 1) { - // Draw the selection partway between the tabs - View nextTitle = getChildAt(mSelectedPosition + 1); - top = (int) (mSelectionOffset * nextTitle.getTop() + - (1.0f - mSelectionOffset) * top); - bottom = (int) (mSelectionOffset * nextTitle.getBottom() + - (1.0f - mSelectionOffset) * bottom); - } - } else { - top = bottom = -1; - } - - setIndicatorPosition(top, bottom); - } - - void setIndicatorPosition(int top, int bottom) { - if (top != mIndicatorTop || bottom != mIndicatorBottom) { - // If the indicator's left/right has changed, invalidate - mIndicatorTop = top; - mIndicatorBottom = bottom; - ViewCompat.postInvalidateOnAnimation(this); - } - } - - void animateIndicatorToPosition(final int position, int duration) { - if (mIndicatorAnimator != null && mIndicatorAnimator.isRunning()) { - mIndicatorAnimator.cancel(); - } - - final View targetView = getChildAt(position); - if (targetView == null) { - // If we don't have a view, just update the position now and return - updateIndicatorPosition(); - return; - } - - final int targetTop = targetView.getTop(); - final int targetBottom = targetView.getBottom(); - final int startTop; - final int startBottom; - - if (Math.abs(position - mSelectedPosition) <= 1) { - // If the views are adjacent, we'll animate from edge-to-edge - startTop = mIndicatorTop; - startBottom = mIndicatorBottom; - } else { - // Else, we'll just grow from the nearest edge - final int offset = dpToPx(MOTION_NON_ADJACENT_OFFSET); - if (position < mSelectedPosition) { - // We're going end-to-start - /* - if (isRtl) { - startTop = startBottom = targetTop - offset; - } else { - */ - startTop = startBottom = targetBottom + offset; - // } - } else { - // We're going start-to-end - /* - if (isRtl) { - startLeft = startRight = targetRight + offset; - } else { - */ - startTop = startBottom = targetTop - offset; - // } - } - } - - if (startTop != targetTop || startBottom != targetBottom) { - ValueAnimator animator = mIndicatorAnimator = new ValueAnimator(); - animator.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR); - animator.setDuration(duration); - animator.setFloatValues(0, 1); - animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @SuppressLint("RestrictedApi") - @Override - public void onAnimationUpdate(ValueAnimator animator) { - final float fraction = animator.getAnimatedFraction(); - setIndicatorPosition( - AnimationUtils.lerp(startTop, targetTop, fraction), - AnimationUtils.lerp(startBottom, targetBottom, fraction)); - } - }); - animator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animator) { - mSelectedPosition = position; - mSelectionOffset = 0f; - } - }); - animator.start(); - } - } - - @Override - public void draw(Canvas canvas) { - super.draw(canvas); - - // Thick colored line at the left side of the current selection - if (mIndicatorTop >= 0 && mIndicatorBottom > mIndicatorTop) { - canvas.drawRect(0, mIndicatorTop, - mSelectedIndicatorHeight, mIndicatorBottom, mSelectedIndicatorPaint); - } - } - } - - private static ColorStateList createColorStateList(int defaultColor, int selectedColor) { - final int[][] states = new int[2][]; - final int[] colors = new int[2]; - int i = 0; - - states[i] = SELECTED_STATE_SET; - colors[i] = selectedColor; - i++; - - // Default enabled state - states[i] = EMPTY_STATE_SET; - colors[i] = defaultColor; - i++; - - return new ColorStateList(states, colors); - } - - private int getDefaultHeight() { - boolean hasIconAndText = false; - for (int i = 0, count = mTabs.size(); i < count; i++) { - Tab tab = mTabs.get(i); - if (tab != null && tab.getIcon() != null && !TextUtils.isEmpty(tab.getText())) { - hasIconAndText = true; - break; - } - } - return hasIconAndText ? DEFAULT_HEIGHT_WITH_TEXT_ICON : DEFAULT_HEIGHT; - } - - private int getTabMinWidth() { - if (mRequestedTabMinWidth != INVALID_WIDTH) { - // If we have been given a min width, use it - return mRequestedTabMinWidth; - } - // Else, we'll use the default value - return mMode == MODE_SCROLLABLE ? mScrollableTabMinWidth : 0; - } - - @Override - public LayoutParams generateLayoutParams(AttributeSet attrs) { - // We don't care about the layout params of any views added to us, since we don't actually - // add them. The only view we add is the SlidingTabStrip, which is done manually. - // We return the default layout params so that we don't blow up if we're given a TabItem - // without android:layout_* values. - return generateDefaultLayoutParams(); - } - - int getTabMaxWidth() { - return mTabMaxWidth; - } - - /** - * A {@link ViewPager.OnPageChangeListener} class which contains the - * necessary calls back to the provided {@link TabLayout} so that the tab position is - * kept in sync. - * - *

This class stores the provided VerticalTabLayout weakly, meaning that you can use - * {@link ViewPager#addOnPageChangeListener(ViewPager.OnPageChangeListener) - * addOnPageChangeListener(OnPageChangeListener)} without removing the listener and - * not cause a leak. - */ - public static class VerticalTabLayoutOnPageChangeListener implements ViewPager.OnPageChangeListener { - private final WeakReference mTabLayoutRef; - private int mPreviousScrollState; - private int mScrollState; - - public VerticalTabLayoutOnPageChangeListener(VerticalTabLayout tabLayout) { - mTabLayoutRef = new WeakReference<>(tabLayout); - } - - @Override - public void onPageScrollStateChanged(final int state) { - mPreviousScrollState = mScrollState; - mScrollState = state; - } - - @Override - public void onPageScrolled(final int position, final float positionOffset, - final int positionOffsetPixels) { - final VerticalTabLayout tabLayout = mTabLayoutRef.get(); - if (tabLayout != null) { - // Only update the text selection if we're not settling, or we are settling after - // being dragged - final boolean updateText = mScrollState != SCROLL_STATE_SETTLING || - mPreviousScrollState == SCROLL_STATE_DRAGGING; - // Update the indicator if we're not settling after being idle. This is caused - // from a setCurrentItem() call and will be handled by an animation from - // onPageSelected() instead. - final boolean updateIndicator = !(mScrollState == SCROLL_STATE_SETTLING - && mPreviousScrollState == SCROLL_STATE_IDLE); - tabLayout.setScrollPosition(position, positionOffset, updateText, updateIndicator); - } - } - - @Override - public void onPageSelected(final int position) { - final VerticalTabLayout tabLayout = mTabLayoutRef.get(); - if (tabLayout != null && tabLayout.getSelectedTabPosition() != position - && position < tabLayout.getTabCount()) { - // Select the tab, only updating the indicator if we're not being dragged/settled - // (since onPageScrolled will handle that). - final boolean updateIndicator = mScrollState == SCROLL_STATE_IDLE - || (mScrollState == SCROLL_STATE_SETTLING - && mPreviousScrollState == SCROLL_STATE_IDLE); - tabLayout.selectTab(tabLayout.getTabAt(position), updateIndicator); - } - } - - void reset() { - mPreviousScrollState = mScrollState = SCROLL_STATE_IDLE; - } - } - - /** - * A {@link TabLayout.OnTabSelectedListener} class which contains the necessary calls back - * to the provided {@link ViewPager} so that the tab position is kept in sync. - */ - public static class ViewPagerOnTabSelectedListener implements VerticalTabLayout.OnTabSelectedListener { - private final ViewPager mViewPager; - - public ViewPagerOnTabSelectedListener(ViewPager viewPager) { - mViewPager = viewPager; - } - - @Override - public void onTabSelected(VerticalTabLayout.Tab tab) { - mViewPager.setCurrentItem(tab.getPosition()); - } - - @Override - public void onTabUnselected(VerticalTabLayout.Tab tab) { - // No-op - } - - @Override - public void onTabReselected(VerticalTabLayout.Tab tab) { - // No-op - } - } - - private class PagerAdapterObserver extends DataSetObserver { - PagerAdapterObserver() { - } - - @Override - public void onChanged() { - populateFromPagerAdapter(); - } - - @Override - public void onInvalidated() { - populateFromPagerAdapter(); - } - } - - private class AdapterChangeListener implements ViewPager.OnAdapterChangeListener { - private boolean mAutoRefresh; - - AdapterChangeListener() { - } - - @Override - public void onAdapterChanged(@NonNull ViewPager viewPager, - @Nullable PagerAdapter oldAdapter, @Nullable PagerAdapter newAdapter) { - if (mViewPager == viewPager) { - setPagerAdapter((ViewPagerAdapter) newAdapter, mAutoRefresh); - } - } - - void setAutoRefresh(boolean autoRefresh) { - mAutoRefresh = autoRefresh; - } - } - - public static class ViewPagerAdapter extends FragmentPagerAdapter { - List viewPagerList = new ArrayList<>(); - - public ViewPagerAdapter(FragmentManager fragmentManager) { - super(fragmentManager); - } - - @Override - public Fragment getItem(int position) { - return viewPagerList.get(position).fragment; - } - - @Override - public int getCount() { - return viewPagerList.size(); - } - - public int getIcon(int position) { - return viewPagerList.get(position).icon; - } - - @Override - public CharSequence getPageTitle(int position) { - return viewPagerList.get(position).title; - } - - public void addFragment(Fragment fragment, int icon, String name) { - ViewPagerItem item = new ViewPagerItem(); - item.fragment = fragment; - item.icon = icon; - item.title = name; - viewPagerList.add(item); - } - - public void setFragment(int index, Fragment fragment, int icon, String name) { - ViewPagerItem item = new ViewPagerItem(); - item.fragment = fragment; - item.icon = icon; - item.title = name; - viewPagerList.set(index, item); - } - - public void removeFragment(int index) { - viewPagerList.remove(index); - } - } - - public static class ViewPagerItem { - public Fragment fragment; - public String title; - public int icon; - } -} diff --git a/app_pojavlauncher/src/main/java/com/kdt/DefocusableScrollView.java b/app_pojavlauncher/src/main/java/com/kdt/DefocusableScrollView.java index 9bbde79b4..049a46b98 100644 --- a/app_pojavlauncher/src/main/java/com/kdt/DefocusableScrollView.java +++ b/app_pojavlauncher/src/main/java/com/kdt/DefocusableScrollView.java @@ -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); } diff --git a/app_pojavlauncher/src/main/java/com/kdt/LoggerView.java b/app_pojavlauncher/src/main/java/com/kdt/LoggerView.java new file mode 100644 index 000000000..b77ffeb68 --- /dev/null +++ b/app_pojavlauncher/src/main/java/com/kdt/LoggerView.java @@ -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); + } + +} diff --git a/app_pojavlauncher/src/main/java/com/kdt/mcgui/MineButton.java b/app_pojavlauncher/src/main/java/com/kdt/mcgui/MineButton.java index d5cbc7042..6b17cd7a1 100644 --- a/app_pojavlauncher/src/main/java/com/kdt/mcgui/MineButton.java +++ b/app_pojavlauncher/src/main/java/com/kdt/mcgui/MineButton.java @@ -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); diff --git a/app_pojavlauncher/src/main/java/com/kdt/mcgui/MineEditText.java b/app_pojavlauncher/src/main/java/com/kdt/mcgui/MineEditText.java index 47e83cc9d..5b1bc75c3 100644 --- a/app_pojavlauncher/src/main/java/com/kdt/mcgui/MineEditText.java +++ b/app_pojavlauncher/src/main/java/com/kdt/mcgui/MineEditText.java @@ -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); } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/AWTCanvasView.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/AWTCanvasView.java index f9183f1b9..7c8bb19b5 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/AWTCanvasView.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/AWTCanvasView.java @@ -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 times = new LinkedList(){{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 mTimes = new LinkedList(){{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); + } + } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/AWTInputBridge.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/AWTInputBridge.java index 60070e895..7d5aa6a65 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/AWTInputBridge.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/AWTInputBridge.java @@ -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); diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/AWTInputEvent.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/AWTInputEvent.java index de924007d..58217ec26 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/AWTInputEvent.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/AWTInputEvent.java @@ -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 diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/BaseActivity.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/BaseActivity.java index 1b7710b3a..04347eeea 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/BaseActivity.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/BaseActivity.java @@ -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 diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/BaseLauncherActivity.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/BaseLauncherActivity.java index 064be7298..645331835 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/BaseLauncherActivity.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/BaseLauncherActivity.java @@ -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 releaseTable = (Map)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 value, Spinner mVersionSelector, String defaultSelection) { + if(value != null && value.size() > 0) { + ArrayAdapter adapter = new ArrayAdapter(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); diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/BaseMainActivity.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/BaseMainActivity.java index c7fc40c1b..7531e2154 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/BaseMainActivity.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/BaseMainActivity.java @@ -1,719 +1,176 @@ package net.kdt.pojavlaunch; import static net.kdt.pojavlaunch.Architecture.ARCH_X86; +import static net.kdt.pojavlaunch.Tools.currentDisplayMetrics; +import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_VIRTUAL_MOUSE_START; +import static org.lwjgl.glfw.CallbackBridge.sendKeyPress; import static org.lwjgl.glfw.CallbackBridge.windowHeight; import static org.lwjgl.glfw.CallbackBridge.windowWidth; import android.app.*; import android.content.*; import android.content.pm.PackageManager; -import android.graphics.*; +import android.content.res.Configuration; import android.os.*; import android.util.*; import android.view.*; -import android.view.View.*; import android.widget.*; +import androidx.annotation.NonNull; import androidx.drawerlayout.widget.*; -import com.google.android.material.navigation.*; + +import com.kdt.LoggerView; + import java.io.*; import java.util.*; import net.kdt.pojavlaunch.customcontrols.*; +import net.kdt.pojavlaunch.customcontrols.keyboard.LwjglCharSender; +import net.kdt.pojavlaunch.customcontrols.keyboard.TouchCharInput; import net.kdt.pojavlaunch.multirt.MultiRTUtils; -import net.kdt.pojavlaunch.customcontrols.gamepad.Gamepad; - 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.lwjgl.glfw.*; -public class BaseMainActivity extends LoggableActivity { +public class BaseMainActivity extends BaseActivity { public static volatile ClipboardManager GLOBAL_CLIPBOARD; public static TouchCharInput touchCharInput; volatile public static boolean isInputStackCall; - private static final int[] hotbarKeys = { - LWJGLGLFWKeycode.GLFW_KEY_1, LWJGLGLFWKeycode.GLFW_KEY_2, LWJGLGLFWKeycode.GLFW_KEY_3, - LWJGLGLFWKeycode.GLFW_KEY_4, LWJGLGLFWKeycode.GLFW_KEY_5, LWJGLGLFWKeycode.GLFW_KEY_6, - LWJGLGLFWKeycode.GLFW_KEY_7, LWJGLGLFWKeycode.GLFW_KEY_8, LWJGLGLFWKeycode.GLFW_KEY_9}; - - private Gamepad gamepad; - - private static boolean rightOverride = false; - private DisplayMetrics displayMetrics; public float scaleFactor = 1; - public double sensitivityFactor; - private final int fingerStillThreshold = (int) Tools.dpToPx(9); - private float initialX, initialY; - private int scrollInitialX, scrollInitialY; - private float prevX, prevY; - private int currentPointerID; private boolean mIsResuming = false; - private static final int MSG_LEFT_MOUSE_BUTTON_CHECK = 1028; - private static final int MSG_DROP_ITEM_BUTTON_CHECK = 1029; - private static boolean triggeredLeftMouseButton = false; - private final Handler theHandler = new Handler() { - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_LEFT_MOUSE_BUTTON_CHECK: - if(LauncherPreferences.PREF_DISABLE_GESTURES) break; - int x = CallbackBridge.mouseX; - int y = CallbackBridge.mouseY; - if (CallbackBridge.isGrabbing() && - Math.abs(initialX - x) < fingerStillThreshold && - Math.abs(initialY - y) < fingerStillThreshold) { - triggeredLeftMouseButton = true; - sendMouseButton(LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_LEFT, true); - } - break; - case MSG_DROP_ITEM_BUTTON_CHECK: - sendKeyPress(LWJGLGLFWKeycode.GLFW_KEY_Q); - theHandler.sendEmptyMessageDelayed(MSG_DROP_ITEM_BUTTON_CHECK, 600); - break; - } - } - }; + ControlLayout mControlLayout; + private MinecraftGLSurface minecraftGLView; + private static Touchpad touchpad; + private LoggerView loggerView; - private MinecraftGLView minecraftGLView; - private int guiScale; - - - private static boolean isVirtualMouseEnabled; - private static LinearLayout touchPad; - private ImageView mousePointer; - private MinecraftAccount mProfile; + MinecraftAccount mProfile; + MinecraftProfile minecraftProfile; private DrawerLayout drawerLayout; - private NavigationView navDrawer; + private ListView navDrawer; - private LinearLayout contentLog; - private TextView textLog; - private ScrollView contentScroll; - private ToggleButton toggleLog; - private GestureDetector gestureDetector; - private DoubleTapDetector doubleTapDetector; - - private TextView debugText; - private NavigationView.OnNavigationItemSelectedListener gameActionListener; - public NavigationView.OnNavigationItemSelectedListener ingameControlsEditorListener; + private ArrayAdapter gameActionArrayAdapter; + private AdapterView.OnItemClickListener gameActionClickListener; + public ArrayAdapter ingameControlsEditorArrayAdapter; + public AdapterView.OnItemClickListener ingameControlsEditorListener; + //public NavigationView.OnNavigationItemSelectedListener ingameControlsEditorListener; protected volatile JMinecraftVersionList.Version mVersionInfo; - private View.OnTouchListener glTouchListener; - - private File logFile; - private PrintStream logStream; - private PerVersionConfig.VersionConfig config; - private final boolean lastEnabled = false; - private boolean lastGrab = false; - private final boolean isExited = false; - private boolean isLogAllow = false; - - public volatile float mouse_x, mouse_y; - - // @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - } + //private PerVersionConfig.VersionConfig config; protected void initLayout(int resId) { setContentView(resId); - try { - + Logger.getInstance().reset(); // FIXME: is it safe fot multi thread? GLOBAL_CLIPBOARD = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); - touchCharInput = findViewById(R.id.editTextTextPersonName2); - - logFile = new File(Tools.DIR_GAME_HOME, "latestlog.txt"); - logFile.delete(); - logFile.createNewFile(); - logStream = new PrintStream(logFile.getAbsolutePath()); + touchCharInput = findViewById(R.id.mainTouchCharInput); + touchCharInput.setCharacterSender(new LwjglCharSender()); + loggerView = findViewById(R.id.mainLoggerView); + mControlLayout = findViewById(R.id.main_control_layout); mProfile = PojavProfile.getCurrentProfileContent(this); - mVersionInfo = Tools.getVersionInfo(null,mProfile.selectedVersion); - - setTitle("Minecraft " + mProfile.selectedVersion); - PerVersionConfig.update(); - config = PerVersionConfig.configMap.get(mProfile.selectedVersion); + minecraftProfile = LauncherProfiles.mainProfileJson.profiles.get(LauncherPreferences.DEFAULT_PREF.getString(LauncherPreferences.PREF_KEY_CURRENT_PROFILE,"")); + if(minecraftProfile == null) { + Toast.makeText(this,"Attempted to launch nonexistent profile",Toast.LENGTH_SHORT).show(); + finish(); + return; + } String runtime = LauncherPreferences.PREF_DEFAULT_RUNTIME; - if(config != null) { - if(config.selectedRuntime != null) { - if(MultiRTUtils.forceReread(config.selectedRuntime).versionString != null) { - runtime = config.selectedRuntime; + LauncherProfiles.update(); + + mVersionInfo = Tools.getVersionInfo(null, BaseLauncherActivity.getVersionId( + minecraftProfile.lastVersionId)); + if(minecraftProfile.javaDir != null && minecraftProfile.javaDir.startsWith(Tools.LAUNCHERPROFILES_RTPREFIX)) { + String runtimeName = minecraftProfile.javaDir.substring(Tools.LAUNCHERPROFILES_RTPREFIX.length()); + if(MultiRTUtils.forceReread(runtimeName).versionString != null) { + runtime = runtimeName; } } - if(config.renderer != null) { - Tools.LOCAL_RENDERER = config.renderer; + if(minecraftProfile.pojavRendererName != null) { + Log.i("RdrDebug","__P_renderer="+minecraftProfile.pojavRendererName); + Tools.LOCAL_RENDERER = minecraftProfile.pojavRendererName; } - } + + setTitle("Minecraft " + minecraftProfile.lastVersionId); + MultiRTUtils.setRuntimeNamed(this,runtime); // Minecraft 1.13+ isInputStackCall = mVersionInfo.arguments != null; - displayMetrics = Tools.getDisplayMetrics(this); - sensitivityFactor = 1.4 * (1080f/ displayMetrics.heightPixels); - windowWidth = Tools.getDisplayFriendlyRes(displayMetrics.widthPixels, scaleFactor); - windowHeight = Tools.getDisplayFriendlyRes(displayMetrics.heightPixels, scaleFactor); + Tools.getDisplayMetrics(this); + windowWidth = Tools.getDisplayFriendlyRes(currentDisplayMetrics.widthPixels, scaleFactor); + windowHeight = Tools.getDisplayFriendlyRes(currentDisplayMetrics.heightPixels, scaleFactor); System.out.println("WidthHeight: " + windowWidth + ":" + windowHeight); - - gestureDetector = new GestureDetector(this, new SingleTapConfirm()); - doubleTapDetector = new DoubleTapDetector(); - // Menu drawerLayout = findViewById(R.id.main_drawer_options); navDrawer = findViewById(R.id.main_navigation_view); - gameActionListener = menuItem -> { - switch (menuItem.getItemId()) { - case R.id.nav_forceclose: dialogForceClose(BaseMainActivity.this); + + gameActionArrayAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, getResources().getStringArray(R.array.menu_ingame)); + gameActionClickListener = (parent, view, position, id) -> { + switch(position) { + case 0: + dialogForceClose(BaseMainActivity.this); break; - case R.id.nav_viewlog: openLogOutput(); + case 1: + openLogOutput(); break; - case R.id.nav_debug: toggleDebug(); + case 2: + minecraftGLView.togglepointerDebugging(); break; - case R.id.nav_customkey: dialogSendCustomKey(); + case 3: + dialogSendCustomKey(); break; - case R.id.nav_mousespd: adjustMouseSpeedLive(); - break; - case R.id.nav_customctrl: openCustomControls(); + case 4: + adjustMouseSpeedLive(); break; + case 5: + openCustomControls(); } - drawerLayout.closeDrawers(); - return true; }; - navDrawer.setNavigationItemSelectedListener(gameActionListener); + navDrawer.setAdapter(gameActionArrayAdapter); + navDrawer.setOnItemClickListener(gameActionClickListener); - 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 = findViewById(R.id.main_touchpad); - this.contentLog = findViewById(R.id.content_log_layout); - this.contentScroll = findViewById(R.id.content_log_scroll); - this.textLog = (TextView) contentScroll.getChildAt(0); - this.toggleLog = findViewById(R.id.content_log_toggle_log); - this.toggleLog.setChecked(false); - - this.textLog.setTypeface(Typeface.MONOSPACE); - this.toggleLog.setOnCheckedChangeListener((button, isChecked) -> { - isLogAllow = isChecked; - appendToLog(""); - }); - - this.debugText = findViewById(R.id.content_text_debug); this.minecraftGLView = findViewById(R.id.main_game_render_view); this.drawerLayout.closeDrawers(); - placeMouseAt(CallbackBridge.physicalWidth / 2, CallbackBridge.physicalHeight / 2); - Thread virtualMouseGrabThread = new Thread(() -> { - while (!isExited) { - if (lastGrab != CallbackBridge.isGrabbing()) - mousePointer.post(() -> { - if (!CallbackBridge.isGrabbing() && isVirtualMouseEnabled) { - touchPad.setVisibility(View.VISIBLE); - placeMouseAt(displayMetrics.widthPixels / 2, displayMetrics.heightPixels / 2); - } - - if (CallbackBridge.isGrabbing() && touchPad.getVisibility() != View.GONE) { - touchPad.setVisibility(View.GONE); - } - - lastGrab = CallbackBridge.isGrabbing(); - }); + minecraftGLView.setSurfaceReadyListener(() -> { + try { + // Setup virtual mouse right before launching + if (PREF_VIRTUAL_MOUSE_START) + touchpad.switchState(); + runCraft(); + }catch (Throwable e){ + Tools.showError(getApplicationContext(), e, true); } - }, "VirtualMouseGrabThread"); - virtualMouseGrabThread.setPriority(Thread.MIN_PRIORITY); - virtualMouseGrabThread.start(); - - if (isAndroid8OrHigher()) { - touchPad.setDefaultFocusHighlightEnabled(false); - } - touchPad.setOnTouchListener((v, 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()) { - minecraftGLView.dispatchTouchEvent(MotionEvent.obtain(event)); - System.out.println("Transitioned event" + event.hashCode() + " to MinecraftGLView"); - return false; - } - int action = event.getActionMasked(); - - float x = event.getX(); - float y = event.getY(); - float mouseX = mousePointer.getX(); - float mouseY = mousePointer.getY(); - - if (gestureDetector.onTouchEvent(event)) { - mouse_x = (mouseX * scaleFactor); - mouse_y = (mouseY * scaleFactor); - CallbackBridge.sendCursorPos(mouse_x, mouse_y); - CallbackBridge.sendMouseKeycode(rightOverride ? LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_RIGHT : LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_LEFT); - - - - } else { - switch (action) { - case MotionEvent.ACTION_POINTER_DOWN: // 5 - scrollInitialX = CallbackBridge.mouseX; - scrollInitialY = CallbackBridge.mouseY; - break; - - case MotionEvent.ACTION_DOWN: - prevX = x; - prevY = y; - currentPointerID = event.getPointerId(0); - break; - - case MotionEvent.ACTION_MOVE: // 2 - - if (!CallbackBridge.isGrabbing() && event.getPointerCount() == 2 && !LauncherPreferences.PREF_DISABLE_GESTURES) { //Scrolling feature - CallbackBridge.sendScroll( Tools.pxToDp(CallbackBridge.mouseX - scrollInitialX)/30, Tools.pxToDp(CallbackBridge.mouseY - scrollInitialY)/30); - scrollInitialX = CallbackBridge.mouseX; - scrollInitialY = CallbackBridge.mouseY; - } else { - if(currentPointerID == event.getPointerId(0)) { - mouseX = Math.max(0, Math.min(displayMetrics.widthPixels, mouseX + (x - prevX) * LauncherPreferences.PREF_MOUSESPEED)); - mouseY = Math.max(0, Math.min(displayMetrics.heightPixels, mouseY + (y - prevY) * LauncherPreferences.PREF_MOUSESPEED)); - mouse_x = (mouseX * scaleFactor); - mouse_y = (mouseY * scaleFactor); - placeMouseAt(mouseX, mouseY); - CallbackBridge.sendCursorPos(mouse_x, mouse_y); - }else currentPointerID = event.getPointerId(0); - - prevX = x; - prevY = y; - } - break; - } - } - - debugText.setText(CallbackBridge.DEBUG_STRING.toString()); - CallbackBridge.DEBUG_STRING.setLength(0); - - return true; }); + minecraftGLView.start(); - - glTouchListener = new OnTouchListener(){ - private boolean isTouchInHotbar = false; - private int lastHotbarKey = -1; - /* - * Tells if a double tap happened [MOUSE GRAB ONLY]. Doesn't tell where though. - */ - private boolean hasDoubleTapped = false; - /* - * Events can start with only a move instead of an pointerDown - * It is due to the mouse passthrough option bundled with the control button. - */ - private boolean shouldBeDown = false; - /* - * When the android system has fingers really near to each other, it tends to - * either swap or remove a pointer ! - * This variable is here to mitigate the issue. - */ - private int lastPointerCount = 0; - @Override - public boolean onTouch(View p1, 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) { - - if(CallbackBridge.isGrabbing()) return false; - CallbackBridge.sendCursorPos( e.getX(i) * scaleFactor, - e.getY(i) * scaleFactor); - return true; //mouse event handled successfully - } - } - - // System.out.println("Pre touch, isTouchInHotbar=" + Boolean.toString(isTouchInHotbar) + ", action=" + MotionEvent.actionToString(e.getActionMasked())); - - //Getting scaled position from the event - if(!CallbackBridge.isGrabbing()) { - hasDoubleTapped = false; - mouse_x = (e.getX() * scaleFactor); - mouse_y = (e.getY() * scaleFactor); - //One android click = one MC click - if(gestureDetector.onTouchEvent(e)){ - CallbackBridge.putMouseEventWithCoords(rightOverride ? (byte) 1 : (byte) 0, (int)mouse_x, (int)mouse_y); - return true; - } - }else{ - hasDoubleTapped = doubleTapDetector.onTouchEvent(e); - } - - switch (e.getActionMasked()) { - case MotionEvent.ACTION_DOWN: // 0 - //shouldBeDown = true; - CallbackBridge.sendPrepareGrabInitialPos(); - - currentPointerID = e.getPointerId(0); - CallbackBridge.sendCursorPos(mouse_x, mouse_y); - prevX = e.getX(); - prevY = e.getY(); - - int hudKeyHandled; - hudKeyHandled = handleGuiBar((int)e.getX(), (int) e.getY()); - isTouchInHotbar = hudKeyHandled != -1; - if (isTouchInHotbar) { - sendKeyPress(hudKeyHandled); - if(hasDoubleTapped && hudKeyHandled == lastHotbarKey){ - //Prevent double tapping Event on two different slots - sendKeyPress(LWJGLGLFWKeycode.GLFW_KEY_F); - } - - theHandler.sendEmptyMessageDelayed(BaseMainActivity.MSG_DROP_ITEM_BUTTON_CHECK, 350); - CallbackBridge.sendCursorPos(mouse_x, mouse_y); - lastHotbarKey = hudKeyHandled; - break; - } - - - if (CallbackBridge.isGrabbing()) { - // It cause hold left mouse while moving camera - initialX = mouse_x; - initialY = mouse_y; - if(!isTouchInHotbar) theHandler.sendEmptyMessageDelayed(BaseMainActivity.MSG_LEFT_MOUSE_BUTTON_CHECK, LauncherPreferences.PREF_LONGPRESS_TRIGGER); - } - lastHotbarKey = hudKeyHandled; - break; - - case MotionEvent.ACTION_UP: // 1 - case MotionEvent.ACTION_CANCEL: // 3 - shouldBeDown = false; - currentPointerID = -1; - - if (CallbackBridge.isGrabbing()) { - if (!isTouchInHotbar && !triggeredLeftMouseButton && Math.abs(initialX - mouse_x) < fingerStillThreshold && Math.abs(initialY - mouse_y) < fingerStillThreshold) { - if (!LauncherPreferences.PREF_DISABLE_GESTURES) { - sendMouseButton(LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_RIGHT, true); - sendMouseButton(LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_RIGHT, false); - } - } - if (!isTouchInHotbar) { - if (triggeredLeftMouseButton) sendMouseButton(LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_LEFT, false); - - triggeredLeftMouseButton = false; - theHandler.removeMessages(BaseMainActivity.MSG_LEFT_MOUSE_BUTTON_CHECK); - } else { - sendKeyPress(LWJGLGLFWKeycode.GLFW_KEY_Q, 0, false); - theHandler.removeMessages(MSG_DROP_ITEM_BUTTON_CHECK); - } - } - - break; - - case MotionEvent.ACTION_POINTER_DOWN: // 5 - scrollInitialX = CallbackBridge.mouseX; - scrollInitialY = CallbackBridge.mouseY; - //Checking if we are pressing the hotbar to select the item - hudKeyHandled = handleGuiBar((int)e.getX(e.getPointerCount()-1), (int) e.getY(e.getPointerCount()-1)); - if(hudKeyHandled != -1){ - sendKeyPress(hudKeyHandled); - if(hasDoubleTapped && hudKeyHandled == lastHotbarKey){ - //Prevent double tapping Event on two different slots - sendKeyPress(LWJGLGLFWKeycode.GLFW_KEY_F); - } - } - - lastHotbarKey = hudKeyHandled; - break; - - case MotionEvent.ACTION_MOVE: - if (!CallbackBridge.isGrabbing() && e.getPointerCount() == 2 && !LauncherPreferences.PREF_DISABLE_GESTURES) { //Scrolling feature - CallbackBridge.sendScroll(Tools.pxToDp(mouse_x - scrollInitialX)/30 , Tools.pxToDp(mouse_y - scrollInitialY)/30); - scrollInitialX = (int)mouse_x; - scrollInitialY = (int)mouse_y; - } else if (!CallbackBridge.isGrabbing() && e.getPointerCount() == 1) { //Touch hover - CallbackBridge.sendCursorPos(mouse_x, mouse_y); - prevX = e.getX(); - prevY = e.getY(); - } else if (!isTouchInHotbar) { - //Camera movement - if(CallbackBridge.isGrabbing()){ - int pointerIndex = e.findPointerIndex(currentPointerID); - if(pointerIndex == -1 || lastPointerCount != e.getPointerCount() || !shouldBeDown){ - shouldBeDown = true; - currentPointerID = e.getPointerId(0); - prevX = e.getX(); - prevY = e.getY(); - }else{ - mouse_x += (e.getX(pointerIndex) - prevX) * sensitivityFactor; - mouse_y += (e.getY(pointerIndex) - prevY) * sensitivityFactor; - - prevX = e.getX(pointerIndex); - prevY = e.getY(pointerIndex); - - CallbackBridge.sendCursorPos(mouse_x, mouse_y); - } - - } - - - } - lastPointerCount = e.getPointerCount(); - break; - } - - debugText.setText(CallbackBridge.DEBUG_STRING.toString()); - CallbackBridge.DEBUG_STRING.setLength(0); - - return true; - } - }; - - if (isAndroid8OrHigher()) { - minecraftGLView.setDefaultFocusHighlightEnabled(false); - minecraftGLView.setOnCapturedPointerListener(new View.OnCapturedPointerListener() { - //private int x, y; - private boolean debugErrored = false; - - private String getMoving(float pos, boolean xOrY) { - if (pos == 0) return "STOPPED"; - if (pos > 0) return xOrY ? "RIGHT" : "DOWN"; - return xOrY ? "LEFT" : "UP"; - } - - @Override - public boolean onCapturedPointer (View view, MotionEvent e) { - mouse_x += (e.getX()*scaleFactor); - mouse_y += (e.getY()*scaleFactor); - CallbackBridge.mouseX = (int) mouse_x; - CallbackBridge.mouseY = (int) mouse_y; - if(!CallbackBridge.isGrabbing()){ - view.releasePointerCapture(); - view.clearFocus(); - } - - if (debugText.getVisibility() == View.VISIBLE && !debugErrored) { - StringBuilder builder = new StringBuilder(); - try { - builder.append("PointerCapture debug\n"); - builder.append("MotionEvent=").append(e.getActionMasked()).append("\n"); - builder.append("PressingBtn=").append(MotionEvent.class.getDeclaredMethod("buttonStateToString").invoke(null, e.getButtonState())).append("\n\n"); - - builder.append("PointerX=").append(e.getX()).append("\n"); - builder.append("PointerY=").append(e.getY()).append("\n"); - builder.append("RawX=").append(e.getRawX()).append("\n"); - builder.append("RawY=").append(e.getRawY()).append("\n\n"); - - builder.append("XPos=").append(mouse_x).append("\n"); - builder.append("YPos=").append(mouse_y).append("\n\n"); - builder.append("MovingX=").append(getMoving(e.getX(), true)).append("\n"); - builder.append("MovingY=").append(getMoving(e.getY(), false)).append("\n"); - } catch (Throwable th) { - debugErrored = true; - builder.append("Error getting debug. The debug will be stopped!\n").append(Log.getStackTraceString(th)); - } finally { - debugText.setText(builder.toString()); - builder.setLength(0); - } - } - debugText.setText(CallbackBridge.DEBUG_STRING.toString()); - CallbackBridge.DEBUG_STRING.setLength(0); - switch (e.getActionMasked()) { - case MotionEvent.ACTION_MOVE: - CallbackBridge.sendCursorPos(mouse_x, mouse_y); - return true; - case MotionEvent.ACTION_BUTTON_PRESS: - return sendMouseButtonUnconverted(e.getActionButton(), true); - case MotionEvent.ACTION_BUTTON_RELEASE: - return sendMouseButtonUnconverted(e.getActionButton(), false); - case MotionEvent.ACTION_SCROLL: - CallbackBridge.sendScroll(e.getAxisValue(MotionEvent.AXIS_HSCROLL), e.getAxisValue(MotionEvent.AXIS_VSCROLL)); - return true; - default: - return false; - } - } - }); - } - minecraftGLView.setOnTouchListener(glTouchListener); - minecraftGLView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener(){ - - private boolean isCalled = false; - @Override - public void onSurfaceTextureAvailable(SurfaceTexture texture, int width, int height) { - scaleFactor = (LauncherPreferences.DEFAULT_PREF.getInt("resolutionRatio",100)/100f); - windowWidth = Tools.getDisplayFriendlyRes(width, scaleFactor); - windowHeight = Tools.getDisplayFriendlyRes(height, scaleFactor); - texture.setDefaultBufferSize(windowWidth, windowHeight); - - //Load Minecraft options: - MCOptionUtils.load(); - MCOptionUtils.set("overrideWidth", String.valueOf(windowWidth)); - MCOptionUtils.set("overrideHeight", String.valueOf(windowHeight)); - MCOptionUtils.save(); - getMcScale(); - // Should we do that? - if (!isCalled) { - isCalled = true; - - JREUtils.setupBridgeWindow(new Surface(texture)); - - new Thread(() -> { - try { - Thread.sleep(200); - runCraft(); - } catch (Throwable e) { - Tools.showError(BaseMainActivity.this, e, true); - } - }, "JVM Main thread").start(); - } - } - - @Override - public boolean onSurfaceTextureDestroyed(SurfaceTexture texture) { - return true; - } - - @Override - public void onSurfaceTextureSizeChanged(SurfaceTexture texture, int width, int height) { - windowWidth = Tools.getDisplayFriendlyRes(width, scaleFactor); - windowHeight = Tools.getDisplayFriendlyRes(height, scaleFactor); - CallbackBridge.sendUpdateWindowSize(windowWidth, windowHeight); - getMcScale(); - } - - @Override - public void onSurfaceTextureUpdated(SurfaceTexture texture) { - texture.setDefaultBufferSize(windowWidth, windowHeight); - } - }); } catch (Throwable e) { Tools.showError(this, e, true); } } - - @Override - public boolean dispatchGenericMotionEvent(MotionEvent ev) { - int mouseCursorIndex = -1; - - if(Gamepad.isGamepadEvent(ev)){ - if(gamepad == null){ - gamepad = new Gamepad(this, ev.getDevice()); - } - - gamepad.update(ev); - return true; - } - - for(int i = 0; i < ev.getPointerCount(); i++) { - if(ev.getToolType(i) == MotionEvent.TOOL_TYPE_MOUSE) { - mouseCursorIndex = i; - break; - } - } - if(mouseCursorIndex == -1) return false; // we cant consoom that, theres no mice! - if(CallbackBridge.isGrabbing()) { - if(BaseMainActivity.isAndroid8OrHigher()){ - minecraftGLView.requestFocus(); - minecraftGLView.requestPointerCapture(); - } - } - switch(ev.getActionMasked()) { - case MotionEvent.ACTION_HOVER_MOVE: - mouse_x = (ev.getX(mouseCursorIndex) * scaleFactor); - mouse_y = (ev.getY(mouseCursorIndex) * scaleFactor); - CallbackBridge.sendCursorPos(mouse_x, mouse_y); - debugText.setText(CallbackBridge.DEBUG_STRING.toString()); - CallbackBridge.DEBUG_STRING.setLength(0); - return true; - case MotionEvent.ACTION_SCROLL: - CallbackBridge.sendScroll((double) ev.getAxisValue(MotionEvent.AXIS_VSCROLL), (double) ev.getAxisValue(MotionEvent.AXIS_HSCROLL)); - return true; - case MotionEvent.ACTION_BUTTON_PRESS: - return sendMouseButtonUnconverted(ev.getActionButton(),true); - case MotionEvent.ACTION_BUTTON_RELEASE: - return sendMouseButtonUnconverted(ev.getActionButton(),false); - default: - return false; - } - - - } - - @Override - public boolean dispatchKeyEvent(KeyEvent event) { - //Toast.makeText(this, event.toString(),Toast.LENGTH_SHORT).show(); - //Toast.makeText(this, event.getDevice().toString(), Toast.LENGTH_SHORT).show(); - - //Filtering useless events by order of probability - if((event.getFlags() & KeyEvent.FLAG_FALLBACK) == KeyEvent.FLAG_FALLBACK) return true; - int eventKeycode = event.getKeyCode(); - if(eventKeycode == KeyEvent.KEYCODE_UNKNOWN) return true; - if(eventKeycode == KeyEvent.KEYCODE_VOLUME_DOWN) return false; - if(eventKeycode == KeyEvent.KEYCODE_VOLUME_UP) return false; - if(event.getRepeatCount() != 0) return true; - if(event.getAction() == KeyEvent.ACTION_MULTIPLE) return true; - - //Toast.makeText(this, "FIRST VERIF PASSED", Toast.LENGTH_SHORT).show(); - - //Sometimes, key events comes from SOME keys of the software keyboard - //Even weirder, is is unknown why a key or another is selected to trigger a keyEvent - if((event.getFlags() & KeyEvent.FLAG_SOFT_KEYBOARD) == KeyEvent.FLAG_SOFT_KEYBOARD){ - if(eventKeycode == KeyEvent.KEYCODE_ENTER) return true; //We already listen to it. - touchCharInput.dispatchKeyEvent(event); - return true; - } - //Toast.makeText(this, "SECOND VERIF PASSED", Toast.LENGTH_SHORT).show(); - - - //Sometimes, key events may come from the mouse - if(event.getDevice() != null - && ( (event.getSource() & InputDevice.SOURCE_MOUSE_RELATIVE) == InputDevice.SOURCE_MOUSE_RELATIVE - || (event.getSource() & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) ){ - //Toast.makeText(this, "THE EVENT COMES FROM A MOUSE", Toast.LENGTH_SHORT).show(); - - - if(eventKeycode == KeyEvent.KEYCODE_BACK){ - sendMouseButton(LWJGLGLFWKeycode.GLFW_MOUSE_BUTTON_RIGHT, event.getAction() == KeyEvent.ACTION_DOWN); - return true; - } - } - System.out.println(event); - - if(Gamepad.isGamepadEvent(event)){ - if(gamepad == null){ - gamepad = new Gamepad(this, event.getDevice()); - } - - gamepad.update(event); - return true; - } - - int index = EfficientAndroidLWJGLKeycode.getIndexByKey(eventKeycode); - if(index >= 0) { - //Toast.makeText(this,"THIS IS A KEYBOARD EVENT !", Toast.LENGTH_SHORT).show(); - EfficientAndroidLWJGLKeycode.execKey(event, index); - return true; - } - - return false; - } - - @Override public void onResume() { super.onResume(); @@ -721,17 +178,48 @@ public class BaseMainActivity extends LoggableActivity { final int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; final View decorView = getWindow().getDecorView(); decorView.setSystemUiVisibility(uiOptions); + CallbackBridge.nativeSetWindowAttrib(LwjglGlfwKeycode.GLFW_HOVERED, 1); } @Override protected void onPause() { if (CallbackBridge.isGrabbing()){ - sendKeyPress(LWJGLGLFWKeycode.GLFW_KEY_ESCAPE); + sendKeyPress(LwjglGlfwKeycode.GLFW_KEY_ESCAPE); } + CallbackBridge.nativeSetWindowAttrib(LwjglGlfwKeycode.GLFW_HOVERED, 0); mIsResuming = false; super.onPause(); } + @Override + protected void onStart() { + super.onStart(); + CallbackBridge.nativeSetWindowAttrib(LwjglGlfwKeycode.GLFW_VISIBLE, 1); + } + + @Override + protected void onStop() { + CallbackBridge.nativeSetWindowAttrib(LwjglGlfwKeycode.GLFW_VISIBLE, 0); + super.onStop(); + } + + @Override + public void onConfigurationChanged(@NonNull Configuration newConfig) { + super.onConfigurationChanged(newConfig); + + Tools.updateWindowSize(this); + minecraftGLView.refreshSize(); + runOnUiThread(() -> mControlLayout.refreshControlButtonPositions()); + } + + @Override + protected void onPostResume() { + super.onPostResume(); + if(minecraftGLView != null) // Useful when backing out of the app + new Handler(Looper.getMainLooper()).postDelayed(() -> minecraftGLView.refreshSize(), 500); + } + + public static void fullyExit() { android.os.Process.killProcess(android.os.Process.myPid()); } @@ -744,37 +232,40 @@ public class BaseMainActivity extends LoggableActivity { if(Tools.LOCAL_RENDERER == null) { Tools.LOCAL_RENDERER = LauncherPreferences.PREF_RENDERER; } - appendlnToLog("--------- beggining with launcher debug"); - appendlnToLog("Info: Launcher version: " + BuildConfig.VERSION_NAME); + Logger.getInstance().appendToLog("--------- beggining with launcher debug"); + Logger.getInstance().appendToLog("Info: Launcher version: " + BuildConfig.VERSION_NAME); if (Tools.LOCAL_RENDERER.equals("vulkan_zink")) { checkVulkanZinkIsSupported(); } checkLWJGL3Installed(); - jreReleaseList = JREUtils.readJREReleaseProperties(); - JREUtils.checkJavaArchitecture(this, jreReleaseList.get("OS_ARCH")); - checkJavaArgsIsLaunchable(jreReleaseList.get("JAVA_VERSION")); + JREUtils.jreReleaseList = JREUtils.readJREReleaseProperties(); + Logger.getInstance().appendToLog("Architecture: " + Architecture.archAsString(Tools.DEVICE_ARCHITECTURE)); + checkJavaArgsIsLaunchable(JREUtils.jreReleaseList.get("JAVA_VERSION")); // appendlnToLog("Info: Custom Java arguments: \"" + LauncherPreferences.PREF_CUSTOM_JAVA_ARGS + "\""); - appendlnToLog("Info: Selected Minecraft version: " + mVersionInfo.id + + Logger.getInstance().appendToLog("Info: Selected Minecraft version: " + mVersionInfo.id + ((mVersionInfo.inheritsFrom == null || mVersionInfo.inheritsFrom.equals(mVersionInfo.id)) ? "" : " (" + mVersionInfo.inheritsFrom + ")")); - JREUtils.redirectAndPrintJRELog(this); - Tools.launchMinecraft(this, mProfile, mProfile.selectedVersion); + + JREUtils.redirectAndPrintJRELog(); + LauncherProfiles.update(); + Tools.launchMinecraft(this, mProfile, BaseLauncherActivity.getVersionId( + minecraftProfile.lastVersionId)); } private void checkJavaArgsIsLaunchable(String jreVersion) throws Throwable { - appendlnToLog("Info: Custom Java arguments: \"" + LauncherPreferences.PREF_CUSTOM_JAVA_ARGS + "\""); + Logger.getInstance().appendToLog("Info: Custom Java arguments: \"" + LauncherPreferences.PREF_CUSTOM_JAVA_ARGS + "\""); } private void checkLWJGL3Installed() { File lwjgl3dir = new File(Tools.DIR_GAME_HOME, "lwjgl3"); if (!lwjgl3dir.exists() || lwjgl3dir.isFile() || lwjgl3dir.list().length == 0) { - appendlnToLog("Error: LWJGL3 was not installed!"); + Logger.getInstance().appendToLog("Error: LWJGL3 was not installed!"); throw new RuntimeException(getString(R.string.mcn_check_fail_lwjgl)); } else { - appendlnToLog("Info: LWJGL3 directory: " + Arrays.toString(lwjgl3dir.list())); + Logger.getInstance().appendToLog("Info: LWJGL3 directory: " + Arrays.toString(lwjgl3dir.list())); } } @@ -783,7 +274,7 @@ public class BaseMainActivity extends LoggableActivity { || Build.VERSION.SDK_INT < 25 || !getPackageManager().hasSystemFeature(PackageManager.FEATURE_VULKAN_HARDWARE_LEVEL) || !getPackageManager().hasSystemFeature(PackageManager.FEATURE_VULKAN_HARDWARE_VERSION)) { - appendlnToLog("Error: Vulkan Zink renderer is not supported!"); + Logger.getInstance().appendToLog("Error: Vulkan Zink renderer is not supported!"); throw new RuntimeException(getString(R.string. mcn_check_fail_vulkan_support)); } } @@ -793,7 +284,7 @@ public class BaseMainActivity extends LoggableActivity { BufferedReader buffStream = new BufferedReader(new InputStreamReader(stream)); String line = null; while ((line = buffStream.readLine()) != null) { - appendlnToLog(line); + Logger.getInstance().appendToLog(line); } } catch (Exception e) { e.printStackTrace(); @@ -808,10 +299,6 @@ public class BaseMainActivity extends LoggableActivity { return s.toString(); } - private void toggleDebug() { - debugText.setVisibility(debugText.getVisibility() == View.GONE ? View.VISIBLE : View.GONE); - } - private void dialogSendCustomKey() { AlertDialog.Builder dialog = new AlertDialog.Builder(this); dialog.setTitle(R.string.control_customkey); @@ -821,13 +308,12 @@ public class BaseMainActivity extends LoggableActivity { boolean isInEditor; private void openCustomControls() { - if(ingameControlsEditorListener != null) { - ((MainActivity)this).mControlLayout.setModifiable(true); - navDrawer.getMenu().clear(); - navDrawer.inflateMenu(R.menu.menu_customctrl); - navDrawer.setNavigationItemSelectedListener(ingameControlsEditorListener); - isInEditor = true; - } + if(ingameControlsEditorListener == null || ingameControlsEditorArrayAdapter == null) return; + + ((MainActivity)this).mControlLayout.setModifiable(true); + navDrawer.setAdapter(ingameControlsEditorArrayAdapter); + navDrawer.setOnItemClickListener(ingameControlsEditorListener); + isInEditor = true; } public void leaveCustomControls() { @@ -843,57 +329,29 @@ public class BaseMainActivity extends LoggableActivity { } //((MainActivity) this).mControlLayout.loadLayout((CustomControls)null); } - navDrawer.getMenu().clear(); - navDrawer.inflateMenu(R.menu.menu_runopt); - navDrawer.setNavigationItemSelectedListener(gameActionListener); + navDrawer.setAdapter(gameActionArrayAdapter); + navDrawer.setOnItemClickListener(gameActionClickListener); isInEditor = false; } private void openLogOutput() { - contentLog.setVisibility(View.VISIBLE); + loggerView.setVisibility(View.VISIBLE); mIsResuming = false; } public void closeLogOutput(View view) { - contentLog.setVisibility(View.GONE); + loggerView.setVisibility(View.GONE); mIsResuming = true; } - - @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); - }); - } - - public int mcscale(int input) { - return (int)((this.guiScale * input)/scaleFactor); - } - public void toggleMenu(View v) { drawerLayout.openDrawer(Gravity.RIGHT); } - public void placeMouseAdd(float x, float y) { - this.mousePointer.setX(mousePointer.getX() + x); - this.mousePointer.setY(mousePointer.getY() + y); - } - - public void placeMouseAt(float x, float y) { - this.mousePointer.setX(x); - this.mousePointer.setY(y); - } - public static void toggleMouse(Context ctx) { if (CallbackBridge.isGrabbing()) return; - isVirtualMouseEnabled = !isVirtualMouseEnabled; - touchPad.setVisibility(isVirtualMouseEnabled ? View.VISIBLE : View.GONE); - Toast.makeText(ctx, - isVirtualMouseEnabled ? R.string.control_mouseon : R.string.control_mouseoff, + Toast.makeText(ctx, touchpad.switchState() + ? R.string.control_mouseon : R.string.control_mouseoff, Toast.LENGTH_SHORT).show(); } @@ -911,10 +369,13 @@ public class BaseMainActivity extends LoggableActivity { } @Override - public void onBackPressed() { - // Prevent back - // Catch back as Esc keycode at another place - sendKeyPress(LWJGLGLFWKeycode.GLFW_KEY_ESCAPE); + public boolean dispatchKeyEvent(KeyEvent event) { + if (event.getKeyCode() == KeyEvent.KEYCODE_BACK && !touchCharInput.isEnabled()) { + if(event.getAction() != KeyEvent.ACTION_UP) return true; // We eat it anyway + sendKeyPress(LwjglGlfwKeycode.GLFW_KEY_ESCAPE); + return true; + } + return super.dispatchKeyEvent(event); } public static void switchKeyboardState() { @@ -922,85 +383,11 @@ public class BaseMainActivity extends LoggableActivity { } - - - public static void setRightOverride(boolean val) { - rightOverride = val; - } - - public static void sendKeyPress(int keyCode, int modifiers, boolean status) { - sendKeyPress(keyCode, 0, modifiers, status); - } - - public static void sendKeyPress(int keyCode, int scancode, int modifiers, boolean status) { - sendKeyPress(keyCode, '\u0000', scancode, modifiers, status); - } - - public static void sendKeyPress(int keyCode, char keyChar, int scancode, int modifiers, boolean status) { - CallbackBridge.sendKeycode(keyCode, keyChar, scancode, modifiers, status); - } - - public static void sendKeyPress(int keyCode) { - sendKeyPress(keyCode, CallbackBridge.getCurrentMods(), true); - sendKeyPress(keyCode, CallbackBridge.getCurrentMods(), false); - } - - public static void sendMouseButton(int button, boolean status) { - CallbackBridge.sendMouseKeycode(button, CallbackBridge.getCurrentMods(), status); - } - - 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; - } - - public int getMcScale() { - //Get the scale stored in game files, used auto scale if found or if the stored scaled is bigger than the authorized size. - MCOptionUtils.load(); - String str = MCOptionUtils.get("guiScale"); - this.guiScale = (str == null ? 0 :Integer.parseInt(str)); - - - int scale = Math.max(Math.min(windowWidth / 320, windowHeight / 240), 1); - if(scale < this.guiScale || guiScale == 0){ - this.guiScale = scale; - } - - if(gamepad != null) gamepad.notifyGUISizeChange(this.guiScale); - return this.guiScale; - } - - public int handleGuiBar(int x, int y) { - if (!CallbackBridge.isGrabbing()) return -1; - - int barHeight = mcscale(20); - int barWidth = mcscale(180); - int barX = (CallbackBridge.physicalWidth / 2) - (barWidth / 2); - int barY = CallbackBridge.physicalHeight - barHeight; - if (x < barX || x >= barX + barWidth || y < barY || y >= barY + barHeight) { - return -1; - } - return hotbarKeys[((x - barX) / mcscale(180 / 9)) % 9]; - } int tmpMouseSpeed; - public void adjustMouseSpeedLive() { AlertDialog.Builder b = new AlertDialog.Builder(this); b.setTitle(R.string.mcl_setting_title_mousespeed); - View v = LayoutInflater.from(this).inflate(R.layout.live_mouse_speed_editor,null); + View v = LayoutInflater.from(this).inflate(R.layout.dialog_live_mouse_speed_editor,null); final SeekBar sb = v.findViewById(R.id.mouseSpeed); final TextView tv = v.findViewById(R.id.mouseSpeedTV); sb.setMax(275); diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/CustomControlsActivity.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/CustomControlsActivity.java index 92795cd21..d793dc771 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/CustomControlsActivity.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/CustomControlsActivity.java @@ -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(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); } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/CustomTabs.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/CustomTabs.java deleted file mode 100644 index 5f99814ce..000000000 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/CustomTabs.java +++ /dev/null @@ -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)); - } -} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/DoubleTapDetector.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/DoubleTapDetector.java deleted file mode 100644 index e87c893f9..000000000 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/DoubleTapDetector.java +++ /dev/null @@ -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; - } - -} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/EfficientAndroidLWJGLKeycode.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/EfficientAndroidLWJGLKeycode.java index ffb6b0e3f..dfc650ad1 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/EfficientAndroidLWJGLKeycode.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/EfficientAndroidLWJGLKeycode.java @@ -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 ++; + } } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/ExitActivity.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/ExitActivity.java index d65ae238a..1fa7de72d 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/ExitActivity.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/ExitActivity.java @@ -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); + } + } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/FatalErrorActivity.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/FatalErrorActivity.java index c7bb1da41..87dd4f545 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/FatalErrorActivity.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/FatalErrorActivity.java @@ -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); } } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/GLTextureView.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/GLTextureView.java deleted file mode 100644 index 40af9b420..000000000 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/GLTextureView.java +++ /dev/null @@ -1,1821 +0,0 @@ -package net.kdt.pojavlaunch; - -import java.io.Writer; -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import javax.microedition.khronos.egl.EGL10; -import javax.microedition.khronos.egl.EGL11; -import javax.microedition.khronos.egl.EGLConfig; -import javax.microedition.khronos.egl.EGLContext; -import javax.microedition.khronos.egl.EGLDisplay; -import javax.microedition.khronos.egl.EGLSurface; -import javax.microedition.khronos.opengles.GL; -import javax.microedition.khronos.opengles.GL10; -import android.content.Context; -import android.graphics.SurfaceTexture; -import android.opengl.GLDebugHelper; -import android.util.AttributeSet; -import android.util.Log; -import android.view.TextureView; -import android.view.View; - -public class GLTextureView - extends TextureView - implements TextureView.SurfaceTextureListener, - View.OnLayoutChangeListener { - - private final static String TAG = "GLTextureView"; - private final static boolean LOG_ALL = false; - private final static boolean LOG_ATTACH_DETACH = LOG_ALL; - private final static boolean LOG_THREADS = LOG_ALL; - private final static boolean LOG_PAUSE_RESUME = LOG_ALL; - private final static boolean LOG_SURFACE = LOG_ALL; - private final static boolean LOG_RENDERER = LOG_ALL; - private final static boolean LOG_RENDERER_DRAW_FRAME = LOG_ALL; - private final static boolean LOG_EGL = LOG_ALL; - /** - * The renderer only renders - * when the surface is created, or when {@link #requestRender} is called. - * - * @see #getRenderMode() - * @see #setRenderMode(int) - * @see #requestRender() - */ - public final static int RENDERMODE_WHEN_DIRTY = 0; - /** - * The renderer is called - * continuously to re-render the scene. - * - * @see #getRenderMode() - * @see #setRenderMode(int) - */ - public final static int RENDERMODE_CONTINUOUSLY = 1; - - /** - * Check glError() after every GL call and throw an exception if glError indicates - * that an error has occurred. This can be used to help track down which OpenGL ES call - * is causing an error. - * - * @see #getDebugFlags - * @see #setDebugFlags - */ - public final static int DEBUG_CHECK_GL_ERROR = 1; - - /** - * Log GL calls to the system log at "verbose" level with tag "GLTextureView". - * - * @see #getDebugFlags - * @see #setDebugFlags - */ - public final static int DEBUG_LOG_GL_CALLS = 2; - - /** - * Standard View constructor. In order to render something, you - * must call {@link #setRenderer} to register a renderer. - */ - public GLTextureView(Context context) { - super(context); - init(); - } - - /** - * Standard View constructor. In order to render something, you - * must call {@link #setRenderer} to register a renderer. - */ - public GLTextureView(Context context, AttributeSet attrs) { - super(context, attrs); - init(); - } - - @Override - protected void finalize() throws Throwable { - try { - if (mGLThread != null) { - // GLThread may still be running if this view was never - // attached to a window. - mGLThread.requestExitAndWait(); - } - } finally { - super.finalize(); - } - } - - private void init() { - setSurfaceTextureListener(this); - } - - /** - * Set the glWrapper. If the glWrapper is not null, its - * {@link GLWrapper#wrap(javax.microedition.khronos.opengles.GL)} method is called - * whenever a surface is created. A GLWrapper can be used to wrap - * the GL object that's passed to the renderer. Wrapping a GL - * object enables examining and modifying the behavior of the - * GL calls made by the renderer. - *

- * Wrapping is typically used for debugging purposes. - *

- * The default value is null. - * @param glWrapper the new GLWrapper - */ - public void setGLWrapper(GLWrapper glWrapper) { - mGLWrapper = glWrapper; - } - - /** - * Set the debug flags to a new value. The value is - * constructed by OR-together zero or more - * of the DEBUG_CHECK_* constants. The debug flags take effect - * whenever a surface is created. The default value is zero. - * @param debugFlags the new debug flags - * @see #DEBUG_CHECK_GL_ERROR - * @see #DEBUG_LOG_GL_CALLS - */ - public void setDebugFlags(int debugFlags) { - mDebugFlags = debugFlags; - } - - /** - * Get the current value of the debug flags. - * @return the current value of the debug flags. - */ - public int getDebugFlags() { - return mDebugFlags; - } - - /** - * Control whether the EGL context is preserved when the GLTextureView is paused and - * resumed. - *

- * If set to true, then the EGL context may be preserved when the GLTextureView is paused. - * Whether the EGL context is actually preserved or not depends upon whether the - * Android device that the program is running on can support an arbitrary number of EGL - * contexts or not. Devices that can only support a limited number of EGL contexts must - * release the EGL context in order to allow multiple applications to share the GPU. - *

- * If set to false, the EGL context will be released when the GLTextureView is paused, - * and recreated when the GLTextureView is resumed. - *

- * - * The default is false. - * - * @param preserveOnPause preserve the EGL context when paused - */ - public void setPreserveEGLContextOnPause(boolean preserveOnPause) { - mPreserveEGLContextOnPause = preserveOnPause; - } - - /** - * @return true if the EGL context will be preserved when paused - */ - public boolean getPreserveEGLContextOnPause() { - return mPreserveEGLContextOnPause; - } - - /** - * Set the renderer associated with this view. Also starts the thread that - * will call the renderer, which in turn causes the rendering to start. - *

This method should be called once and only once in the life-cycle of - * a GLTextureView. - *

The following GLTextureView methods can only be called before - * setRenderer is called: - *

    - *
  • {@link #setEGLConfigChooser(boolean)} - *
  • {@link #setEGLConfigChooser(EGLConfigChooser)} - *
  • {@link #setEGLConfigChooser(int, int, int, int, int, int)} - *
- *

- * The following GLTextureView methods can only be called after - * setRenderer is called: - *

    - *
  • {@link #getRenderMode()} - *
  • {@link #onPause()} - *
  • {@link #onResume()} - *
  • {@link #queueEvent(Runnable)} - *
  • {@link #requestRender()} - *
  • {@link #setRenderMode(int)} - *
- * - * @param renderer the renderer to use to perform OpenGL drawing. - */ - public void setRenderer(Renderer renderer) { - checkRenderThreadState(); - if (mEGLConfigChooser == null) { - mEGLConfigChooser = new SimpleEGLConfigChooser(true); - } - if (mEGLContextFactory == null) { - mEGLContextFactory = new DefaultContextFactory(); - } - if (mEGLWindowSurfaceFactory == null) { - mEGLWindowSurfaceFactory = new DefaultWindowSurfaceFactory(); - } - mRenderer = renderer; - mGLThread = new GLThread(mThisWeakRef); - mGLThread.start(); - } - - /** - * Install a custom EGLContextFactory. - *

If this method is - * called, it must be called before {@link #setRenderer(Renderer)} - * is called. - *

- * If this method is not called, then by default - * a context will be created with no shared context and - * with a null attribute list. - */ - public void setEGLContextFactory(EGLContextFactory factory) { - checkRenderThreadState(); - mEGLContextFactory = factory; - } - - /** - * Install a custom EGLWindowSurfaceFactory. - *

If this method is - * called, it must be called before {@link #setRenderer(Renderer)} - * is called. - *

- * If this method is not called, then by default - * a window surface will be created with a null attribute list. - */ - public void setEGLWindowSurfaceFactory(EGLWindowSurfaceFactory factory) { - checkRenderThreadState(); - mEGLWindowSurfaceFactory = factory; - } - - /** - * Install a custom EGLConfigChooser. - *

If this method is - * called, it must be called before {@link #setRenderer(Renderer)} - * is called. - *

- * If no setEGLConfigChooser method is called, then by default the - * view will choose an EGLConfig that is compatible with the current - * android.view.Surface, with a depth buffer depth of - * at least 16 bits. - * @param configChooser - */ - public void setEGLConfigChooser(EGLConfigChooser configChooser) { - checkRenderThreadState(); - mEGLConfigChooser = configChooser; - } - - /** - * Install a config chooser which will choose a config - * as close to 16-bit RGB as possible, with or without an optional depth - * buffer as close to 16-bits as possible. - *

If this method is - * called, it must be called before {@link #setRenderer(Renderer)} - * is called. - *

- * If no setEGLConfigChooser method is called, then by default the - * view will choose an RGB_888 surface with a depth buffer depth of - * at least 16 bits. - * - * @param needDepth - */ - public void setEGLConfigChooser(boolean needDepth) { - setEGLConfigChooser(new SimpleEGLConfigChooser(needDepth)); - } - - /** - * Install a config chooser which will choose a config - * with at least the specified depthSize and stencilSize, - * and exactly the specified redSize, greenSize, blueSize and alphaSize. - *

If this method is - * called, it must be called before {@link #setRenderer(Renderer)} - * is called. - *

- * If no setEGLConfigChooser method is called, then by default the - * view will choose an RGB_888 surface with a depth buffer depth of - * at least 16 bits. - * - */ - public void setEGLConfigChooser(int redSize, int greenSize, int blueSize, - int alphaSize, int depthSize, int stencilSize) { - setEGLConfigChooser(new ComponentSizeChooser(redSize, greenSize, - blueSize, alphaSize, depthSize, stencilSize)); - } - - /** - * Inform the default EGLContextFactory and default EGLConfigChooser - * which EGLContext client version to pick. - *

Use this method to create an OpenGL ES 2.0-compatible context. - * Example: - *

-     *     public MyView(Context context) {
-     *         super(context);
-     *         setEGLContextClientVersion(2); // Pick an OpenGL ES 2.0 context.
-     *         setRenderer(new MyRenderer());
-     *     }
-     * 
- *

Note: Activities which require OpenGL ES 2.0 should indicate this by - * setting @lt;uses-feature android:glEsVersion="0x00020000" /> in the activity's - * AndroidManifest.xml file. - *

If this method is called, it must be called before {@link #setRenderer(Renderer)} - * is called. - *

This method only affects the behavior of the default EGLContexFactory and the - * default EGLConfigChooser. If - * {@link #setEGLContextFactory(EGLContextFactory)} has been called, then the supplied - * EGLContextFactory is responsible for creating an OpenGL ES 2.0-compatible context. - * If - * {@link #setEGLConfigChooser(EGLConfigChooser)} has been called, then the supplied - * EGLConfigChooser is responsible for choosing an OpenGL ES 2.0-compatible config. - * @param version The EGLContext client version to choose. Use 2 for OpenGL ES 2.0 - */ - public void setEGLContextClientVersion(int version) { - checkRenderThreadState(); - mEGLContextClientVersion = version; - } - - /** - * Set the rendering mode. When renderMode is - * RENDERMODE_CONTINUOUSLY, the renderer is called - * repeatedly to re-render the scene. When renderMode - * is RENDERMODE_WHEN_DIRTY, the renderer only rendered when the surface - * is created, or when {@link #requestRender} is called. Defaults to RENDERMODE_CONTINUOUSLY. - *

- * Using RENDERMODE_WHEN_DIRTY can improve battery life and overall system performance - * by allowing the GPU and CPU to idle when the view does not need to be updated. - *

- * This method can only be called after {@link #setRenderer(Renderer)} - * - * @param renderMode one of the RENDERMODE_X constants - * @see #RENDERMODE_CONTINUOUSLY - * @see #RENDERMODE_WHEN_DIRTY - */ - public void setRenderMode(int renderMode) { - mGLThread.setRenderMode(renderMode); - } - - /** - * Get the current rendering mode. May be called - * from any thread. Must not be called before a renderer has been set. - * @return the current rendering mode. - * @see #RENDERMODE_CONTINUOUSLY - * @see #RENDERMODE_WHEN_DIRTY - */ - public int getRenderMode() { - return mGLThread.getRenderMode(); - } - - /** - * Request that the renderer render a frame. - * This method is typically used when the render mode has been set to - * {@link #RENDERMODE_WHEN_DIRTY}, so that frames are only rendered on demand. - * May be called - * from any thread. Must not be called before a renderer has been set. - */ - public void requestRender() { - mGLThread.requestRender(); - } - - /** - * This method is part of the SurfaceHolder.Callback interface, and is - * not normally called or subclassed by clients of GLTextureView. - */ - public void surfaceCreated(SurfaceTexture texture) { - mGLThread.surfaceCreated(); - } - - /** - * This method is part of the SurfaceHolder.Callback interface, and is - * not normally called or subclassed by clients of GLTextureView. - */ - public void surfaceDestroyed(SurfaceTexture texture) { - // Surface will be destroyed when we return - mGLThread.surfaceDestroyed(); - } - - /** - * This method is part of the SurfaceHolder.Callback interface, and is - * not normally called or subclassed by clients of GLTextureView. - */ - public void surfaceChanged(SurfaceTexture texture, int format, int w, int h) { - mGLThread.onWindowResize(w, h); - } - - /** - * Inform the view that the activity is paused. The owner of this view must - * call this method when the activity is paused. Calling this method will - * pause the rendering thread. - * Must not be called before a renderer has been set. - */ - public void onPause() { - mGLThread.onPause(); - } - - /** - * Inform the view that the activity is resumed. The owner of this view must - * call this method when the activity is resumed. Calling this method will - * recreate the OpenGL display and resume the rendering - * thread. - * Must not be called before a renderer has been set. - */ - public void onResume() { - mGLThread.onResume(); - } - - /** - * Queue a runnable to be run on the GL rendering thread. This can be used - * to communicate with the Renderer on the rendering thread. - * Must not be called before a renderer has been set. - * @param r the runnable to be run on the GL rendering thread. - */ - public void queueEvent(Runnable r) { - mGLThread.queueEvent(r); - } - - /** - * This method is used as part of the View class and is not normally - * called or subclassed by clients of GLTextureView. - */ - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - if (LOG_ATTACH_DETACH) { - Log.d(TAG, "onAttachedToWindow reattach =" + mDetached); - } - if (mDetached && (mRenderer != null)) { - int renderMode = RENDERMODE_CONTINUOUSLY; - if (mGLThread != null) { - renderMode = mGLThread.getRenderMode(); - } - mGLThread = new GLThread(mThisWeakRef); - if (renderMode != RENDERMODE_CONTINUOUSLY) { - mGLThread.setRenderMode(renderMode); - } - mGLThread.start(); - } - mDetached = false; - } - - /** - * This method is used as part of the View class and is not normally - * called or subclassed by clients of GLTextureView. - * Must not be called before a renderer has been set. - */ - @Override - protected void onDetachedFromWindow() { - if (LOG_ATTACH_DETACH) { - Log.d(TAG, "onDetachedFromWindow"); - } - if (mGLThread != null) { - mGLThread.requestExitAndWait(); - } - mDetached = true; - super.onDetachedFromWindow(); - } - - public void onLayoutChange(View v, int left, int top, int right, int bottom, - int oldLeft, int oldTop, int oldRight, int oldBottom) { - surfaceChanged(getSurfaceTexture(), 0, right - left, bottom - top); - } - - public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { - surfaceCreated(surface); - surfaceChanged(surface, 0, width, height); - } - - public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { - surfaceChanged(surface, 0, width, height); - } - - public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { - // MOD: Code replaced: -/* - surfaceDestroyed(surface); - return true; -*/ - // MOD: Replaced to: - if (sGLThreadManager.shouldTerminateEGLWhenPausing() && !mPreserveEGLContextOnPause) { - surfaceDestroyed(surface); - return true; - } - return false; - } - - public void onSurfaceTextureUpdated(SurfaceTexture surface) { - requestRender(); - } - - // ---------------------------------------------------------------------- - - /** - * An interface used to wrap a GL interface. - *

Typically - * used for implementing debugging and tracing on top of the default - * GL interface. You would typically use this by creating your own class - * that implemented all the GL methods by delegating to another GL instance. - * Then you could add your own behavior before or after calling the - * delegate. All the GLWrapper would do was instantiate and return the - * wrapper GL instance: - *

-     * class MyGLWrapper implements GLWrapper {
-     *     GL wrap(GL gl) {
-     *         return new MyGLImplementation(gl);
-     *     }
-     *     static class MyGLImplementation implements GL,GL10,GL11,... {
-     *         ...
-     *     }
-     * }
-     * 
- * @see #setGLWrapper(GLWrapper) - */ - public interface GLWrapper { - /** - * Wraps a gl interface in another gl interface. - * @param gl a GL interface that is to be wrapped. - * @return either the input argument or another GL object that wraps the input argument. - */ - GL wrap(GL gl); - } - - /** - * A generic renderer interface. - *

- * The renderer is responsible for making OpenGL calls to render a frame. - *

- * GLTextureView clients typically create their own classes that implement - * this interface, and then call {@link GLTextureView#setRenderer} to - * register the renderer with the GLTextureView. - *

- * - *

- *

Developer Guides

- *

For more information about how to use OpenGL, read the - * OpenGL developer guide.

- *
- * - *

Threading

- * The renderer will be called on a separate thread, so that rendering - * performance is decoupled from the UI thread. Clients typically need to - * communicate with the renderer from the UI thread, because that's where - * input events are received. Clients can communicate using any of the - * standard Java techniques for cross-thread communication, or they can - * use the {@link GLTextureView#queueEvent(Runnable)} convenience method. - *

- *

EGL Context Lost

- * There are situations where the EGL rendering context will be lost. This - * typically happens when device wakes up after going to sleep. When - * the EGL context is lost, all OpenGL resources (such as textures) that are - * associated with that context will be automatically deleted. In order to - * keep rendering correctly, a renderer must recreate any lost resources - * that it still needs. The {@link #onSurfaceCreated(javax.microedition.khronos.opengles.GL10, javax.microedition.khronos.egl.EGLConfig)} method - * is a convenient place to do this. - * - * - * @see #setRenderer(Renderer) - */ - public interface Renderer { - /** - * Called when the surface is created or recreated. - *

- * Called when the rendering thread - * starts and whenever the EGL context is lost. The EGL context will typically - * be lost when the Android device awakes after going to sleep. - *

- * Since this method is called at the beginning of rendering, as well as - * every time the EGL context is lost, this method is a convenient place to put - * code to create resources that need to be created when the rendering - * starts, and that need to be recreated when the EGL context is lost. - * Textures are an example of a resource that you might want to create - * here. - *

- * Note that when the EGL context is lost, all OpenGL resources associated - * with that context will be automatically deleted. You do not need to call - * the corresponding "glDelete" methods such as glDeleteTextures to - * manually delete these lost resources. - *

- * @param gl the GL interface. Use instanceof to - * test if the interface supports GL11 or higher interfaces. - * @param config the EGLConfig of the created surface. Can be used - * to create matching pbuffers. - */ - void onSurfaceCreated(GL10 gl, EGLConfig config); - - /** - * Called when the surface changed size. - *

- * Called after the surface is created and whenever - * the OpenGL ES surface size changes. - *

- * Typically you will set your viewport here. If your camera - * is fixed then you could also set your projection matrix here: - *

-         * void onSurfaceChanged(GL10 gl, int width, int height) {
-         *     gl.glViewport(0, 0, width, height);
-         *     // for a fixed camera, set the projection too
-         *     float ratio = (float) width / height;
-         *     gl.glMatrixMode(GL10.GL_PROJECTION);
-         *     gl.glLoadIdentity();
-         *     gl.glFrustumf(-ratio, ratio, -1, 1, 1, 10);
-         * }
-         * 
- * @param gl the GL interface. Use instanceof to - * test if the interface supports GL11 or higher interfaces. - * @param width - * @param height - */ - void onSurfaceChanged(GL10 gl, int width, int height); - - /** - * Called to draw the current frame. - *

- * This method is responsible for drawing the current frame. - *

- * The implementation of this method typically looks like this: - *

-         * void onDrawFrame(GL10 gl) {
-         *     gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
-         *     //... other gl calls to render the scene ...
-         * }
-         * 
- * @param gl the GL interface. Use instanceof to - * test if the interface supports GL11 or higher interfaces. - */ - void onDrawFrame(GL10 gl); - - void onSurfaceDestroyed(GL10 gl); - } - - /** - * An interface for customizing the eglCreateContext and eglDestroyContext calls. - *

- * This interface must be implemented by clients wishing to call - * {@link GLTextureView#setEGLContextFactory(EGLContextFactory)} - */ - public interface EGLContextFactory { - EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig eglConfig); - void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context); - } - - private class DefaultContextFactory implements EGLContextFactory { - private int EGL_CONTEXT_CLIENT_VERSION = 0x3098; - - public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig config) { - int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, mEGLContextClientVersion, - EGL10.EGL_NONE }; - - return egl.eglCreateContext(display, config, EGL10.EGL_NO_CONTEXT, - mEGLContextClientVersion != 0 ? attrib_list : null); - } - - public void destroyContext(EGL10 egl, EGLDisplay display, - EGLContext context) { - if (!egl.eglDestroyContext(display, context)) { - Log.e("DefaultContextFactory", "display:" + display + " context: " + context); - if (LOG_THREADS) { - Log.i("DefaultContextFactory", "tid=" + Thread.currentThread().getId()); - } - EglHelper.throwEglException("eglDestroyContex", egl.eglGetError()); - } - } - } - - /** - * An interface for customizing the eglCreateWindowSurface and eglDestroySurface calls. - *

- * This interface must be implemented by clients wishing to call - * {@link GLTextureView#setEGLWindowSurfaceFactory(EGLWindowSurfaceFactory)} - */ - public interface EGLWindowSurfaceFactory { - /** - * @return null if the surface cannot be constructed. - */ - EGLSurface createWindowSurface(EGL10 egl, EGLDisplay display, EGLConfig config, - Object nativeWindow); - void destroySurface(EGL10 egl, EGLDisplay display, EGLSurface surface); - } - - private static class DefaultWindowSurfaceFactory implements EGLWindowSurfaceFactory { - - public EGLSurface createWindowSurface(EGL10 egl, EGLDisplay display, - EGLConfig config, Object nativeWindow) { - EGLSurface result = null; - try { - result = egl.eglCreateWindowSurface(display, config, nativeWindow, null); - } catch (IllegalArgumentException e) { - // This exception indicates that the surface flinger surface - // is not valid. This can happen if the surface flinger surface has - // been torn down, but the application has not yet been - // notified via SurfaceHolder.Callback.surfaceDestroyed. - // In theory the application should be notified first, - // but in practice sometimes it is not. See b/4588890 - Log.e(TAG, "eglCreateWindowSurface", e); - } - return result; - } - - public void destroySurface(EGL10 egl, EGLDisplay display, - EGLSurface surface) { - egl.eglDestroySurface(display, surface); - } - } - - /** - * An interface for choosing an EGLConfig configuration from a list of - * potential configurations. - *

- * This interface must be implemented by clients wishing to call - * {@link GLTextureView#setEGLConfigChooser(EGLConfigChooser)} - */ - public interface EGLConfigChooser { - /** - * Choose a configuration from the list. Implementors typically - * implement this method by calling - * {@link EGL10#eglChooseConfig} and iterating through the results. Please consult the - * EGL specification available from The Khronos Group to learn how to call eglChooseConfig. - * @param egl the EGL10 for the current display. - * @param display the current display. - * @return the chosen configuration. - */ - EGLConfig chooseConfig(EGL10 egl, EGLDisplay display); - } - - private abstract class BaseConfigChooser - implements EGLConfigChooser { - public BaseConfigChooser(int[] configSpec) { - mConfigSpec = filterConfigSpec(configSpec); - } - - public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) { - int[] num_config = new int[1]; - if (!egl.eglChooseConfig(display, mConfigSpec, null, 0, - num_config)) { - throw new IllegalArgumentException("eglChooseConfig failed"); - } - - int numConfigs = num_config[0]; - - if (numConfigs <= 0) { - throw new IllegalArgumentException( - "No configs match configSpec"); - } - - EGLConfig[] configs = new EGLConfig[numConfigs]; - if (!egl.eglChooseConfig(display, mConfigSpec, configs, numConfigs, - num_config)) { - throw new IllegalArgumentException("eglChooseConfig#2 failed"); - } - EGLConfig config = chooseConfig(egl, display, configs); - if (config == null) { - throw new IllegalArgumentException("No config chosen"); - } - return config; - } - - abstract EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, - EGLConfig[] configs); - - protected int[] mConfigSpec; - - private int[] filterConfigSpec(int[] configSpec) { - if (mEGLContextClientVersion != 2) { - return configSpec; - } - /* We know none of the subclasses define EGL_RENDERABLE_TYPE. - * And we know the configSpec is well formed. - */ - int len = configSpec.length; - int[] newConfigSpec = new int[len + 2]; - // net.kdt.pojavlaunch.SystemCrackResolver.arraycopy(configSpec, 0, newConfigSpec, 0, len-1); - for (int i = 0; i < len; i++) { - newConfigSpec[i] = configSpec[i]; - } - newConfigSpec[len-1] = EGL10.EGL_RENDERABLE_TYPE; - newConfigSpec[len] = 4; /* EGL_OPENGL_ES2_BIT */ - newConfigSpec[len+1] = EGL10.EGL_NONE; - return newConfigSpec; - } - } - - /** - * Choose a configuration with exactly the specified r,g,b,a sizes, - * and at least the specified depth and stencil sizes. - */ - private class ComponentSizeChooser extends BaseConfigChooser { - public ComponentSizeChooser(int redSize, int greenSize, int blueSize, - int alphaSize, int depthSize, int stencilSize) { - super(new int[] { - EGL10.EGL_RED_SIZE, redSize, - EGL10.EGL_GREEN_SIZE, greenSize, - EGL10.EGL_BLUE_SIZE, blueSize, - EGL10.EGL_ALPHA_SIZE, alphaSize, - EGL10.EGL_DEPTH_SIZE, depthSize, - EGL10.EGL_STENCIL_SIZE, stencilSize, - EGL10.EGL_NONE}); - mValue = new int[1]; - mRedSize = redSize; - mGreenSize = greenSize; - mBlueSize = blueSize; - mAlphaSize = alphaSize; - mDepthSize = depthSize; - mStencilSize = stencilSize; - } - - @Override - public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, - EGLConfig[] configs) { - for (EGLConfig config : configs) { - int d = findConfigAttrib(egl, display, config, - EGL10.EGL_DEPTH_SIZE, 0); - int s = findConfigAttrib(egl, display, config, - EGL10.EGL_STENCIL_SIZE, 0); - if ((d >= mDepthSize) && (s >= mStencilSize)) { - int r = findConfigAttrib(egl, display, config, - EGL10.EGL_RED_SIZE, 0); - int g = findConfigAttrib(egl, display, config, - EGL10.EGL_GREEN_SIZE, 0); - int b = findConfigAttrib(egl, display, config, - EGL10.EGL_BLUE_SIZE, 0); - int a = findConfigAttrib(egl, display, config, - EGL10.EGL_ALPHA_SIZE, 0); - if ((r == mRedSize) && (g == mGreenSize) - && (b == mBlueSize) && (a == mAlphaSize)) { - return config; - } - } - } - return null; - } - - private int findConfigAttrib(EGL10 egl, EGLDisplay display, - EGLConfig config, int attribute, int defaultValue) { - - if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) { - return mValue[0]; - } - return defaultValue; - } - - private int[] mValue; - // Subclasses can adjust these values: - protected int mRedSize; - protected int mGreenSize; - protected int mBlueSize; - protected int mAlphaSize; - protected int mDepthSize; - protected int mStencilSize; - } - - /** - * This class will choose a RGB_888 surface with - * or without a depth buffer. - * - */ - private class SimpleEGLConfigChooser extends ComponentSizeChooser { - public SimpleEGLConfigChooser(boolean withDepthBuffer) { - super(8, 8, 8, 0, withDepthBuffer ? 16 : 0, 0); - } - } - - /** - * An EGL helper class. - */ - - private static class EglHelper { - public EglHelper(WeakReference glSurfaceViewWeakRef) { - mGLSurfaceViewWeakRef = glSurfaceViewWeakRef; - } - - /** - * Initialize EGL for a given configuration spec. - * @param - */ - public void start() { - if (LOG_EGL) { - Log.w("EglHelper", "start() tid=" + Thread.currentThread().getId()); - } - /* - * Get an EGL instance - */ - mEgl = (EGL10) EGLContext.getEGL(); - - /* - * Get to the default display. - */ - mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); - - if (mEglDisplay == EGL10.EGL_NO_DISPLAY) { - throw new RuntimeException("eglGetDisplay failed"); - } - - /* - * We can now initialize EGL for that display - */ - int[] version = new int[2]; - if(!mEgl.eglInitialize(mEglDisplay, version)) { - throw new RuntimeException("eglInitialize failed"); - } - GLTextureView view = mGLSurfaceViewWeakRef.get(); - if (view == null) { - mEglConfig = null; - mEglContext = null; - } else { - mEglConfig = view.mEGLConfigChooser.chooseConfig(mEgl, mEglDisplay); - - /* - * Create an EGL context. We want to do this as rarely as we can, because an - * EGL context is a somewhat heavy object. - */ - mEglContext = view.mEGLContextFactory.createContext(mEgl, mEglDisplay, mEglConfig); - } - if (mEglContext == null || mEglContext == EGL10.EGL_NO_CONTEXT) { - mEglContext = null; - throwEglException("createContext"); - } - if (LOG_EGL) { - Log.w("EglHelper", "createContext " + mEglContext + " tid=" + Thread.currentThread().getId()); - } - - mEglSurface = null; - } - - /** - * Create an egl surface for the current SurfaceHolder surface. If a surface - * already exists, destroy it before creating the new surface. - * - * @return true if the surface was created successfully. - */ - public boolean createSurface() { - if (LOG_EGL) { - Log.w("EglHelper", "createSurface() tid=" + Thread.currentThread().getId()); - } - /* - * Check preconditions. - */ - if (mEgl == null) { - throw new RuntimeException("egl not initialized"); - } - if (mEglDisplay == null) { - throw new RuntimeException("eglDisplay not initialized"); - } - if (mEglConfig == null) { - throw new RuntimeException("mEglConfig not initialized"); - } - - /* - * The window size has changed, so we need to create a new - * surface. - */ - destroySurfaceImp(); - - /* - * Create an EGL surface we can render into. - */ - GLTextureView view = mGLSurfaceViewWeakRef.get(); - if (view != null) { - mEglSurface = view.mEGLWindowSurfaceFactory.createWindowSurface(mEgl, - mEglDisplay, mEglConfig, view.getSurfaceTexture()); - } else { - mEglSurface = null; - } - - if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) { - int error = mEgl.eglGetError(); - if (error == EGL10.EGL_BAD_NATIVE_WINDOW) { - Log.e("EglHelper", "createWindowSurface returned EGL_BAD_NATIVE_WINDOW."); - } - return false; - } - - /* - * Before we can issue GL commands, we need to make sure - * the context is current and bound to a surface. - */ - if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) { - /* - * Could not make the context current, probably because the underlying - * SurfaceView surface has been destroyed. - */ - logEglErrorAsWarning("EGLHelper", "eglMakeCurrent", mEgl.eglGetError()); - return false; - } - - return true; - } - - /** - * Create a GL object for the current EGL context. - * @return - */ - GL createGL() { - - GL gl = mEglContext.getGL(); - GLTextureView view = mGLSurfaceViewWeakRef.get(); - if (view != null) { - if (view.mGLWrapper != null) { - gl = view.mGLWrapper.wrap(gl); - } - - if ((view.mDebugFlags & (DEBUG_CHECK_GL_ERROR | DEBUG_LOG_GL_CALLS)) != 0) { - int configFlags = 0; - Writer log = null; - if ((view.mDebugFlags & DEBUG_CHECK_GL_ERROR) != 0) { - configFlags |= GLDebugHelper.CONFIG_CHECK_GL_ERROR; - } - if ((view.mDebugFlags & DEBUG_LOG_GL_CALLS) != 0) { - log = new LogWriter(); - } - gl = GLDebugHelper.wrap(gl, configFlags, log); - } - } - return gl; - } - - /** - * Display the current render surface. - * @return the EGL error code from eglSwapBuffers. - */ - public int swap() { - if (! mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) { - return mEgl.eglGetError(); - } - return EGL10.EGL_SUCCESS; - } - - public void destroySurface() { - if (LOG_EGL) { - Log.w("EglHelper", "destroySurface() tid=" + Thread.currentThread().getId()); - } - destroySurfaceImp(); - } - - private void destroySurfaceImp() { - if (mEglSurface != null && mEglSurface != EGL10.EGL_NO_SURFACE) { - mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE, - EGL10.EGL_NO_SURFACE, - EGL10.EGL_NO_CONTEXT); - GLTextureView view = mGLSurfaceViewWeakRef.get(); - if (view != null) { - view.mEGLWindowSurfaceFactory.destroySurface(mEgl, mEglDisplay, mEglSurface); - } - mEglSurface = null; - } - } - - public void finish() { - if (LOG_EGL) { - Log.w("EglHelper", "finish() tid=" + Thread.currentThread().getId()); - } - if (mEglContext != null) { - GLTextureView view = mGLSurfaceViewWeakRef.get(); - if (view != null) { - view.mEGLContextFactory.destroyContext(mEgl, mEglDisplay, mEglContext); - } - mEglContext = null; - } - if (mEglDisplay != null) { - mEgl.eglTerminate(mEglDisplay); - mEglDisplay = null; - } - } - - private void throwEglException(String function) { - throwEglException(function, mEgl.eglGetError()); - } - - public static void throwEglException(String function, int error) { - String message = formatEglError(function, error); - if (LOG_THREADS) { - Log.e("EglHelper", "throwEglException tid=" + Thread.currentThread().getId() + " " - + message); - } - throw new RuntimeException(message); - } - - public static void logEglErrorAsWarning(String tag, String function, int error) { - Log.w(tag, formatEglError(function, error)); - } - - public static String formatEglError(String function, int error) { - return function + " failed: " + error; - } - - private WeakReference mGLSurfaceViewWeakRef; - EGL10 mEgl; - EGLDisplay mEglDisplay; - EGLSurface mEglSurface; - EGLConfig mEglConfig; - EGLContext mEglContext; - - } - - /** - * A generic GL Thread. Takes care of initializing EGL and GL. Delegates - * to a Renderer instance to do the actual drawing. Can be configured to - * render continuously or on request. - * - * All potentially blocking synchronization is done through the - * sGLThreadManager object. This avoids multiple-lock ordering issues. - * - */ - static class GLThread extends Thread { - GLThread(WeakReference glSurfaceViewWeakRef) { - super(); - mWidth = 0; - mHeight = 0; - mRequestRender = true; - mRenderMode = RENDERMODE_CONTINUOUSLY; - mGLSurfaceViewWeakRef = glSurfaceViewWeakRef; - } - - @Override - public void run() { - setName("GLThread " + getId()); - if (LOG_THREADS) { - Log.i("GLThread", "starting tid=" + getId()); - } - - try { - guardedRun(); - } catch (InterruptedException e) { - // fall thru and exit normally - } finally { - sGLThreadManager.threadExiting(this); - } - } - - /* - * This private method should only be called inside a - * synchronized(sGLThreadManager) block. - */ - private void stopEglSurfaceLocked() { - if (mHaveEglSurface) { - mHaveEglSurface = false; - mEglHelper.destroySurface(); - } - } - - /* - * This private method should only be called inside a - * synchronized(sGLThreadManager) block. - */ - private void stopEglContextLocked() { - if (mHaveEglContext) { - mEglHelper.finish(); - mHaveEglContext = false; - sGLThreadManager.releaseEglContextLocked(this); - } - } - private void guardedRun() throws InterruptedException { - mEglHelper = new EglHelper(mGLSurfaceViewWeakRef); - mHaveEglContext = false; - mHaveEglSurface = false; - try { - GL10 gl = null; - boolean createEglContext = false; - boolean createEglSurface = false; - boolean createGlInterface = false; - boolean lostEglContext = false; - boolean sizeChanged = false; - boolean wantRenderNotification = false; - boolean doRenderNotification = false; - boolean askedToReleaseEglContext = false; - int w = 0; - int h = 0; - Runnable event = null; - - while (true) { - synchronized (sGLThreadManager) { - while (true) { - if (mShouldExit) { - return; - } - - if (! mEventQueue.isEmpty()) { - event = mEventQueue.remove(0); - break; - } - - // Update the pause state. - boolean pausing = false; - if (mPaused != mRequestPaused) { - pausing = mRequestPaused; - mPaused = mRequestPaused; - sGLThreadManager.notifyAll(); - if (LOG_PAUSE_RESUME) { - Log.i("GLThread", "mPaused is now " + mPaused + " tid=" + getId()); - } - } - - // Do we need to give up the EGL context? - if (mShouldReleaseEglContext) { - if (LOG_SURFACE) { - Log.i("GLThread", "releasing EGL context because asked to tid=" + getId()); - } - stopEglSurfaceLocked(); - stopEglContextLocked(); - mShouldReleaseEglContext = false; - askedToReleaseEglContext = true; - } - - // Have we lost the EGL context? - if (lostEglContext) { - stopEglSurfaceLocked(); - stopEglContextLocked(); - lostEglContext = false; - } - - // When pausing, release the EGL surface: - if (pausing && mHaveEglSurface) { - if (LOG_SURFACE) { - Log.i("GLThread", "releasing EGL surface because paused tid=" + getId()); - } - stopEglSurfaceLocked(); - } - - // When pausing, optionally release the EGL Context: - if (pausing && mHaveEglContext) { - GLTextureView view = mGLSurfaceViewWeakRef.get(); - boolean preserveEglContextOnPause = view == null ? - false : view.mPreserveEGLContextOnPause; - if (!preserveEglContextOnPause || sGLThreadManager.shouldReleaseEGLContextWhenPausing()) { - stopEglContextLocked(); - if (LOG_SURFACE) { - Log.i("GLThread", "releasing EGL context because paused tid=" + getId()); - } - } - } - - // When pausing, optionally terminate EGL: - if (pausing) { - if (sGLThreadManager.shouldTerminateEGLWhenPausing()) { - mEglHelper.finish(); - if (LOG_SURFACE) { - Log.i("GLThread", "terminating EGL because paused tid=" + getId()); - } - } - } - - // Have we lost the SurfaceView surface? - if ((! mHasSurface) && (! mWaitingForSurface)) { - if (LOG_SURFACE) { - Log.i("GLThread", "noticed surfaceView surface lost tid=" + getId()); - } - if (mHaveEglSurface) { - stopEglSurfaceLocked(); - } - mWaitingForSurface = true; - mSurfaceIsBad = false; - sGLThreadManager.notifyAll(); - } - - // Have we acquired the surface view surface? - if (mHasSurface && mWaitingForSurface) { - if (LOG_SURFACE) { - Log.i("GLThread", "noticed surfaceView surface acquired tid=" + getId()); - } - mWaitingForSurface = false; - sGLThreadManager.notifyAll(); - } - - if (doRenderNotification) { - if (LOG_SURFACE) { - Log.i("GLThread", "sending render notification tid=" + getId()); - } - wantRenderNotification = false; - doRenderNotification = false; - mRenderComplete = true; - sGLThreadManager.notifyAll(); - } - - // Ready to draw? - if (readyToDraw()) { - - // If we don't have an EGL context, try to acquire one. - if (! mHaveEglContext) { - if (askedToReleaseEglContext) { - askedToReleaseEglContext = false; - } else if (sGLThreadManager.tryAcquireEglContextLocked(this)) { - try { - mEglHelper.start(); - } catch (RuntimeException t) { - sGLThreadManager.releaseEglContextLocked(this); - throw t; - } - mHaveEglContext = true; - createEglContext = true; - - sGLThreadManager.notifyAll(); - } - } - - if (mHaveEglContext && !mHaveEglSurface) { - mHaveEglSurface = true; - createEglSurface = true; - createGlInterface = true; - sizeChanged = true; - } - - if (mHaveEglSurface) { - if (mSizeChanged) { - sizeChanged = true; - w = mWidth; - h = mHeight; - wantRenderNotification = true; - if (LOG_SURFACE) { - Log.i("GLThread", - "noticing that we want render notification tid=" - + getId()); - } - - // Destroy and recreate the EGL surface. - createEglSurface = true; - - mSizeChanged = false; - } - mRequestRender = false; - sGLThreadManager.notifyAll(); - break; - } - } - - // By design, this is the only place in a GLThread thread where we wait(). - if (LOG_THREADS) { - Log.i("GLThread", "waiting tid=" + getId() - + " mHaveEglContext: " + mHaveEglContext - + " mHaveEglSurface: " + mHaveEglSurface - + " mPaused: " + mPaused - + " mHasSurface: " + mHasSurface - + " mSurfaceIsBad: " + mSurfaceIsBad - + " mWaitingForSurface: " + mWaitingForSurface - + " mWidth: " + mWidth - + " mHeight: " + mHeight - + " mRequestRender: " + mRequestRender - + " mRenderMode: " + mRenderMode); - } - sGLThreadManager.wait(); - } - } // end of synchronized(sGLThreadManager) - - if (event != null) { - event.run(); - event = null; - continue; - } - - if (createEglSurface) { - if (LOG_SURFACE) { - Log.w("GLThread", "egl createSurface"); - } - if (!mEglHelper.createSurface()) { - synchronized(sGLThreadManager) { - mSurfaceIsBad = true; - sGLThreadManager.notifyAll(); - } - continue; - } - createEglSurface = false; - } - - if (createGlInterface) { - gl = (GL10) mEglHelper.createGL(); - - sGLThreadManager.checkGLDriver(gl); - createGlInterface = false; - } - - if (createEglContext) { - if (LOG_RENDERER) { - Log.w("GLThread", "onSurfaceCreated"); - } - GLTextureView view = mGLSurfaceViewWeakRef.get(); - if (view != null) { - view.mRenderer.onSurfaceCreated(gl, mEglHelper.mEglConfig); - } - createEglContext = false; - } - - if (sizeChanged) { - if (LOG_RENDERER) { - Log.w("GLThread", "onSurfaceChanged(" + w + ", " + h + ")"); - } - GLTextureView view = mGLSurfaceViewWeakRef.get(); - if (view != null) { - view.mRenderer.onSurfaceChanged(gl, w, h); - } - sizeChanged = false; - } - - if (LOG_RENDERER_DRAW_FRAME) { - Log.w("GLThread", "onDrawFrame tid=" + getId()); - } - { - GLTextureView view = mGLSurfaceViewWeakRef.get(); - if (view != null) { - view.mRenderer.onDrawFrame(gl); - } - } - int swapError = mEglHelper.swap(); - switch (swapError) { - case EGL10.EGL_SUCCESS: - break; - case EGL11.EGL_CONTEXT_LOST: - if (LOG_SURFACE) { - Log.i("GLThread", "egl context lost tid=" + getId()); - } - lostEglContext = true; - break; - default: - // Other errors typically mean that the current surface is bad, - // probably because the SurfaceView surface has been destroyed, - // but we haven't been notified yet. - // Log the error to help developers understand why rendering stopped. - EglHelper.logEglErrorAsWarning("GLThread", "eglSwapBuffers", swapError); - - synchronized(sGLThreadManager) { - mSurfaceIsBad = true; - sGLThreadManager.notifyAll(); - } - break; - } - - if (wantRenderNotification) { - doRenderNotification = true; - } - } - - } finally { - /* - * clean-up everything... - */ - synchronized (sGLThreadManager) { - stopEglSurfaceLocked(); - stopEglContextLocked(); - } - } - } - - public boolean ableToDraw() { - return mHaveEglContext && mHaveEglSurface && readyToDraw(); - } - - private boolean readyToDraw() { - return (!mPaused) && mHasSurface && (!mSurfaceIsBad) - && (mWidth > 0) && (mHeight > 0) - && (mRequestRender || (mRenderMode == RENDERMODE_CONTINUOUSLY)); - } - - public void setRenderMode(int renderMode) { - if ( !((RENDERMODE_WHEN_DIRTY <= renderMode) && (renderMode <= RENDERMODE_CONTINUOUSLY)) ) { - throw new IllegalArgumentException("renderMode"); - } - synchronized(sGLThreadManager) { - mRenderMode = renderMode; - sGLThreadManager.notifyAll(); - } - } - - public int getRenderMode() { - synchronized(sGLThreadManager) { - return mRenderMode; - } - } - - public void requestRender() { - synchronized(sGLThreadManager) { - mRequestRender = true; - sGLThreadManager.notifyAll(); - } - } - - public void surfaceCreated() { - synchronized(sGLThreadManager) { - if (LOG_THREADS) { - Log.i("GLThread", "surfaceCreated tid=" + getId()); - } - mHasSurface = true; - sGLThreadManager.notifyAll(); - while((mWaitingForSurface) && (!mExited)) { - try { - sGLThreadManager.wait(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - } - } - - public void surfaceDestroyed() { - synchronized(sGLThreadManager) { - if (LOG_THREADS) { - Log.i("GLThread", "surfaceDestroyed tid=" + getId()); - // new Throwable("Surface destroy calling at???").printStackTrace(); - } - mHasSurface = false; - sGLThreadManager.notifyAll(); - while((!mWaitingForSurface) && (!mExited)) { - try { - sGLThreadManager.wait(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - } - } - - public void onPause() { - synchronized (sGLThreadManager) { - if (LOG_PAUSE_RESUME) { - Log.i("GLThread", "onPause tid=" + getId()); - } - mRequestPaused = true; - sGLThreadManager.notifyAll(); - while ((! mExited) && (! mPaused)) { - if (LOG_PAUSE_RESUME) { - Log.i("Main thread", "onPause waiting for mPaused."); - } - try { - sGLThreadManager.wait(); - } catch (InterruptedException ex) { - Thread.currentThread().interrupt(); - } - } - } - } - - public void onResume() { - synchronized (sGLThreadManager) { - if (LOG_PAUSE_RESUME) { - Log.i("GLThread", "onResume tid=" + getId()); - } - mRequestPaused = false; - mRequestRender = true; - mRenderComplete = false; - sGLThreadManager.notifyAll(); - while ((! mExited) && mPaused && (!mRenderComplete)) { - if (LOG_PAUSE_RESUME) { - Log.i("Main thread", "onResume waiting for !mPaused."); - } - try { - sGLThreadManager.wait(); - } catch (InterruptedException ex) { - Thread.currentThread().interrupt(); - } - } - } - } - - public void onWindowResize(int w, int h) { - synchronized (sGLThreadManager) { - mWidth = w; - mHeight = h; - mSizeChanged = true; - mRequestRender = true; - mRenderComplete = false; - sGLThreadManager.notifyAll(); - - // Wait for thread to react to resize and render a frame - while (! mExited && !mPaused && !mRenderComplete - && ableToDraw()) { - if (LOG_SURFACE) { - Log.i("Main thread", "onWindowResize waiting for render complete from tid=" + getId()); - } - try { - sGLThreadManager.wait(); - } catch (InterruptedException ex) { - Thread.currentThread().interrupt(); - } - } - } - } - - public void requestExitAndWait() { - // don't call this from GLThread thread or it is a guaranteed - // deadlock! - synchronized(sGLThreadManager) { - mShouldExit = true; - sGLThreadManager.notifyAll(); - while (! mExited) { - try { - sGLThreadManager.wait(); - } catch (InterruptedException ex) { - Thread.currentThread().interrupt(); - } - } - } - } - - public void requestReleaseEglContextLocked() { - mShouldReleaseEglContext = true; - sGLThreadManager.notifyAll(); - } - - /** - * Queue an "event" to be run on the GL rendering thread. - * @param r the runnable to be run on the GL rendering thread. - */ - public void queueEvent(Runnable r) { - if (r == null) { - throw new IllegalArgumentException("r must not be null"); - } - synchronized(sGLThreadManager) { - mEventQueue.add(r); - sGLThreadManager.notifyAll(); - } - } - - // Once the thread is started, all accesses to the following member - // variables are protected by the sGLThreadManager monitor - private boolean mShouldExit; - private boolean mExited; - private boolean mRequestPaused; - private boolean mPaused; - private boolean mHasSurface; - private boolean mSurfaceIsBad; - private boolean mWaitingForSurface; - private boolean mHaveEglContext; - private boolean mHaveEglSurface; - private boolean mShouldReleaseEglContext; - private int mWidth; - private int mHeight; - private int mRenderMode; - private boolean mRequestRender; - private boolean mRenderComplete; - private ArrayList mEventQueue = new ArrayList(); - private boolean mSizeChanged = true; - - // End of member variables protected by the sGLThreadManager monitor. - - private EglHelper mEglHelper; - - /** - * Set once at thread construction time, nulled out when the parent view is garbage - * called. This weak reference allows the GLTextureView to be garbage collected while - * the GLThread is still alive. - */ - private WeakReference mGLSurfaceViewWeakRef; - - } - - static class LogWriter extends Writer { - - @Override public void close() { - flushBuilder(); - } - - @Override public void flush() { - flushBuilder(); - } - - @Override public void write(char[] buf, int offset, int count) { - for(int i = 0; i < count; i++) { - char c = buf[offset + i]; - if ( c == '\n') { - flushBuilder(); - } - else { - mBuilder.append(c); - } - } - } - - private void flushBuilder() { - if (mBuilder.length() > 0) { - Log.v("GLTextureView", mBuilder.toString()); - mBuilder.delete(0, mBuilder.length()); - } - } - - private StringBuilder mBuilder = new StringBuilder(); - } - - - private void checkRenderThreadState() { - if (mGLThread != null) { - throw new IllegalStateException( - "setRenderer has already been called for this instance."); - } - } - - private static class GLThreadManager { - private static String TAG = "GLThreadManager"; - - public synchronized void threadExiting(GLThread thread) { - if (LOG_THREADS) { - Log.i("GLThread", "exiting tid=" + thread.getId()); - } - thread.mExited = true; - if (mEglOwner == thread) { - mEglOwner = null; - } - notifyAll(); - } - - /* - * Tries once to acquire the right to use an EGL - * context. Does not block. Requires that we are already - * in the sGLThreadManager monitor when this is called. - * - * @return true if the right to use an EGL context was acquired. - */ - public boolean tryAcquireEglContextLocked(GLThread thread) { - if (mEglOwner == thread || mEglOwner == null) { - mEglOwner = thread; - notifyAll(); - return true; - } - checkGLESVersion(); - if (mMultipleGLESContextsAllowed) { - return true; - } - // Notify the owning thread that it should release the context. - // TODO: implement a fairness policy. Currently - // if the owning thread is drawing continuously it will just - // reacquire the EGL context. - if (mEglOwner != null) { - mEglOwner.requestReleaseEglContextLocked(); - } - return false; - } - - /* - * Releases the EGL context. Requires that we are already in the - * sGLThreadManager monitor when this is called. - */ - public void releaseEglContextLocked(GLThread thread) { - if (mEglOwner == thread) { - mEglOwner = null; - } - notifyAll(); - } - - public synchronized boolean shouldReleaseEGLContextWhenPausing() { - // Release the EGL context when pausing even if - // the hardware supports multiple EGL contexts. - // Otherwise the device could run out of EGL contexts. - return mLimitedGLESContexts; - } - - public synchronized boolean shouldTerminateEGLWhenPausing() { - checkGLESVersion(); - return !mMultipleGLESContextsAllowed; - } - - public synchronized void checkGLDriver(GL10 gl) { - if (! mGLESDriverCheckComplete) { - checkGLESVersion(); - String renderer = gl.glGetString(GL10.GL_RENDERER); - if (mGLESVersion < kGLES_20) { - mMultipleGLESContextsAllowed = - ! renderer.startsWith(kMSM7K_RENDERER_PREFIX); - notifyAll(); - } - mLimitedGLESContexts = !mMultipleGLESContextsAllowed; - if (LOG_SURFACE) { - Log.w(TAG, "checkGLDriver renderer = \"" + renderer + "\" multipleContextsAllowed = " - + mMultipleGLESContextsAllowed - + " mLimitedGLESContexts = " + mLimitedGLESContexts); - } - mGLESDriverCheckComplete = true; - } - } - - private void checkGLESVersion() { - if (! mGLESVersionCheckComplete) { -// mGLESVersion = SystemProperties.getInt( -// "ro.opengles.version", -// ConfigurationInfo.GL_ES_VERSION_UNDEFINED); -// if (mGLESVersion >= kGLES_20) { -// mMultipleGLESContextsAllowed = true; -// } -// if (LOG_SURFACE) { -// Log.w(TAG, "checkGLESVersion mGLESVersion =" + -// " " + mGLESVersion + " mMultipleGLESContextsAllowed = " + mMultipleGLESContextsAllowed); -// } - mGLESVersionCheckComplete = true; - } - } - - /** - * This check was required for some pre-Android-3.0 hardware. Android 3.0 provides - * support for hardware-accelerated views, therefore multiple EGL contexts are - * supported on all Android 3.0+ EGL drivers. - */ - private boolean mGLESVersionCheckComplete; - private int mGLESVersion; - private boolean mGLESDriverCheckComplete; - private boolean mMultipleGLESContextsAllowed; - private boolean mLimitedGLESContexts; - private static final int kGLES_20 = 0x20000; - private static final String kMSM7K_RENDERER_PREFIX = - "Q3Dimension MSM7500 "; - private GLThread mEglOwner; - } - - private static final GLThreadManager sGLThreadManager = new GLThreadManager(); - - private final WeakReference mThisWeakRef = - new WeakReference(this); - private GLThread mGLThread; - private Renderer mRenderer; - private boolean mDetached; - private EGLConfigChooser mEGLConfigChooser; - private EGLContextFactory mEGLContextFactory; - private EGLWindowSurfaceFactory mEGLWindowSurfaceFactory; - private GLWrapper mGLWrapper; - private int mDebugFlags; - private int mEGLContextClientVersion; - private boolean mPreserveEGLContextOnPause; -} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/ImportControlActivity.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/ImportControlActivity.java index 16afb9246..15fe8abd6 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/ImportControlActivity.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/ImportControlActivity.java @@ -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) { diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/InstallerTask.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/InstallerTask.java deleted file mode 100644 index 5e6b84f2c..000000000 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/InstallerTask.java +++ /dev/null @@ -1,52 +0,0 @@ -package net.kdt.pojavlaunch; -import android.os.*; - -public class InstallerTask extends AsyncTask -{ - @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 - { - - } -} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/JAssets.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/JAssets.java index d05c4bce7..2f166583d 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/JAssets.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/JAssets.java @@ -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 objects; } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/JMinecraftVersionList.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/JMinecraftVersionList.java index e2924dbfe..0f32bebb1 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/JMinecraftVersionList.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/JMinecraftVersionList.java @@ -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 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 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; } } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/JavaGUILauncherActivity.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/JavaGUILauncherActivity.java index 7d9e48ee3..2815780a9 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/JavaGUILauncherActivity.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/JavaGUILauncherActivity.java @@ -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 javaArgList = new ArrayList(); // 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(); } } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/LoggableActivity.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/LoggableActivity.java deleted file mode 100644 index 689de95ff..000000000 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/LoggableActivity.java +++ /dev/null @@ -1,27 +0,0 @@ -package net.kdt.pojavlaunch; - -import java.util.Map; - -public abstract class LoggableActivity extends BaseActivity { - public Map 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 )"; - 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); -} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Logger.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Logger.java new file mode 100644 index 000000000..d6987d9ea --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Logger.java @@ -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 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); + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/LWJGLGLFWKeycode.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/LwjglGlfwKeycode.java similarity index 97% rename from app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/LWJGLGLFWKeycode.java rename to app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/LwjglGlfwKeycode.java index 5482b01f6..fc807ef06 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/LWJGLGLFWKeycode.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/LwjglGlfwKeycode.java @@ -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; } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MainActivity.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MainActivity.java index 21e003483..9a2f27a67 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MainActivity.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MainActivity.java @@ -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(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); diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MainEditText.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MainEditText.java deleted file mode 100644 index 11bf6aa75..000000000 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MainEditText.java +++ /dev/null @@ -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; - } -} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MinecraftGLSurface.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MinecraftGLSurface.java new file mode 100644 index 000000000..ad060bc43 --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MinecraftGLSurface.java @@ -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; + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MinecraftGLView.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MinecraftGLView.java deleted file mode 100644 index 50da957c5..000000000 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MinecraftGLView.java +++ /dev/null @@ -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); - } -} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/PojavApplication.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/PojavApplication.java index 8b27f901d..13b83ead8 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/PojavApplication.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/PojavApplication.java @@ -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); } } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/PojavLauncherActivity.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/PojavLauncherActivity.java index bc6037f5d..6c1cba882 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/PojavLauncherActivity.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/PojavLauncherActivity.java @@ -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 accountList = new ArrayList(); - final MinecraftAccount tempProfile = PojavProfile.getTempProfileContent(this); + // Try to load the temporary account + final List 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 adapterAcc = new ArrayAdapter(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 versions = new ArrayList(); + + // 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 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 adapterVer = new ArrayAdapter(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(); + } + } } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/PojavLoginActivity.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/PojavLoginActivity.java index 3bd485b38..ceec3e5e3 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/PojavLoginActivity.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/PojavLoginActivity.java @@ -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()); } } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/PojavMigrator.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/PojavMigrator.java index 825f4a49c..ed4082e7b 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/PojavMigrator.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/PojavMigrator.java @@ -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()) { diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/PojavProfile.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/PojavProfile.java index 999651a78..54c1b06b0 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/PojavProfile.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/PojavProfile.java @@ -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); } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/SingleTapConfirm.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/SingleTapConfirm.java index bd82eb77f..ebfb922bc 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/SingleTapConfirm.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/SingleTapConfirm.java @@ -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; diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/TapDetector.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/TapDetector.java new file mode 100644 index 000000000..a76dd6d95 --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/TapDetector.java @@ -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; + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Tools.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Tools.java index e95244e61..69067d36a 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Tools.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Tools.java @@ -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 javaArgList = new ArrayList(); // 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 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 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; + } } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Touchpad.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Touchpad.java new file mode 100644 index 000000000..dc9d70448 --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Touchpad.java @@ -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(); + } + +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/authenticator/microsoft/MicrosoftAuthTask.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/authenticator/microsoft/MicrosoftAuthTask.java index ee6a6ed67..6426dac88 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/authenticator/microsoft/MicrosoftAuthTask.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/authenticator/microsoft/MicrosoftAuthTask.java @@ -71,7 +71,7 @@ public class MicrosoftAuthTask extends AsyncTask { */ 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; diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/authenticator/microsoft/ui/MicrosoftLoginGUIActivity.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/authenticator/microsoft/ui/MicrosoftLoginGUIActivity.java index 6285cbb59..d82f17986 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/authenticator/microsoft/ui/MicrosoftLoginGUIActivity.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/authenticator/microsoft/ui/MicrosoftLoginGUIActivity.java @@ -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); diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/authenticator/mojang/yggdrasil/AuthenticateRequest.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/authenticator/mojang/AuthenticateRequest.java similarity index 99% rename from app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/authenticator/mojang/yggdrasil/AuthenticateRequest.java rename to app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/authenticator/mojang/AuthenticateRequest.java index f9dce5790..2d72a9eca 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/authenticator/mojang/yggdrasil/AuthenticateRequest.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/authenticator/mojang/AuthenticateRequest.java @@ -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; } -} - +} \ No newline at end of file diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/authenticator/mojang/LoginListener.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/authenticator/mojang/LoginListener.java index 444dffc6d..540c3dc7a 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/authenticator/mojang/LoginListener.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/authenticator/mojang/LoginListener.java @@ -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); +} \ No newline at end of file diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/authenticator/mojang/LoginTask.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/authenticator/mojang/LoginTask.java index b61478e4e..d75901275 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/authenticator/mojang/LoginTask.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/authenticator/mojang/LoginTask.java @@ -11,23 +11,23 @@ public class LoginTask extends AsyncTask 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 str = new ArrayList(); @@ -49,7 +49,7 @@ public class LoginTask extends AsyncTask } } } - //MainActivity.updateStatus(804); + //MainActivity.updateStatus(804); catch(Throwable e){ str.add(e.getMessage()); } @@ -57,15 +57,15 @@ public class LoginTask extends AsyncTask 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); } -} +} \ No newline at end of file diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/authenticator/mojang/RefreshTokenTask.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/authenticator/mojang/RefreshTokenTask.java index b7fb145e0..6ecab4cd8 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/authenticator/mojang/RefreshTokenTask.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/authenticator/mojang/RefreshTokenTask.java @@ -52,7 +52,7 @@ public class RefreshTokenTask extends AsyncTask { } 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 { listener.onFailed(result); } } -} - +} \ No newline at end of file diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/authenticator/mojang/yggdrasil/AuthenticateResponse.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/authenticator/mojang/yggdrasil/AuthenticateResponse.java index 8989acd15..1e166964d 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/authenticator/mojang/yggdrasil/AuthenticateResponse.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/authenticator/mojang/yggdrasil/AuthenticateResponse.java @@ -7,5 +7,4 @@ public class AuthenticateResponse { public Profile[] availableProfiles; public UUID clientToken; public Profile selectedProfile; -} - +} \ No newline at end of file diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/authenticator/mojang/yggdrasil/RefreshResponse.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/authenticator/mojang/yggdrasil/RefreshResponse.java index b9a597955..9c87f7fea 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/authenticator/mojang/yggdrasil/RefreshResponse.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/authenticator/mojang/yggdrasil/RefreshResponse.java @@ -6,5 +6,4 @@ public class RefreshResponse { public String accessToken; public UUID clientToken; public Profile selectedProfile; -} - +} \ No newline at end of file diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/authenticator/mojang/yggdrasil/YggdrasilAuthenticator.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/authenticator/mojang/yggdrasil/YggdrasilAuthenticator.java index fb3029608..7fb7ba5cd 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/authenticator/mojang/yggdrasil/YggdrasilAuthenticator.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/authenticator/mojang/yggdrasil/YggdrasilAuthenticator.java @@ -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); } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/colorselector/AlphaSelectionListener.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/colorselector/AlphaSelectionListener.java new file mode 100644 index 000000000..b307fa98e --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/colorselector/AlphaSelectionListener.java @@ -0,0 +1,5 @@ +package net.kdt.pojavlaunch.colorselector; + +public interface AlphaSelectionListener { + void onAlphaSelected(int alpha); +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/colorselector/AlphaView.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/colorselector/AlphaView.java new file mode 100644 index 000000000..c7d2ab487 --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/colorselector/AlphaView.java @@ -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); + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/colorselector/ColorSelectionListener.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/colorselector/ColorSelectionListener.java new file mode 100644 index 000000000..9dd43a2b0 --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/colorselector/ColorSelectionListener.java @@ -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); +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/colorselector/ColorSelector.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/colorselector/ColorSelector.java new file mode 100644 index 000000000..e463b3689 --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/colorselector/ColorSelector.java @@ -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; + } + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/colorselector/ColorSideBySideView.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/colorselector/ColorSideBySideView.java new file mode 100644 index 000000000..6aaeaa015 --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/colorselector/ColorSideBySideView.java @@ -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; + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/colorselector/HueSelectionListener.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/colorselector/HueSelectionListener.java new file mode 100644 index 000000000..34becdd6f --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/colorselector/HueSelectionListener.java @@ -0,0 +1,5 @@ +package net.kdt.pojavlaunch.colorselector; + +public interface HueSelectionListener { + void onHueSelected(float hue); +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/colorselector/HueView.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/colorselector/HueView.java new file mode 100644 index 000000000..6440adb6c --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/colorselector/HueView.java @@ -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); + } + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/colorselector/RectangleSelectionListener.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/colorselector/RectangleSelectionListener.java new file mode 100644 index 000000000..72a47d499 --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/colorselector/RectangleSelectionListener.java @@ -0,0 +1,5 @@ +package net.kdt.pojavlaunch.colorselector; + +public interface RectangleSelectionListener { + void onLuminosityIntensityChanged(float luminosity, float intensity); +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/colorselector/SVRectangleView.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/colorselector/SVRectangleView.java new file mode 100644 index 000000000..a05aedd6f --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/colorselector/SVRectangleView.java @@ -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); + + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/ControlData.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/ControlData.java index fd4ba4762..5382e90b4 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/ControlData.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/ControlData.java @@ -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 builder = new WeakReference<>(null); - private static WeakReference expression = new WeakReference<>(null); private static WeakReference> 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; } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/ControlDrawerData.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/ControlDrawerData.java index 4db6526e7..68374be0f 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/ControlDrawerData.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/ControlDrawerData.java @@ -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 buttonProperties; public ControlData properties; public Orientation orientation; + @androidx.annotation.Keep public enum Orientation { DOWN, LEFT, diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/ControlLayout.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/ControlLayout.java index dfaf130d8..79bb53422 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/ControlLayout.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/ControlLayout.java @@ -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 mapTable = new HashMap<>(); //While this is called onTouch, this should only be called from a ControlButton. public boolean onTouch(View v, MotionEvent ev) { diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/CustomControls.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/CustomControls.java index 66d264aeb..eade383fa 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/CustomControls.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/CustomControls.java @@ -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)); diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/LayoutConverter.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/LayoutConverter.java index c0035ebac..c9316bca7 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/LayoutConverter.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/LayoutConverter.java @@ -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); diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/buttons/ControlButton.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/buttons/ControlButton.java index 22c25e977..f0189d237 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/buttons/ControlButton.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/buttons/ControlButton.java @@ -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: diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/buttons/ControlDrawer.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/buttons/ControlDrawer.java index a7fbb154c..d048ab99f 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/buttons/ControlDrawer.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/buttons/ControlDrawer.java @@ -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 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){ diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/Gamepad.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/Gamepad.java index dfb56b474..daf76160d 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/Gamepad.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/Gamepad.java @@ -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); } } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/GamepadButton.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/GamepadButton.java index f502ff60f..a6c7cc1e7 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/GamepadButton.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/GamepadButton.java @@ -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; } } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/GamepadDpad.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/GamepadDpad.java index 8cd12c46f..24277ea17 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/GamepadDpad.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/GamepadDpad.java @@ -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; } } \ No newline at end of file diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/GamepadJoystick.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/GamepadJoystick.java index 22cb0b999..b419ec5c8 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/GamepadJoystick.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/GamepadJoystick.java @@ -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)) ); + } } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/GamepadMap.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/GamepadMap.java index 90ec915b6..ac33e4c8a 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/GamepadMap.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/gamepad/GamepadMap.java @@ -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; diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/handleview/ActionPopupWindow.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/handleview/ActionPopupWindow.java index f64d3f8c6..81b96334b 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/handleview/ActionPopupWindow.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/handleview/ActionPopupWindow.java @@ -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 + " %"); } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/handleview/EditControlButtonPopup.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/handleview/EditControlButtonPopup.java index eb6ff7fb9..d085d6471 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/handleview/EditControlButtonPopup.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/handleview/EditControlButtonPopup.java @@ -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 adapter; - protected String[] specialArr; + protected final ControlButton mControlButton; + protected final ControlData mProperties; + + protected ArrayAdapter 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 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(); } } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/handleview/EditControlSubButtonPopup.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/handleview/EditControlSubButtonPopup.java index 064a5829e..0b0f90e0b 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/handleview/EditControlSubButtonPopup.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/handleview/EditControlSubButtonPopup.java @@ -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); } } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/handleview/HandleView.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/handleview/HandleView.java index 1378be5db..c8278cadf 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/handleview/HandleView.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/handleview/HandleView.java @@ -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; } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/handleview/SelectionEndHandleView.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/handleview/SelectionEndHandleView.java index 489c91bec..ebbb5e64c 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/handleview/SelectionEndHandleView.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/handleview/SelectionEndHandleView.java @@ -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); } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/handleview/ViewPositionListener.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/handleview/ViewPositionListener.java index 0dd53f6c2..cf0dda3a2 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/handleview/ViewPositionListener.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/handleview/ViewPositionListener.java @@ -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); } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/keyboard/AwtCharSender.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/keyboard/AwtCharSender.java new file mode 100644 index 000000000..afd4dd998 --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/keyboard/AwtCharSender.java @@ -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); + } + +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/keyboard/CharacterSenderStrategy.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/keyboard/CharacterSenderStrategy.java new file mode 100644 index 000000000..9d674bb3e --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/keyboard/CharacterSenderStrategy.java @@ -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); + +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/keyboard/LwjglCharSender.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/keyboard/LwjglCharSender.java new file mode 100644 index 000000000..59b76ba04 --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/keyboard/LwjglCharSender.java @@ -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); + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/TouchCharInput.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/keyboard/TouchCharInput.java similarity index 76% rename from app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/TouchCharInput.java rename to app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/keyboard/TouchCharInput.java index 5cb26354a..4d8c6a6b1 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/TouchCharInput.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/keyboard/TouchCharInput.java @@ -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(); diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/extra/ExtraConstants.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/extra/ExtraConstants.java new file mode 100644 index 000000000..9412ab5b8 --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/extra/ExtraConstants.java @@ -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"; +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/extra/ExtraCore.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/extra/ExtraCore.java new file mode 100644 index 000000000..e14ad285f --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/extra/ExtraCore.java @@ -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 mValueMap = new ConcurrentHashMap<>(); + + // Store what each ExtraListener listen to + private final Map>> 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> extraListenerList = getInstance().mListenerMap.get(key); + if(extraListenerList == null) return; //No listeners + for(WeakReference 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> 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> 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 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> 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(); + } + +} \ No newline at end of file diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/extra/ExtraListener.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/extra/ExtraListener.java new file mode 100644 index 000000000..a3df5cc12 --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/extra/ExtraListener.java @@ -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 { + + /** + * 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); + +} \ No newline at end of file diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/ConsoleFragment.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/ConsoleFragment.java index df917a536..4dc026e47 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/ConsoleFragment.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/ConsoleFragment.java @@ -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); } } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/CrashFragment.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/CrashFragment.java index 4ad687bfc..93175af75 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/CrashFragment.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/CrashFragment.java @@ -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 ""; } } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/LauncherFragment.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/LauncherFragment.java index c0a3be4a5..c0871d3c8 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/LauncherFragment.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/LauncherFragment.java @@ -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; } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/multirt/MultiRTConfigDialog.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/multirt/MultiRTConfigDialog.java index e00eb8e45..7e0bdbfc2 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/multirt/MultiRTConfigDialog.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/multirt/MultiRTConfigDialog.java @@ -14,31 +14,33 @@ import net.kdt.pojavlaunch.R; public class MultiRTConfigDialog { public static final int MULTIRT_PICK_RUNTIME = 2048; public static final int MULTIRT_PICK_RUNTIME_STARTUP = 2049; - public AlertDialog dialog; - public RecyclerView dialogView; - public void prepare(BaseLauncherActivity ctx) { - AlertDialog.Builder builder = new AlertDialog.Builder(ctx); + public AlertDialog mDialog; + public RecyclerView mDialogView; + + public void prepare(BaseLauncherActivity activity) { + mDialogView = new RecyclerView(activity); + mDialogView.setLayoutManager(new LinearLayoutManager(activity, LinearLayoutManager.VERTICAL, false)); + mDialogView.setAdapter(new RTRecyclerViewAdapter(this)); + + AlertDialog.Builder builder = new AlertDialog.Builder(activity); builder.setTitle(R.string.multirt_config_title); - dialogView = new RecyclerView(ctx); - LinearLayoutManager linearLayoutManager = new LinearLayoutManager(ctx); - linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL); - dialogView.setLayoutManager(linearLayoutManager); - dialogView.setAdapter(new RTRecyclerViewAdapter(this)); - builder.setView(dialogView); - builder.setPositiveButton(R.string.multirt_config_add, (dialog, which) -> openRuntimeSelector(ctx,MULTIRT_PICK_RUNTIME)); + builder.setView(mDialogView); + builder.setPositiveButton(R.string.multirt_config_add, (dialog, which) -> openRuntimeSelector(activity,MULTIRT_PICK_RUNTIME)); builder.setNegativeButton(R.string.mcn_exit_call, (dialog, which) -> dialog.cancel()); - dialog = builder.create(); + mDialog = builder.create(); } + public void refresh() { - RecyclerView.Adapter adapter = dialogView.getAdapter(); - if(adapter != null)dialogView.getAdapter().notifyDataSetChanged(); + RecyclerView.Adapter adapter = mDialogView.getAdapter(); + if(adapter != null) mDialogView.getAdapter().notifyDataSetChanged(); } - public static void openRuntimeSelector(Activity ctx, int code) { + + public static void openRuntimeSelector(Activity activity, int code) { Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); intent.addCategory(Intent.CATEGORY_OPENABLE); String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension("xz"); if(mimeType == null) mimeType = "*/*"; intent.setType(mimeType); - ctx.startActivityForResult(intent,code); + activity.startActivityForResult(intent,code); } } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/multirt/MultiRTUtils.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/multirt/MultiRTUtils.java index a5f717ca5..13ae94d75 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/multirt/MultiRTUtils.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/multirt/MultiRTUtils.java @@ -1,7 +1,10 @@ package net.kdt.pojavlaunch.multirt; +import static org.apache.commons.io.FileUtils.listFiles; + import android.content.Context; import android.system.Os; +import android.util.Log; import net.kdt.pojavlaunch.R; import net.kdt.pojavlaunch.Tools; @@ -20,128 +23,109 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.List; -import java.util.Objects; public class MultiRTUtils { - public static HashMap cache = new HashMap<>(); - public static class Runtime { - public Runtime(String name) { - this.name = name; - } - public String name; - public String versionString; - public String arch; - public int javaVersion; + public interface RuntimeProgressReporter { + void reportStringProgress(int resId, Object ... stuff); + } + + private static final HashMap sCache = new HashMap<>(); + + private static final File RUNTIME_FOLDER = new File(Tools.MULTIRT_HOME); + private static final String JAVA_VERSION_STR = "JAVA_VERSION=\""; + private static final String OS_ARCH_STR = "OS_ARCH=\""; - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Runtime runtime = (Runtime) o; - return name.equals(runtime.name); - } - @Override - public int hashCode() { - return Objects.hash(name); - } - } - public static interface ProgressReporterThingy { - void reportStringProgress(int resid, Object ... stuff); - } - private static final File runtimeFolder = new File(Tools.MULTIRT_HOME); - private static final String JAVA_VERSION_str = "JAVA_VERSION=\""; - private static final String OS_ARCH_str = "OS_ARCH=\""; public static List getRuntimes() { - if(!runtimeFolder.exists()) runtimeFolder.mkdirs(); - ArrayList ret = new ArrayList<>(); + if(!RUNTIME_FOLDER.exists()) RUNTIME_FOLDER.mkdirs(); + + ArrayList runtimes = new ArrayList<>(); System.out.println("Fetch runtime list"); - for(File f : runtimeFolder.listFiles()) { - ret.add(read(f.getName())); + for(File f : RUNTIME_FOLDER.listFiles()) { + runtimes.add(read(f.getName())); } - return ret; + return runtimes; } - public static String getNearestJREName(int majorVersion) { + + public static String getExactJreName(int majorVersion) { + List runtimes = getRuntimes(); + for(Runtime r : runtimes) + if(r.javaVersion == majorVersion)return r.name; + + return null; + } + + public static String getNearestJreName(int majorVersion) { List runtimes = getRuntimes(); int diff_factor = Integer.MAX_VALUE; String result = null; for(Runtime r : runtimes) { - if(r.javaVersion >= majorVersion) { // lower - not useful - int currentFactor = r.javaVersion - majorVersion; - if(diff_factor > currentFactor) { - result = r.name; - diff_factor = currentFactor; - } + if(r.javaVersion < majorVersion) continue; // lower - not useful + + int currentFactor = r.javaVersion - majorVersion; + if(diff_factor > currentFactor) { + result = r.name; + diff_factor = currentFactor; } } + return result; } - public static void installRuntimeNamed(InputStream runtimeInputStream, String name, ProgressReporterThingy thingy) throws IOException { - File dest = new File(runtimeFolder,"/"+name); + + public static void installRuntimeNamed(InputStream runtimeInputStream, String name, RuntimeProgressReporter progressReporter) throws IOException { + File dest = new File(RUNTIME_FOLDER,"/"+name); File tmp = new File(dest,"temporary"); if(dest.exists()) FileUtils.deleteDirectory(dest); dest.mkdirs(); FileOutputStream fos = new FileOutputStream(tmp); - thingy.reportStringProgress(R.string.multirt_progress_caching); + progressReporter.reportStringProgress(R.string.multirt_progress_caching); IOUtils.copy(runtimeInputStream,fos); fos.close(); runtimeInputStream.close(); - uncompressTarXZ(tmp,dest,thingy); + uncompressTarXZ(tmp,dest,progressReporter); tmp.delete(); read(name); } - private static void __installRuntimeNamed__NoRM(InputStream runtimeInputStream, File dest, ProgressReporterThingy thingy) throws IOException { - File tmp = new File(dest,"temporary"); - FileOutputStream fos = new FileOutputStream(tmp); - thingy.reportStringProgress(R.string.multirt_progress_caching); - IOUtils.copy(runtimeInputStream,fos); - fos.close(); - runtimeInputStream.close(); - uncompressTarXZ(tmp,dest,thingy); - tmp.delete(); - } + public static void postPrepare(Context ctx, String name) throws IOException { - File dest = new File(runtimeFolder,"/"+name); + File dest = new File(RUNTIME_FOLDER,"/" + name); if(!dest.exists()) return; - Runtime r = read(name); + Runtime runtime = read(name); String libFolder = "lib"; - if(new File(dest,libFolder+"/"+r.arch).exists()) libFolder = libFolder+"/"+r.arch; - File ftIn = new File(dest, libFolder+ "/libfreetype.so.6"); + if(new File(dest,libFolder + "/" + runtime.arch).exists()) libFolder = libFolder + "/" + runtime.arch; + File ftIn = new File(dest, libFolder + "/libfreetype.so.6"); File ftOut = new File(dest, libFolder + "/libfreetype.so"); if (ftIn.exists() && (!ftOut.exists() || ftIn.length() != ftOut.length())) { ftIn.renameTo(ftOut); } // Refresh libraries - copyDummyNativeLib(ctx,"libawt_xawt.so",dest,libFolder); + copyDummyNativeLib(ctx,"libawt_xawt.so", dest, libFolder); } - private static void copyDummyNativeLib(Context ctx, String name, File dest, String libFolder) throws IOException { - File fileLib = new File(dest, "/"+libFolder + "/" + name); - fileLib.delete(); - FileInputStream is = new FileInputStream(new File(ctx.getApplicationInfo().nativeLibraryDir, name)); - FileOutputStream os = new FileOutputStream(fileLib); - IOUtils.copy(is, os); - is.close(); - os.close(); - } - public static Runtime installRuntimeNamedBinpack(InputStream universalFileInputStream, InputStream platformBinsInputStream, String name, String binpackVersion, ProgressReporterThingy thingy) throws IOException { - File dest = new File(runtimeFolder,"/"+name); + public static Runtime installRuntimeNamedBinpack(String nativeLibDir, InputStream universalFileInputStream, InputStream platformBinsInputStream, String name, String binpackVersion, RuntimeProgressReporter thingy) throws IOException { + File dest = new File(RUNTIME_FOLDER,"/"+name); if(dest.exists()) FileUtils.deleteDirectory(dest); dest.mkdirs(); - __installRuntimeNamed__NoRM(universalFileInputStream,dest,thingy); - __installRuntimeNamed__NoRM(platformBinsInputStream,dest,thingy); - File binpack_verfile = new File(runtimeFolder,"/"+name+"/pojav_version"); + installRuntimeNamedNoRemove(universalFileInputStream,dest,thingy); + installRuntimeNamedNoRemove(platformBinsInputStream,dest,thingy); + File binpack_verfile = new File(RUNTIME_FOLDER,"/"+name+"/pojav_version"); FileOutputStream fos = new FileOutputStream(binpack_verfile); fos.write(binpackVersion.getBytes()); fos.close(); - cache.remove(name); // Force reread + + unpack200(nativeLibDir,RUNTIME_FOLDER + "/" + name); + + sCache.remove(name); // Force reread return read(name); } + + public static String __internal__readBinpackVersion(String name) { - File binpack_verfile = new File(runtimeFolder,"/"+name+"/pojav_version"); + File binpack_verfile = new File(RUNTIME_FOLDER,"/"+name+"/pojav_version"); try { if (binpack_verfile.exists()) { return Tools.read(binpack_verfile.getAbsolutePath()); @@ -153,38 +137,42 @@ public class MultiRTUtils { return null; } } + public static void removeRuntimeNamed(String name) throws IOException { - File dest = new File(runtimeFolder,"/"+name); + File dest = new File(RUNTIME_FOLDER,"/"+name); if(dest.exists()) { FileUtils.deleteDirectory(dest); - cache.remove(name); + sCache.remove(name); } } + public static void setRuntimeNamed(Context ctx, String name) throws IOException { - File dest = new File(runtimeFolder,"/"+name); + File dest = new File(RUNTIME_FOLDER,"/"+name); if((!dest.exists()) || MultiRTUtils.forceReread(name).versionString == null) throw new RuntimeException("Selected runtime is broken!"); Tools.DIR_HOME_JRE = dest.getAbsolutePath(); JREUtils.relocateLibPath(ctx); } + public static Runtime forceReread(String name) { - cache.remove(name); + sCache.remove(name); return read(name); } + public static Runtime read(String name) { - if(cache.containsKey(name)) return cache.get(name); - Runtime retur; - File release = new File(runtimeFolder,"/"+name+"/release"); + if(sCache.containsKey(name)) return sCache.get(name); + Runtime returnRuntime; + File release = new File(RUNTIME_FOLDER,"/"+name+"/release"); if(!release.exists()) { return new Runtime(name); } try { String content = Tools.read(release.getAbsolutePath()); - int _JAVA_VERSION_index = content.indexOf(JAVA_VERSION_str); - int _OS_ARCH_index = content.indexOf(OS_ARCH_str); - if(_JAVA_VERSION_index != -1 && _OS_ARCH_index != -1) { - _JAVA_VERSION_index += JAVA_VERSION_str.length(); - _OS_ARCH_index += OS_ARCH_str.length(); - String javaVersion = content.substring(_JAVA_VERSION_index,content.indexOf('"',_JAVA_VERSION_index)); + int javaVersionIndex = content.indexOf(JAVA_VERSION_STR); + int osArchIndex = content.indexOf(OS_ARCH_STR); + if(javaVersionIndex != -1 && osArchIndex != -1) { + javaVersionIndex += JAVA_VERSION_STR.length(); + osArchIndex += OS_ARCH_STR.length(); + String javaVersion = content.substring(javaVersionIndex,content.indexOf('"', javaVersionIndex)); String[] javaVersionSplit = javaVersion.split("\\."); int javaVersionInt; if (javaVersionSplit[0].equals("1")) { @@ -192,25 +180,68 @@ public class MultiRTUtils { } else { javaVersionInt = Integer.parseInt(javaVersionSplit[0]); } - Runtime r = new Runtime(name); - r.arch = content.substring(_OS_ARCH_index,content.indexOf('"',_OS_ARCH_index)); - r.javaVersion = javaVersionInt; - r.versionString = javaVersion; - retur = r; + Runtime runtime = new Runtime(name); + runtime.arch = content.substring(osArchIndex,content.indexOf('"', osArchIndex)); + runtime.javaVersion = javaVersionInt; + runtime.versionString = javaVersion; + returnRuntime = runtime; }else{ - retur = new Runtime(name); + returnRuntime = new Runtime(name); } }catch(IOException e) { - retur = new Runtime(name); + returnRuntime = new Runtime(name); } - cache.put(name,retur); - return retur; + sCache.put(name, returnRuntime); + return returnRuntime; } - private static void uncompressTarXZ(final File tarFile, final File dest, final ProgressReporterThingy thingy) throws IOException { - dest.mkdirs(); - TarArchiveInputStream tarIn = null; - tarIn = new TarArchiveInputStream( + /** + * Unpacks all .pack files into .jar + * @param nativeLibraryDir The native lib path, required to execute the unpack200 binary + * @param runtimePath The path to the runtime to walk into + */ + private static void unpack200(String nativeLibraryDir, String runtimePath) { + File basePath = new File(runtimePath); + Collection files = listFiles(basePath, new String[]{"pack"}, true); + + File workdir = new File(nativeLibraryDir); + + ProcessBuilder processBuilder = new ProcessBuilder().directory(workdir); + for(File jarFile : files){ + try{ + Process process = processBuilder.command("./unpack200.so", "-r", jarFile.getAbsolutePath(), jarFile.getAbsolutePath().replace(".pack", "")).start(); + process.waitFor(); + }catch (InterruptedException | IOException e) { + Log.e("MULTIRT", "Failed to unpack the runtime !"); + } + } + } + + private static void copyDummyNativeLib(Context ctx, String name, File dest, String libFolder) throws IOException { + File fileLib = new File(dest, "/"+libFolder + "/" + name); + fileLib.delete(); + FileInputStream is = new FileInputStream(new File(ctx.getApplicationInfo().nativeLibraryDir, name)); + FileOutputStream os = new FileOutputStream(fileLib); + IOUtils.copy(is, os); + is.close(); + os.close(); + } + + private static void installRuntimeNamedNoRemove(InputStream runtimeInputStream, File dest, RuntimeProgressReporter progressReporter) throws IOException { + File tmp = new File(dest,"temporary"); + FileOutputStream fos = new FileOutputStream(tmp); + progressReporter.reportStringProgress(R.string.multirt_progress_caching); + IOUtils.copy(runtimeInputStream,fos); + fos.close(); + runtimeInputStream.close(); + uncompressTarXZ(tmp,dest,progressReporter); + tmp.delete(); + } + + private static void uncompressTarXZ(final File tarFile, final File dest, final RuntimeProgressReporter thingy) throws IOException { + dest.mkdirs(); + + TarArchiveInputStream tarIn = new TarArchiveInputStream( new XZCompressorInputStream( new BufferedInputStream( new FileInputStream(tarFile) @@ -242,7 +273,7 @@ public class MultiRTUtils { // Libcore one support all Android versions Os.symlink(tarEntry.getName(), tarEntry.getLinkName()); } catch (Throwable e) { - e.printStackTrace(); + Log.e("MultiRT", e.toString()); } } else if (tarEntry.isDirectory()) { diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/multirt/RTRecyclerViewAdapter.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/multirt/RTRecyclerViewAdapter.java index e268f773a..cec96f6a0 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/multirt/RTRecyclerViewAdapter.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/multirt/RTRecyclerViewAdapter.java @@ -14,6 +14,7 @@ import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import androidx.recyclerview.widget.RecyclerView; +import net.kdt.pojavlaunch.Architecture; import net.kdt.pojavlaunch.R; import net.kdt.pojavlaunch.Tools; import net.kdt.pojavlaunch.prefs.LauncherPreferences; @@ -22,111 +23,126 @@ import java.io.IOException; import java.util.List; public class RTRecyclerViewAdapter extends RecyclerView.Adapter { - MultiRTConfigDialog dialog; + + MultiRTConfigDialog mConfigDialog; public RTRecyclerViewAdapter(MultiRTConfigDialog dialog) { - this.dialog = dialog; + this.mConfigDialog = dialog; } + @NonNull @Override public RTViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - View recyclableView = LayoutInflater.from(parent.getContext()).inflate(R.layout.multirt_recyclable_view,parent,false); + View recyclableView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_multirt_runtime,parent,false); return new RTViewHolder(recyclableView); } @Override public void onBindViewHolder(@NonNull RTViewHolder holder, int position) { - final List runtimes = MultiRTUtils.getRuntimes(); + final List runtimes = MultiRTUtils.getRuntimes(); holder.bindRuntime(runtimes.get(position),position); } - public boolean isDefaultRuntime(MultiRTUtils.Runtime rt) { - return LauncherPreferences.PREF_DEFAULT_RUNTIME.equals(rt.name); - } - public void setDefault(MultiRTUtils.Runtime rt){ - LauncherPreferences.PREF_DEFAULT_RUNTIME = rt.name; - LauncherPreferences.DEFAULT_PREF.edit().putString("defaultRuntime",LauncherPreferences.PREF_DEFAULT_RUNTIME).apply(); - RTRecyclerViewAdapter.this.notifyDataSetChanged(); - } + @Override public int getItemCount() { return MultiRTUtils.getRuntimes().size(); } + + public boolean isDefaultRuntime(Runtime rt) { + return LauncherPreferences.PREF_DEFAULT_RUNTIME.equals(rt.name); + } + + public void setDefault(Runtime rt){ + LauncherPreferences.PREF_DEFAULT_RUNTIME = rt.name; + LauncherPreferences.DEFAULT_PREF.edit().putString("defaultRuntime",LauncherPreferences.PREF_DEFAULT_RUNTIME).apply(); + RTRecyclerViewAdapter.this.notifyDataSetChanged(); + } + + public class RTViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{ - final TextView javaVersionView; - final TextView fullJavaVersionView; - final ColorStateList defaultColors; - final Button setDefaultButton; - final Context ctx; - MultiRTUtils.Runtime currentRuntime; - int currentPosition; + final TextView mJavaVersionTextView; + final TextView mFullJavaVersionTextView; + final ColorStateList mDefaultColors; + final Button mSetDefaultButton; + final Context mContext; + Runtime mCurrentRuntime; + int mCurrentPosition; + public RTViewHolder(View itemView) { super(itemView); - javaVersionView = itemView.findViewById(R.id.multirt_view_java_version); - fullJavaVersionView = itemView.findViewById(R.id.multirt_view_java_version_full); + mJavaVersionTextView = itemView.findViewById(R.id.multirt_view_java_version); + mFullJavaVersionTextView = itemView.findViewById(R.id.multirt_view_java_version_full); + mSetDefaultButton = itemView.findViewById(R.id.multirt_view_setdefaultbtn); + mSetDefaultButton.setOnClickListener(this); + mDefaultColors = mFullJavaVersionTextView.getTextColors(); + mContext = itemView.getContext(); itemView.findViewById(R.id.multirt_view_removebtn).setOnClickListener(this); - setDefaultButton = itemView.findViewById(R.id.multirt_view_setdefaultbtn); - setDefaultButton.setOnClickListener(this); - defaultColors = fullJavaVersionView.getTextColors(); - ctx = itemView.getContext(); - } - public void bindRuntime(MultiRTUtils.Runtime rt, int pos) { - currentRuntime = rt; - currentPosition = pos; - if(rt.versionString != null) { - javaVersionView.setText(ctx.getString(R.string.multirt_java_ver, rt.name, rt.javaVersion)); - fullJavaVersionView.setText(rt.versionString); - fullJavaVersionView.setTextColor(defaultColors); - setDefaultButton.setVisibility(View.VISIBLE); - boolean default_ = isDefaultRuntime(rt); - setDefaultButton.setEnabled(!default_); - setDefaultButton.setText(default_?R.string.multirt_config_setdefault_already:R.string.multirt_config_setdefault); - }else{ - javaVersionView.setText(rt.name); - fullJavaVersionView.setText(R.string.multirt_runtime_corrupt); - fullJavaVersionView.setTextColor(Color.RED); - setDefaultButton.setVisibility(View.GONE); - } } @Override - public void onClick(View v) { - if(v.getId() == R.id.multirt_view_removebtn) { - if (currentRuntime != null) { - if(MultiRTUtils.getRuntimes().size() < 2) { - AlertDialog.Builder bldr = new AlertDialog.Builder(ctx); - bldr.setTitle(R.string.global_error); - bldr.setMessage(R.string.multirt_config_removeerror_last); - bldr.setPositiveButton(android.R.string.ok,(adapter, which)->adapter.dismiss()); - bldr.show(); - return; - } + public void onClick(View view) { + if(view.getId() == R.id.multirt_view_removebtn) { + if (mCurrentRuntime == null) return; - final ProgressDialog barrier = new ProgressDialog(ctx); - barrier.setMessage(ctx.getString(R.string.global_waiting)); - barrier.setProgressStyle(ProgressDialog.STYLE_SPINNER); - barrier.setCancelable(false); - barrier.show(); - Thread t = new Thread(() -> { - try { - MultiRTUtils.removeRuntimeNamed(currentRuntime.name); - } catch (IOException e) { - Tools.showError(itemView.getContext(), e); - } - v.post(() -> { - if(isDefaultRuntime(currentRuntime)) setDefault(MultiRTUtils.getRuntimes().get(0)); - barrier.dismiss(); - RTRecyclerViewAdapter.this.notifyDataSetChanged(); - dialog.dialog.show(); - }); - }); - t.start(); + if(MultiRTUtils.getRuntimes().size() < 2 && mSetDefaultButton.isShown()) { + AlertDialog.Builder bldr = new AlertDialog.Builder(mContext); + bldr.setTitle(R.string.global_error); + bldr.setMessage(R.string.multirt_config_removeerror_last); + bldr.setPositiveButton(android.R.string.ok,(adapter, which)->adapter.dismiss()); + bldr.show(); + return; } - }else if(v.getId() == R.id.multirt_view_setdefaultbtn) { - if(currentRuntime != null) { - setDefault(currentRuntime); + + final ProgressDialog barrier = new ProgressDialog(mContext); + barrier.setMessage(mContext.getString(R.string.global_waiting)); + barrier.setProgressStyle(ProgressDialog.STYLE_SPINNER); + barrier.setCancelable(false); + barrier.show(); + Thread t = new Thread(() -> { + try { + MultiRTUtils.removeRuntimeNamed(mCurrentRuntime.name); + } catch (IOException e) { + Tools.showError(itemView.getContext(), e); + } + view.post(() -> { + if(isDefaultRuntime(mCurrentRuntime)) setDefault(MultiRTUtils.getRuntimes().get(0)); + barrier.dismiss(); + RTRecyclerViewAdapter.this.notifyDataSetChanged(); + mConfigDialog.mDialog.show(); + }); + }); + t.start(); + + }else if(view.getId() == R.id.multirt_view_setdefaultbtn) { + if(mCurrentRuntime != null) { + setDefault(mCurrentRuntime); RTRecyclerViewAdapter.this.notifyDataSetChanged(); } } } + public void bindRuntime(Runtime runtime, int pos) { + mCurrentRuntime = runtime; + mCurrentPosition = pos; + if(runtime.versionString != null && Tools.DEVICE_ARCHITECTURE == Architecture.archAsInt(runtime.arch)) { + mJavaVersionTextView.setText(mContext.getString(R.string.multirt_java_ver, runtime.name, runtime.javaVersion)); + mFullJavaVersionTextView.setText(runtime.versionString); + mFullJavaVersionTextView.setTextColor(mDefaultColors); + mSetDefaultButton.setVisibility(View.VISIBLE); + boolean defaultRuntime = isDefaultRuntime(runtime); + mSetDefaultButton.setEnabled(!defaultRuntime); + mSetDefaultButton.setText(defaultRuntime ? R.string.multirt_config_setdefault_already:R.string.multirt_config_setdefault); + return; + } + + // Problematic runtime moment + if(runtime.versionString == null){ + mFullJavaVersionTextView.setText(R.string.multirt_runtime_corrupt); + }else{ + mFullJavaVersionTextView.setText(mContext.getString(R.string.multirt_runtime_incompatiblearch, runtime.arch)); + } + mJavaVersionTextView.setText(runtime.name); + mFullJavaVersionTextView.setTextColor(Color.RED); + mSetDefaultButton.setVisibility(View.GONE); + } } } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/multirt/RTSpinnerAdapter.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/multirt/RTSpinnerAdapter.java index 4a499c371..776453a73 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/multirt/RTSpinnerAdapter.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/multirt/RTSpinnerAdapter.java @@ -16,39 +16,39 @@ import net.kdt.pojavlaunch.R; import java.util.List; public class RTSpinnerAdapter implements SpinnerAdapter { - final Context ctx; - List runtimes; - public RTSpinnerAdapter(@NonNull Context context, List runtimes) { - this.runtimes = runtimes; - MultiRTUtils.Runtime runtime = new MultiRTUtils.Runtime(""); + final Context mContext; + List mRuntimes; + public RTSpinnerAdapter(@NonNull Context context, List runtimes) { + mRuntimes = runtimes; + Runtime runtime = new Runtime(""); runtime.versionString = ""; - this.runtimes.add(runtime); - ctx = context; + mRuntimes.add(runtime); + mContext = context; } @Override public void registerDataSetObserver(DataSetObserver observer) { - + //STUB } @Override public void unregisterDataSetObserver(DataSetObserver observer) { - + //STUB } @Override public int getCount() { - return runtimes.size(); + return mRuntimes.size(); } @Override public Object getItem(int position) { - return runtimes.get(position); + return mRuntimes.get(position); } @Override public long getItemId(int position) { - return runtimes.get(position).name.hashCode(); + return mRuntimes.get(position).name.hashCode(); } @Override @@ -59,25 +59,25 @@ public class RTSpinnerAdapter implements SpinnerAdapter { @NonNull @Override public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { - View v = convertView!=null? + View view = convertView != null? convertView: - LayoutInflater.from(ctx).inflate(R.layout.multirt_recyclable_view,parent,false); + LayoutInflater.from(mContext).inflate(R.layout.item_multirt_runtime,parent,false); - MultiRTUtils.Runtime rt = runtimes.get(position); + Runtime runtime = mRuntimes.get(position); - final TextView javaVersionView = v.findViewById(R.id.multirt_view_java_version); - final TextView fullJavaVersionView = v.findViewById(R.id.multirt_view_java_version_full); - v.findViewById(R.id.multirt_view_removebtn).setVisibility(View.GONE); - v.findViewById(R.id.multirt_view_setdefaultbtn).setVisibility(View.GONE); + final TextView javaVersionView = view.findViewById(R.id.multirt_view_java_version); + final TextView fullJavaVersionView = view.findViewById(R.id.multirt_view_java_version_full); + view.findViewById(R.id.multirt_view_removebtn).setVisibility(View.GONE); + view.findViewById(R.id.multirt_view_setdefaultbtn).setVisibility(View.GONE); - if(rt.versionString != null) { - javaVersionView.setText(ctx.getString(R.string.multirt_java_ver, rt.name, rt.javaVersion)); - fullJavaVersionView.setText(rt.versionString); + if(runtime.versionString != null) { + javaVersionView.setText(mContext.getString(R.string.multirt_java_ver, runtime.name, runtime.javaVersion)); + fullJavaVersionView.setText(runtime.versionString); }else{ - javaVersionView.setText(rt.name); + javaVersionView.setText(runtime.name); fullJavaVersionView.setText(R.string.multirt_runtime_corrupt); } - return v; + return view; } @Override @@ -92,7 +92,7 @@ public class RTSpinnerAdapter implements SpinnerAdapter { @Override public boolean isEmpty() { - return runtimes.isEmpty(); + return mRuntimes.isEmpty(); } @Override diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/multirt/Runtime.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/multirt/Runtime.java new file mode 100644 index 000000000..9c4d7897e --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/multirt/Runtime.java @@ -0,0 +1,26 @@ +package net.kdt.pojavlaunch.multirt; + +import java.util.Objects; + +public class Runtime { + public Runtime(String name) { + this.name = name; + } + + public String name; + public String versionString; + public String arch; + public int javaVersion; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Runtime runtime = (Runtime) o; + return name.equals(runtime.name); + } + @Override + public int hashCode() { + return Objects.hash(name); + } +} \ No newline at end of file diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/BackButtonPreference.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/BackButtonPreference.java new file mode 100644 index 000000000..5745513b0 --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/BackButtonPreference.java @@ -0,0 +1,37 @@ +package net.kdt.pojavlaunch.prefs; + +import android.content.Context; +import android.util.AttributeSet; + +import androidx.preference.Preference; + +import net.kdt.pojavlaunch.R; +import net.kdt.pojavlaunch.extra.ExtraConstants; +import net.kdt.pojavlaunch.extra.ExtraCore; + +public class BackButtonPreference extends Preference { + public BackButtonPreference(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public BackButtonPreference(Context context) { + this(context, null); + } + + private void init(){ + if(getTitle() == null){ + setTitle(R.string.preference_back_title); + } + if(getIcon() == null){ + setIcon(R.drawable.ic_arrow_back_white); + } + } + + + @Override + protected void onClick() { + // It is caught by an ExtraListener in the LauncherActivity + ExtraCore.setValue(ExtraConstants.BACK_PREFERENCE, "true"); + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/ControlOffsetPreference.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/ControlOffsetPreference.java new file mode 100644 index 000000000..a378017a5 --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/ControlOffsetPreference.java @@ -0,0 +1,127 @@ +package net.kdt.pojavlaunch.prefs; + +import static net.kdt.pojavlaunch.prefs.LauncherPreferences.DEFAULT_PREF; +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 android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.util.AttributeSet; +import android.widget.SeekBar; +import android.widget.TextView; + +import androidx.preference.Preference; + +import net.kdt.pojavlaunch.R; + +/** Custom preference class displaying a dialog */ +public class ControlOffsetPreference extends Preference { + + private AlertDialog mPreferenceDialog; + + public ControlOffsetPreference(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public ControlOffsetPreference(Context context) { + super(context); + init(); + } + + @Override + protected void onClick() { + mPreferenceDialog.show(); + + SeekBar topOffsetSeekbar = mPreferenceDialog.findViewById(R.id.control_offset_top_seekbar); + SeekBar rightOffsetSeekbar = mPreferenceDialog.findViewById(R.id.control_offset_right_seekbar); + SeekBar bottomOffsetSeekbar = mPreferenceDialog.findViewById(R.id.control_offset_bottom_seekbar); + SeekBar leftOffsetSeekbar = mPreferenceDialog.findViewById(R.id.control_offset_left_seekbar); + + TextView topOffsetTextView = mPreferenceDialog.findViewById(R.id.control_offset_top_textview); + TextView rightOffsetTextView = mPreferenceDialog.findViewById(R.id.control_offset_right_textview); + TextView bottomOffsetTextView = mPreferenceDialog.findViewById(R.id.control_offset_bottom_textview); + TextView leftOffsetTextView = mPreferenceDialog.findViewById(R.id.control_offset_left_textview); + + SeekBar.OnSeekBarChangeListener seekBarChangeListener = new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int i, boolean b) { + if(seekBar == topOffsetSeekbar){ + String text = String.format("%s %d%s", getContext().getString(R.string.control_top_offset), i, " px"); + topOffsetTextView.setText(text); + return; + } + if(seekBar == rightOffsetSeekbar){ + String text = String.format("%s %d%s", getContext().getString(R.string.control_right_offset), i, " px"); + rightOffsetTextView.setText(text); + return; + } + if(seekBar == bottomOffsetSeekbar){ + String text = String.format("%s %d%s", getContext().getString(R.string.control_bottom_offset), i, " px"); + bottomOffsetTextView.setText(text); + return; + } + if(seekBar == leftOffsetSeekbar){ + String text = String.format("%s %d%s", getContext().getString(R.string.control_left_offset), i, " px"); + leftOffsetTextView.setText(text); + return; + } + } + @Override + public void onStartTrackingTouch(SeekBar seekBar) {} + @Override + public void onStopTrackingTouch(SeekBar seekBar) {} + }; + + topOffsetSeekbar.setOnSeekBarChangeListener(seekBarChangeListener); + rightOffsetSeekbar.setOnSeekBarChangeListener(seekBarChangeListener); + bottomOffsetSeekbar.setOnSeekBarChangeListener(seekBarChangeListener); + leftOffsetSeekbar.setOnSeekBarChangeListener(seekBarChangeListener); + + topOffsetSeekbar.setProgress(PREF_CONTROL_TOP_OFFSET); + rightOffsetSeekbar.setProgress(PREF_CONTROL_RIGHT_OFFSET); + bottomOffsetSeekbar.setProgress(PREF_CONTROL_BOTTOM_OFFSET); + leftOffsetSeekbar.setProgress(PREF_CONTROL_LEFT_OFFSET); + + seekBarChangeListener.onProgressChanged(topOffsetSeekbar, PREF_CONTROL_TOP_OFFSET, false); + seekBarChangeListener.onProgressChanged(rightOffsetSeekbar, PREF_CONTROL_RIGHT_OFFSET, false); + seekBarChangeListener.onProgressChanged(bottomOffsetSeekbar, PREF_CONTROL_BOTTOM_OFFSET, false); + seekBarChangeListener.onProgressChanged(leftOffsetSeekbar, PREF_CONTROL_LEFT_OFFSET, false); + + // Custom writing to preferences + mPreferenceDialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(view -> { + DEFAULT_PREF.edit().putInt("controlTopOffset", topOffsetSeekbar.getProgress()).apply(); + DEFAULT_PREF.edit().putInt("controlRightOffset", rightOffsetSeekbar.getProgress()).apply(); + DEFAULT_PREF.edit().putInt("controlBottomOffset", bottomOffsetSeekbar.getProgress()).apply(); + DEFAULT_PREF.edit().putInt("controlLeftOffset", leftOffsetSeekbar.getProgress()).apply(); + + + mPreferenceDialog.dismiss(); + }); + } + + private void init(){ + // Setup visual values + if(getTitle() == null){ + setTitle(R.string.preference_control_offset_title); + setSummary(R.string.preference_control_offset_description); + } + if(getIcon() == null){ + setIcon(android.R.drawable.radiobutton_off_background); + } + + // Prepare Alert dialog + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getContext()); + dialogBuilder.setView(R.layout.dialog_control_offset_preference); + dialogBuilder.setTitle(getContext().getString(R.string.control_offset_title)); + + dialogBuilder.setPositiveButton(android.R.string.ok, null); + dialogBuilder.setNegativeButton(android.R.string.cancel, null); + + mPreferenceDialog = dialogBuilder.create(); + } + +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/CustomSeekBarPreference.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/CustomSeekBarPreference.java index f973c8813..925289dd2 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/CustomSeekBarPreference.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/CustomSeekBarPreference.java @@ -15,9 +15,13 @@ import net.kdt.pojavlaunch.R; public class CustomSeekBarPreference extends SeekBarPreference { - private String suffix = ""; + /** The suffix displayed */ + private String mSuffix = ""; + /** Custom minimum value to provide the same behavior as the usual setMin */ private int mMin; - private TextView textView; + /** The textview associated by default to the preference */ + private TextView mTextView; + public CustomSeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); @@ -33,7 +37,6 @@ public class CustomSeekBarPreference extends SeekBarPreference { public CustomSeekBarPreference(Context context, AttributeSet attrs) { this(context, attrs, R.attr.seekBarPreferenceStyle); - } public CustomSeekBarPreference(Context context) { @@ -54,48 +57,48 @@ public class CustomSeekBarPreference extends SeekBarPreference { TextView titleTextView = (TextView) view.findViewById(android.R.id.title); titleTextView.setTextColor(Color.WHITE); - textView = (TextView) view.findViewById(R.id.seekbar_value); - textView.setTextAlignment(View.TEXT_ALIGNMENT_TEXT_START); + mTextView = (TextView) view.findViewById(R.id.seekbar_value); + mTextView.setTextAlignment(View.TEXT_ALIGNMENT_TEXT_START); SeekBar seekBar = (SeekBar) view.findViewById(R.id.seekbar); seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - textView.setText(String.valueOf(progress + mMin)); + progress += mMin; + progress = progress / getSeekBarIncrement(); + progress = progress * getSeekBarIncrement(); + progress -= mMin; + + mTextView.setText(String.valueOf(progress + mMin)); updateTextViewWithSuffix(); } @Override - public void onStartTrackingTouch(SeekBar seekBar) { - - } + public void onStartTrackingTouch(SeekBar seekBar) {} @Override public void onStopTrackingTouch(SeekBar seekBar) { - setValue(seekBar.getProgress() + mMin); - updateTextViewWithSuffix(); + int progress = seekBar.getProgress() + mMin; + progress /= getSeekBarIncrement(); + progress *= getSeekBarIncrement(); + progress -= mMin; + + setValue(progress + mMin); + updateTextViewWithSuffix(); } }); updateTextViewWithSuffix(); } - - - private void updateTextViewWithSuffix(){ - if(!textView.getText().toString().endsWith(suffix)){ - textView.setText(String.format("%s%s", textView.getText(), suffix)); - } - - } - /** * Set a suffix to be appended on the TextView associated to the value * @param suffix The suffix to append as a String */ public void setSuffix(String suffix) { - this.suffix = suffix; + this.mSuffix = suffix; } /** @@ -107,4 +110,11 @@ public class CustomSeekBarPreference extends SeekBarPreference { setMin(min); setMax(max); } + + + private void updateTextViewWithSuffix(){ + if(!mTextView.getText().toString().endsWith(mSuffix)){ + mTextView.setText(String.format("%s%s", mTextView.getText(), mSuffix)); + } + } } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/LauncherPreferenceFragment.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/LauncherPreferenceFragment.java deleted file mode 100644 index fca26f1c9..000000000 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/LauncherPreferenceFragment.java +++ /dev/null @@ -1,104 +0,0 @@ -package net.kdt.pojavlaunch.prefs; - - -import android.graphics.Color; -import android.os.*; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.preference.*; -import net.kdt.pojavlaunch.R; -import net.kdt.pojavlaunch.fragments.LauncherFragment; - -import android.content.*; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import static net.kdt.pojavlaunch.Architecture.is32BitsDevice; -import static net.kdt.pojavlaunch.Tools.getTotalDeviceMemory; -import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_NOTCH_SIZE; - -public class LauncherPreferenceFragment extends PreferenceFragmentCompat implements SharedPreferences.OnSharedPreferenceChangeListener -{ - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - view.setBackgroundColor(Color.parseColor("#44000000")); - super.onViewCreated(view, savedInstanceState); - } - - @Override - public void onCreatePreferences(Bundle b, String str) { - addPreferencesFromResource(R.xml.pref_main); - - //Disable notch checking behavior on android 8.1 and below. - findPreference("ignoreNotch").setVisible(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && PREF_NOTCH_SIZE != 0); - - CustomSeekBarPreference seek2 = findPreference("timeLongPressTrigger"); - seek2.setRange(100, 1000); - seek2.setValue(LauncherPreferences.PREF_LONGPRESS_TRIGGER); - seek2.setSuffix(" ms"); - - CustomSeekBarPreference seek3 = findPreference("buttonscale"); - seek3.setRange(80, 250); - seek3.setValue((int) LauncherPreferences.PREF_BUTTONSIZE); - seek3.setSuffix(" %"); - - CustomSeekBarPreference seek4 = findPreference("mousescale"); - seek4.setRange(25, 300); - seek4.setValue((int) LauncherPreferences.PREF_MOUSESCALE); - seek4.setSuffix(" %"); - - CustomSeekBarPreference seek5 = findPreference("resolutionRatio"); - seek5.setMin(25); - seek5.setSuffix(" %"); - - CustomSeekBarPreference seek6 = findPreference("mousespeed"); - seek6.setRange(25, 300); - seek6.setValue((int)(LauncherPreferences.PREF_MOUSESPEED*100f)); - seek6.setSuffix(" %"); - - - int maxRAM; - int deviceRam = getTotalDeviceMemory(getContext()); - - - CustomSeekBarPreference seek7 = findPreference("allocation"); - seek7.setMin(256); - - if(is32BitsDevice()) maxRAM = Math.min(1100, deviceRam); - else maxRAM = deviceRam - (deviceRam < 3064 ? 800 : 1024); //To have a minimum for the device to breathe - - seek7.setMax(maxRAM); - seek7.setValue(LauncherPreferences.PREF_RAM_ALLOCATION); - seek7.setSuffix(" MB"); - - // #724 bug fix - if (seek5.getValue() < 25) { - seek5.setValue(100); - } - - EditTextPreference editJVMArgs = findPreference("javaArgs"); - if (editJVMArgs != null) { - editJVMArgs.setOnBindEditTextListener((editText) -> editText.setSingleLine()); - } - } - - @Override - public void onResume() { - super.onResume(); - getPreferenceManager().getSharedPreferences().registerOnSharedPreferenceChangeListener(this); - } - - @Override - public void onPause() { - getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this); - super.onPause(); - } - - @Override - public void onSharedPreferenceChanged(SharedPreferences p, String s) { - LauncherPreferences.loadPreferences(getContext()); - } -} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/LauncherPreferences.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/LauncherPreferences.java index ee8261885..f3b325d33 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/LauncherPreferences.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/LauncherPreferences.java @@ -5,8 +5,8 @@ import net.kdt.pojavlaunch.*; import net.kdt.pojavlaunch.multirt.MultiRTUtils; import net.kdt.pojavlaunch.utils.JREUtils; -public class LauncherPreferences -{ +public class LauncherPreferences { + public static final String PREF_KEY_CURRENT_PROFILE = "currentProfile"; public static SharedPreferences DEFAULT_PREF; public static String PREF_RENDERER = "opengles2"; @@ -29,6 +29,17 @@ public class LauncherPreferences public static float PREF_MOUSESPEED = 1f; public static int PREF_RAM_ALLOCATION; public static String PREF_DEFAULT_RUNTIME; + public static int PREF_CONTROL_TOP_OFFSET = 0; + public static int PREF_CONTROL_RIGHT_OFFSET = 0; + public static int PREF_CONTROL_BOTTOM_OFFSET = 0; + public static int PREF_CONTROL_LEFT_OFFSET = 0; + public static boolean PREF_SUSTAINED_PERFORMANCE = false; + public static boolean PREF_VIRTUAL_MOUSE_START = false; + public static boolean PREF_ARC_CAPES = false; + public static boolean PREF_USE_ALTERNATE_SURFACE = true; + public static int PREF_SCALE_FACTOR = 100; + + public static void loadPreferences(Context ctx) { //Required for the data folder. Tools.initContextConstants(ctx); @@ -51,6 +62,16 @@ public class LauncherPreferences PREF_DISABLE_GESTURES = DEFAULT_PREF.getBoolean("disableGestures",false); PREF_RAM_ALLOCATION = DEFAULT_PREF.getInt("allocation", findBestRAMAllocation(ctx)); PREF_CUSTOM_JAVA_ARGS = DEFAULT_PREF.getString("javaArgs", ""); + PREF_CONTROL_TOP_OFFSET = DEFAULT_PREF.getInt("controlTopOffset", 0); + PREF_CONTROL_RIGHT_OFFSET = DEFAULT_PREF.getInt("controlRightOffset", 0); + PREF_CONTROL_BOTTOM_OFFSET = DEFAULT_PREF.getInt("controlBottomOffset", 0); + PREF_CONTROL_LEFT_OFFSET = DEFAULT_PREF.getInt("controlLeftOffset", 0); + PREF_SUSTAINED_PERFORMANCE = DEFAULT_PREF.getBoolean("sustainedPerformance", false); + PREF_VIRTUAL_MOUSE_START = DEFAULT_PREF.getBoolean("mouse_start", false); + PREF_ARC_CAPES = DEFAULT_PREF.getBoolean("arc_capes",false); + PREF_USE_ALTERNATE_SURFACE = DEFAULT_PREF.getBoolean("alternate_surface", false); + PREF_SCALE_FACTOR = DEFAULT_PREF.getInt("resolutionRatio", 100); + /* if (PREF_CUSTOM_JAVA_ARGS.isEmpty()) { String DEFAULT_JAVA_ARGS = ""; @@ -79,9 +100,6 @@ public class LauncherPreferences } */ - if (PREF_RENDERER.equals("2") || PREF_RENDERER.equals("3")) { - PREF_RENDERER = "opengles" + PREF_RENDERER; - } String argLwjglLibname = "-Dorg.lwjgl.opengl.libname="; for (String arg : JREUtils.parseJavaArguments(PREF_CUSTOM_JAVA_ARGS)) { if (arg.startsWith(argLwjglLibname)) { diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/PerVersionConfigDialog.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/PerVersionConfigDialog.java deleted file mode 100644 index 78db13c93..000000000 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/PerVersionConfigDialog.java +++ /dev/null @@ -1,120 +0,0 @@ -package net.kdt.pojavlaunch.prefs; - -import android.content.Context; -import android.content.DialogInterface; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.ArrayAdapter; -import android.widget.EditText; -import android.widget.Spinner; - -import androidx.appcompat.app.AlertDialog; - -import net.kdt.pojavlaunch.R; -import net.kdt.pojavlaunch.multirt.MultiRTUtils; -import net.kdt.pojavlaunch.multirt.RTSpinnerAdapter; -import net.kdt.pojavlaunch.tasks.RefreshVersionListTask; -import net.kdt.pojavlaunch.value.PerVersionConfig; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -public class PerVersionConfigDialog{ - final Context ctx; - final AlertDialog dialog; - final View v; - List runtimes; - final Spinner javaVMSpinner; - final Spinner rendererSpinner; - final EditText customDirText; - final EditText jvmArgsEditText; - final List renderNames; - String selectedGameVersion = null; - public PerVersionConfigDialog(Context _ctx) { - ctx = _ctx; - v = LayoutInflater.from(ctx).inflate(R.layout.pvc_popup,null); - javaVMSpinner = v.findViewById(R.id.pvc_javaVm); - rendererSpinner = v.findViewById(R.id.pvc_renderer); - { - List renderList = new ArrayList<>(); - Collections.addAll(renderList, ctx.getResources().getStringArray(R.array.renderer)); - renderList.add("Default"); - renderNames = Arrays.asList(ctx.getResources().getStringArray(R.array.renderer_values)); - rendererSpinner.setAdapter(new ArrayAdapter<>(ctx, android.R.layout.simple_spinner_dropdown_item,renderList)); - } - customDirText = v.findViewById(R.id.pvc_customDir); - jvmArgsEditText = v.findViewById(R.id.pvc_jvmArgs); - AlertDialog.Builder builder = new AlertDialog.Builder(ctx); - builder.setView(v); - builder.setTitle(R.string.pvc_title); - builder.setNegativeButton(android.R.string.cancel,(dialogInterface,i)->dialogInterface.dismiss()); - builder.setPositiveButton(android.R.string.ok,this::save); - dialog = builder.create(); - } - public void refreshRuntimes() { - if(runtimes!=null)runtimes.clear(); - runtimes = MultiRTUtils.getRuntimes(); - //runtimes.add(new MultiRTUtils.Runtime("")); - } - private void save(DialogInterface i, int which) { - if(selectedGameVersion == null) { - i.dismiss(); - return; - } - PerVersionConfig.VersionConfig conf1 = PerVersionConfig.configMap.get(selectedGameVersion); - if(conf1==null){ - conf1=new PerVersionConfig.VersionConfig(); - } - conf1.jvmArgs=jvmArgsEditText.getText().toString(); - conf1.gamePath=customDirText.getText().toString(); - - if(rendererSpinner.getSelectedItemPosition() == renderNames.size()) conf1.renderer = null; - else conf1.renderer = renderNames.get(rendererSpinner.getSelectedItemPosition()); - - String runtime=((MultiRTUtils.Runtime)javaVMSpinner.getSelectedItem()).name;; - if(!runtime.equals(""))conf1.selectedRuntime=runtime; - else conf1.selectedRuntime=null; - - PerVersionConfig.configMap.put(selectedGameVersion,conf1); - try{ - PerVersionConfig.update(); - }catch(IOException e){ - e.printStackTrace(); - } - } - public boolean openConfig(String selectedVersion) { - selectedGameVersion = selectedVersion; - try{ - PerVersionConfig.update(); - }catch(IOException e){ - e.printStackTrace(); - } - PerVersionConfig.VersionConfig conf=PerVersionConfig.configMap.get(selectedGameVersion); - refreshRuntimes(); - javaVMSpinner.setAdapter(new RTSpinnerAdapter(ctx,runtimes)); - { - int jvm_index = runtimes.indexOf(new MultiRTUtils.Runtime("")); - int rnd_index = rendererSpinner.getAdapter().getCount()-1; - if (conf != null) { - customDirText.setText(conf.gamePath); - jvmArgsEditText.setText(conf.jvmArgs); - if (conf.selectedRuntime != null) { - int nindex = runtimes.indexOf(new MultiRTUtils.Runtime(conf.selectedRuntime)); - if (nindex != -1) jvm_index = nindex; - } - if(conf.renderer != null) { - int nindex = renderNames.indexOf(conf.renderer); - if (nindex != -1) rnd_index = nindex; - } - } - javaVMSpinner.setSelection(jvm_index); - rendererSpinner.setSelection(rnd_index); - } - dialog.show(); - return true; - } -} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/RuntimeManagerPreference.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/RuntimeManagerPreference.java index 24ff16977..90d459726 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/RuntimeManagerPreference.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/RuntimeManagerPreference.java @@ -6,7 +6,6 @@ import android.util.AttributeSet; import androidx.preference.Preference; import net.kdt.pojavlaunch.BaseLauncherActivity; -import net.kdt.pojavlaunch.multirt.MultiRTConfigDialog; public class RuntimeManagerPreference extends Preference{ public RuntimeManagerPreference(Context ctx) { @@ -17,9 +16,10 @@ public class RuntimeManagerPreference extends Preference{ super(ctx, attrs); setPersistent(false); } + @Override protected void onClick() { super.onClick(); - ((BaseLauncherActivity)this.getContext()).mRuntimeConfigDialog.dialog.show(); + ((BaseLauncherActivity)this.getContext()).mRuntimeConfigDialog.mDialog.show(); } } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceControlFragment.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceControlFragment.java new file mode 100644 index 000000000..7c3920a26 --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceControlFragment.java @@ -0,0 +1,58 @@ +package net.kdt.pojavlaunch.prefs.screens; + +import android.content.SharedPreferences; +import android.os.Bundle; + +import net.kdt.pojavlaunch.R; +import net.kdt.pojavlaunch.prefs.CustomSeekBarPreference; +import net.kdt.pojavlaunch.prefs.LauncherPreferences; + +public class LauncherPreferenceControlFragment extends LauncherPreferenceFragment { + + @Override + public void onCreatePreferences(Bundle b, String str) { + // Get values + int longPressTrigger = LauncherPreferences.PREF_LONGPRESS_TRIGGER; + int prefButtonSize = (int) LauncherPreferences.PREF_BUTTONSIZE; + int mouseScale = (int) LauncherPreferences.PREF_MOUSESCALE; + float mouseSpeed = LauncherPreferences.PREF_MOUSESPEED; + + //Triggers a write for some reason which resets the value + addPreferencesFromResource(R.xml.pref_control); + + CustomSeekBarPreference seek2 = findPreference("timeLongPressTrigger"); + seek2.setRange(100, 1000); + seek2.setValue(longPressTrigger); + seek2.setSuffix(" ms"); + + CustomSeekBarPreference seek3 = findPreference("buttonscale"); + seek3.setRange(80, 250); + seek3.setValue(prefButtonSize); + seek3.setSuffix(" %"); + + CustomSeekBarPreference seek4 = findPreference("mousescale"); + seek4.setRange(25, 300); + seek4.setValue(mouseScale); + seek4.setSuffix(" %"); + + CustomSeekBarPreference seek6 = findPreference("mousespeed"); + seek6.setRange(25, 300); + seek6.setValue((int)(mouseSpeed *100f)); + seek6.setSuffix(" %"); + + + computeVisibility(); + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences p, String s) { + super.onSharedPreferenceChanged(p, s); + computeVisibility(); + } + + private void computeVisibility(){ + CustomSeekBarPreference seek2 = findPreference("timeLongPressTrigger"); + seek2.setVisible(!LauncherPreferences.PREF_DISABLE_GESTURES); + } + +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceExperimentalFragment.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceExperimentalFragment.java new file mode 100644 index 000000000..6b5ea17b8 --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceExperimentalFragment.java @@ -0,0 +1,13 @@ +package net.kdt.pojavlaunch.prefs.screens; + +import android.os.Bundle; + +import net.kdt.pojavlaunch.R; + +public class LauncherPreferenceExperimentalFragment extends LauncherPreferenceFragment { + + @Override + public void onCreatePreferences(Bundle b, String str) { + addPreferencesFromResource(R.xml.pref_experimental); + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceFragment.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceFragment.java new file mode 100644 index 000000000..531d0d30d --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceFragment.java @@ -0,0 +1,57 @@ +package net.kdt.pojavlaunch.prefs.screens; + + +import android.graphics.Color; +import android.os.*; + +import androidx.activity.OnBackPressedCallback; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.preference.*; +import net.kdt.pojavlaunch.R; +import net.kdt.pojavlaunch.prefs.LauncherPreferences; + +import android.content.*; +import android.view.View; +import android.widget.Toast; + +import static net.kdt.pojavlaunch.Architecture.is32BitsDevice; +import static net.kdt.pojavlaunch.Tools.getTotalDeviceMemory; +import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_NOTCH_SIZE; + +/** + * Preference for the main screen, any sub-screen should inherit this class for consistent behavior, + * overriding only onCreatePreferences + */ +public class LauncherPreferenceFragment extends PreferenceFragmentCompat implements SharedPreferences.OnSharedPreferenceChangeListener { + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + view.setBackgroundColor(Color.parseColor("#232323")); + super.onViewCreated(view, savedInstanceState); + } + + @Override + public void onCreatePreferences(Bundle b, String str) { + addPreferencesFromResource(R.xml.pref_main); + } + + @Override + public void onResume() { + super.onResume(); + getPreferenceManager().getSharedPreferences().registerOnSharedPreferenceChangeListener(this); + } + + @Override + public void onPause() { + getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this); + super.onPause(); + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences p, String s) { + LauncherPreferences.loadPreferences(getContext()); + } + + +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceJavaFragment.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceJavaFragment.java new file mode 100644 index 000000000..9b8a41d34 --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceJavaFragment.java @@ -0,0 +1,43 @@ +package net.kdt.pojavlaunch.prefs.screens; + +import static net.kdt.pojavlaunch.Architecture.is32BitsDevice; +import static net.kdt.pojavlaunch.Tools.getTotalDeviceMemory; + +import android.os.Bundle; +import android.widget.TextView; + +import androidx.preference.EditTextPreference; + +import net.kdt.pojavlaunch.R; +import net.kdt.pojavlaunch.prefs.CustomSeekBarPreference; +import net.kdt.pojavlaunch.prefs.LauncherPreferences; + +public class LauncherPreferenceJavaFragment extends LauncherPreferenceFragment { + @Override + public void onCreatePreferences(Bundle b, String str) { + int ramAllocation = LauncherPreferences.PREF_RAM_ALLOCATION; + + // Triggers a write for some reason + addPreferencesFromResource(R.xml.pref_java); + + int maxRAM; + int deviceRam = getTotalDeviceMemory(getContext()); + + CustomSeekBarPreference seek7 = findPreference("allocation"); + seek7.setMin(256); + + if(is32BitsDevice()) maxRAM = Math.min(1100, deviceRam); + else maxRAM = deviceRam - (deviceRam < 3064 ? 800 : 1024); //To have a minimum for the device to breathe + + seek7.setMax(maxRAM); + seek7.setValue(ramAllocation); + seek7.setSuffix(" MB"); + + + EditTextPreference editJVMArgs = findPreference("javaArgs"); + if (editJVMArgs != null) { + editJVMArgs.setOnBindEditTextListener(TextView::setSingleLine); + } + + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceMiscellaneousFragment.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceMiscellaneousFragment.java new file mode 100644 index 000000000..da42959be --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceMiscellaneousFragment.java @@ -0,0 +1,19 @@ +package net.kdt.pojavlaunch.prefs.screens; + +import android.os.Build; +import android.os.Bundle; + +import androidx.preference.SwitchPreference; + +import net.kdt.pojavlaunch.R; + +public class LauncherPreferenceMiscellaneousFragment extends LauncherPreferenceFragment { + @Override + public void onCreatePreferences(Bundle b, String str) { + addPreferencesFromResource(R.xml.pref_misc); + + // Sustained performance is only available since Nougat + SwitchPreference sustainedPerfSwitch = findPreference("sustainedPerformance"); + sustainedPerfSwitch.setVisible(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N); + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceVideoFragment.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceVideoFragment.java new file mode 100644 index 000000000..9b0ec22fb --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceVideoFragment.java @@ -0,0 +1,33 @@ +package net.kdt.pojavlaunch.prefs.screens; + +import static net.kdt.pojavlaunch.prefs.LauncherPreferences.PREF_NOTCH_SIZE; + +import android.os.Build; +import android.os.Bundle; + +import androidx.preference.PreferenceFragmentCompat; + +import net.kdt.pojavlaunch.R; +import net.kdt.pojavlaunch.prefs.CustomSeekBarPreference; + +/** + * Fragment for any settings video related + */ +public class LauncherPreferenceVideoFragment extends LauncherPreferenceFragment { + @Override + public void onCreatePreferences(Bundle b, String str) { + addPreferencesFromResource(R.xml.pref_video); + + //Disable notch checking behavior on android 8.1 and below. + findPreference("ignoreNotch").setVisible(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && PREF_NOTCH_SIZE > 0); + + CustomSeekBarPreference seek5 = findPreference("resolutionRatio"); + seek5.setMin(25); + seek5.setSuffix(" %"); + + // #724 bug fix + if (seek5.getValue() < 25) { + seek5.setValue(100); + } + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/profiles/ProfileAdapter.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/profiles/ProfileAdapter.java new file mode 100644 index 000000000..2f81fad53 --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/profiles/ProfileAdapter.java @@ -0,0 +1,126 @@ +package net.kdt.pojavlaunch.profiles; + +import android.content.Context; +import android.graphics.Bitmap; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.TextView; + +import net.kdt.pojavlaunch.R; +import net.kdt.pojavlaunch.value.launcherprofiles.LauncherProfiles; +import net.kdt.pojavlaunch.value.launcherprofiles.MinecraftProfile; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +/* + * Adapter for listing launcher profiles in a Spinner + */ +public class ProfileAdapter extends BaseAdapter { + private Map mProfiles; + public static final String CREATE_PROFILE_MAGIC = "___extra____profile-create"; + private final MinecraftProfile dummy = new MinecraftProfile(); + private MinecraftProfile mCreateProfile; + private List mProfileList; + public ProfileAdapter(Context context, boolean enableCreateButton) { + ProfileIconCache.initDefault(context); + LauncherProfiles.update(); + mProfiles = new HashMap<>(LauncherProfiles.mainProfileJson.profiles); + if(enableCreateButton) { + mCreateProfile = new MinecraftProfile(); + mCreateProfile.name = "Create new profile"; + mCreateProfile.lastVersionId = ""; + } + mProfileList = new ArrayList<>(Arrays.asList(mProfiles.keySet().toArray(new String[0]))); + if(enableCreateButton) { + mProfileList.add(ProfileAdapter.CREATE_PROFILE_MAGIC); + mProfiles.put(CREATE_PROFILE_MAGIC, mCreateProfile); + } + } + /* + * Gets how much profiles are loaded in the adapter right now + * @returns loaded profile count + */ + @Override + public int getCount() { + return mProfileList.size(); + } + /* + * Gets the profile at a given index + * @param position index to retreive + * @returns MinecraftProfile name or null + */ + @Override + public Object getItem(int position) { + //safe since the second check in the and statement will be skipped if the first one fails + if(position < mProfileList.size() && mProfiles.containsKey(mProfileList.get(position))) { + return mProfileList.get(position); + }else{ + return null; + } + } + + public int resolveProfileIndex(String name) { + return mProfileList.indexOf(name); + } + + public void fireProfileEdit() { + notifyDataSetChanged(); + } + /* + * Gets the item ID (crc64 hash of the profile name) for a given index + * @param position index to get the hash for + * @returns ID (crc64 of a profile name string) or -1 if the index is out of bounds + */ + @Override + public long getItemId(int position) { + return position; + } + + @Override + public void notifyDataSetChanged() { + mProfiles = new HashMap<>(LauncherProfiles.mainProfileJson.profiles); + mProfileList = new ArrayList<>(Arrays.asList(mProfiles.keySet().toArray(new String[0]))); + mProfileList.add(ProfileAdapter.CREATE_PROFILE_MAGIC); + mProfiles.put(CREATE_PROFILE_MAGIC, mCreateProfile); + super.notifyDataSetChanged(); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + View v = convertView; + if (v == null) v = LayoutInflater.from(parent.getContext()).inflate(R.layout.version_profile_layout,parent,false); + setViewProfile(v,mProfileList.get(position)); + return v; + } + public void setViewProfile(View v, String nm) { + MinecraftProfile minecraftProfile = mProfiles.get(nm); + if(minecraftProfile == null) minecraftProfile = dummy; + Bitmap cachedIcon = ProfileIconCache.getCachedIcon(nm); + ImageView iconView = v.findViewById(R.id.vprof_icon_view); + if(cachedIcon == null) { + cachedIcon = ProfileIconCache.tryResolveIcon(nm,minecraftProfile.icon); + } + iconView.setImageBitmap(cachedIcon); + if(minecraftProfile.name != null && !minecraftProfile.name.isEmpty()) + ((TextView)v.findViewById(R.id.vprof_profile_name_view)).setText(minecraftProfile.name); + else + ((TextView)v.findViewById(R.id.vprof_profile_name_view)).setText(R.string.unnamed); + + TextView tv = v.findViewById(R.id.vprof_version_id_view); + if(minecraftProfile.lastVersionId != null) switch (minecraftProfile.lastVersionId) { + case "latest-release": + tv.setText(R.string.profiles_latest_release); + case "latest-snapshot": + tv.setText(R.string.profiles_latest_snapshot); + default: + tv.setText(minecraftProfile.lastVersionId); + } else tv.setText(android.R.string.unknownName); + + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/profiles/ProfileEditor.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/profiles/ProfileEditor.java new file mode 100644 index 000000000..3dd2203d8 --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/profiles/ProfileEditor.java @@ -0,0 +1,221 @@ +package net.kdt.pojavlaunch.profiles; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.graphics.Bitmap; +import android.os.Handler; +import android.os.Looper; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ArrayAdapter; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.Spinner; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.kdt.pojavlaunch.BaseLauncherActivity; +import net.kdt.pojavlaunch.PojavLauncherActivity; +import net.kdt.pojavlaunch.R; +import net.kdt.pojavlaunch.Tools; +import net.kdt.pojavlaunch.extra.ExtraCore; +import net.kdt.pojavlaunch.extra.ExtraListener; +import net.kdt.pojavlaunch.extra.ExtraConstants; +import net.kdt.pojavlaunch.multirt.MultiRTUtils; +import net.kdt.pojavlaunch.multirt.RTSpinnerAdapter; +import net.kdt.pojavlaunch.value.launcherprofiles.LauncherProfiles; +import net.kdt.pojavlaunch.value.launcherprofiles.MinecraftProfile; +import net.kdt.pojavlaunch.multirt.Runtime; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +public class ProfileEditor implements ExtraListener> { + private final View mMainView; + private final TextView mProfileNameTextView; + private final ImageView mProfileIconImageView; + private final Spinner mVersionSpinner; + private final Spinner mJavaRuntimeSpinner; + private final Spinner mRendererSpinner; + private final EditText mPathSelectionEditText; + private final EditText mArgChangerEditText; + private final List mRenderNames; + private final AlertDialog mDialog; + private String mSelectedVersionId; + private String mEditingProfile; + private final EditSaveCallback mEditSaveCallback; + private final Handler mUiThreadHandler; + + public static MinecraftProfile generateTemplate() { + MinecraftProfile TEMPLATE = new MinecraftProfile(); + TEMPLATE.name = "New"; + TEMPLATE.lastVersionId = "latest-release"; + return TEMPLATE; + } + public ProfileEditor(Context ctx, EditSaveCallback editSaveCallback) { + this.mEditSaveCallback = editSaveCallback; + mUiThreadHandler = new Handler(Looper.getMainLooper()); + LayoutInflater inflater = LayoutInflater.from(ctx); + mMainView = inflater.inflate(R.layout.version_profile_editor,null); + AlertDialog.Builder builder = new AlertDialog.Builder(ctx); + builder.setView(mMainView); + mProfileNameTextView = mMainView.findViewById(R.id.vprof_editior_profile_name); + mVersionSpinner = mMainView.findViewById(R.id.vprof_editor_version_spinner); + mJavaRuntimeSpinner = mMainView.findViewById(R.id.vprof_editor_spinner_runtime); + mRendererSpinner = mMainView.findViewById(R.id.vprof_editor_profile_renderer); + { + Context context = mRendererSpinner.getContext(); + List renderList = new ArrayList<>(); + Collections.addAll(renderList, context.getResources().getStringArray(R.array.renderer)); + renderList.add("Default"); + mRenderNames = Arrays.asList(context.getResources().getStringArray(R.array.renderer_values)); + mRendererSpinner.setAdapter(new ArrayAdapter<>(context, android.R.layout.simple_spinner_dropdown_item,renderList)); + } + mProfileIconImageView = mMainView.findViewById(R.id.vprof_editor_icon); + ((TextView)mMainView.findViewById(R.id.vprof_editor_beginPathView)).setText(Tools.DIR_GAME_HOME+"/"); + mPathSelectionEditText = mMainView.findViewById(R.id.vprof_editor_path); + mArgChangerEditText = mMainView.findViewById(R.id.vprof_editor_jre_args); + + builder.setPositiveButton(R.string.global_save,this::save); + builder.setNegativeButton(android.R.string.cancel,(dialog,which)->destroy(dialog)); + builder.setNeutralButton(R.string.global_delete,(dialogInterface, i) -> { + LauncherProfiles.mainProfileJson.profiles.remove(mEditingProfile); + this.mEditSaveCallback.onSave(mEditingProfile,false, true); + }); + builder.setOnDismissListener(this::destroy); + mDialog = builder.create(); + } + + public boolean show(@NonNull String profile) { + MinecraftProfile minecraftProfile; + if(!ProfileAdapter.CREATE_PROFILE_MAGIC.equals(profile)) { + minecraftProfile = LauncherProfiles.mainProfileJson.profiles.get(profile); + if (minecraftProfile == null) return true; + mEditingProfile = profile; + }else{ + minecraftProfile = generateTemplate(); + String uuid = UUID.randomUUID().toString(); + while(LauncherProfiles.mainProfileJson.profiles.containsKey(uuid)) { + uuid = UUID.randomUUID().toString(); + } + mEditingProfile = uuid; + } + + List runtimes = MultiRTUtils.getRuntimes(); + Context context = mJavaRuntimeSpinner.getContext(); + mJavaRuntimeSpinner.setAdapter(new RTSpinnerAdapter(context, runtimes)); + int jvmIndex = runtimes.indexOf(new Runtime("")); + int rendererIndex = mRendererSpinner.getAdapter().getCount()-1; + if (minecraftProfile.javaDir != null) { + String selectedRuntime = minecraftProfile.javaDir.substring(Tools.LAUNCHERPROFILES_RTPREFIX.length()); + int nindex = runtimes.indexOf(new Runtime(selectedRuntime)); + if (nindex != -1) jvmIndex = nindex; + } + if(minecraftProfile.pojavRendererName != null) { + int nindex = mRenderNames.indexOf(minecraftProfile.pojavRendererName); + if(nindex != -1) rendererIndex = nindex; + } + mJavaRuntimeSpinner.setSelection(jvmIndex); + mRendererSpinner.setSelection(rendererIndex); + ExtraCore.addExtraListener(ExtraConstants.VERSION_LIST,this); + mProfileNameTextView.setText(minecraftProfile.name); + Bitmap profileIcon = ProfileIconCache.getCachedIcon(profile); + if(profileIcon == null) { + profileIcon = ProfileIconCache.tryResolveIcon(profile,minecraftProfile.icon); + } + mProfileIconImageView.setImageBitmap(profileIcon); + if(minecraftProfile.lastVersionId != null && !"latest-release".equals(minecraftProfile.lastVersionId) && !"latest-snapshot".equals(minecraftProfile.lastVersionId)) + mSelectedVersionId = minecraftProfile.lastVersionId; + else if(minecraftProfile.lastVersionId != null) { + Map releaseTable = (Map)ExtraCore.getValue(ExtraConstants.RELEASE_TABLE); + if(releaseTable != null) { + switch (minecraftProfile.lastVersionId) { + case "latest-release": + mSelectedVersionId = releaseTable.get("release"); + case "latest-snapshot": + mSelectedVersionId = releaseTable.get("snapshot"); + } + }else{ + mSelectedVersionId = null; + } + } + else{ + if(PojavLauncherActivity.basicVersionList.length > 0) { + mSelectedVersionId = PojavLauncherActivity.basicVersionList[0]; + } + } + ArrayList versions = (ArrayList) ExtraCore.getValue(ExtraConstants.VERSION_LIST); + + BaseLauncherActivity.updateVersionSpinner(context,versions,mVersionSpinner, mSelectedVersionId); + if(minecraftProfile.gameDir != null && minecraftProfile.gameDir.startsWith(Tools.LAUNCHERPROFILES_RTPREFIX)) + mPathSelectionEditText.setText(minecraftProfile.gameDir.substring(Tools.LAUNCHERPROFILES_RTPREFIX.length())); + else mPathSelectionEditText.setText(""); + + if(minecraftProfile.javaArgs != null) mArgChangerEditText.setText(minecraftProfile.javaArgs); + else mArgChangerEditText.setText(""); + mDialog.show(); + return true; + } + public void save(DialogInterface dialog, int which) { + System.out.println(mEditingProfile); + MinecraftProfile profile; + boolean isNew; + if(LauncherProfiles.mainProfileJson.profiles.containsKey(mEditingProfile)) { + profile = LauncherProfiles.mainProfileJson.profiles.get(mEditingProfile); + if(profile == null) { + profile = new MinecraftProfile(); + isNew = true; + }else{ + isNew = false; + } + LauncherProfiles.mainProfileJson.profiles.remove(mEditingProfile); + }else{ + profile = new MinecraftProfile(); + isNew = true; + } + profile.name = mProfileNameTextView.getText().toString(); + profile.lastVersionId = (String) mVersionSpinner.getSelectedItem(); + Runtime selectedRuntime = (Runtime) mJavaRuntimeSpinner.getSelectedItem(); + if(selectedRuntime.name.equals("")) { + profile.javaDir = null; + }else if(selectedRuntime.versionString == null) { + profile.javaDir = null; + }else{ + profile.javaDir = Tools.LAUNCHERPROFILES_RTPREFIX+selectedRuntime.name; + } + + if(mRendererSpinner.getSelectedItemPosition() == mRenderNames.size()) profile.pojavRendererName = null; + else profile.pojavRendererName = mRenderNames.get(mRendererSpinner.getSelectedItemPosition()); + String selectedPath = mPathSelectionEditText.getText().toString(); + String arguments = mArgChangerEditText.getText().toString(); + if(!selectedPath.isEmpty()) profile.gameDir = Tools.LAUNCHERPROFILES_RTPREFIX+selectedPath; + else profile.gameDir = null; + if(!arguments.isEmpty()) profile.javaArgs = arguments; + else profile.javaArgs = null; + LauncherProfiles.mainProfileJson.profiles.put(mEditingProfile,profile); + mEditSaveCallback.onSave(mEditingProfile,isNew, false); + destroy(dialog); + } + public void destroy(@NonNull DialogInterface dialog) { + ExtraCore.removeExtraListenerFromValue(ExtraConstants.VERSION_LIST,this); + mEditingProfile = null; + mSelectedVersionId = null; + } + @Override + public boolean onValueSet(String key, @Nullable ArrayList value) { + if(value != null) { + mUiThreadHandler.post(() -> BaseLauncherActivity.updateVersionSpinner(mMainView.getContext(), value, mVersionSpinner, mSelectedVersionId)); + } + return false; + } + public interface EditSaveCallback { + void onSave(String name, boolean isNew, boolean isRemoving); + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/profiles/ProfileIconCache.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/profiles/ProfileIconCache.java new file mode 100644 index 000000000..90cfdd75d --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/profiles/ProfileIconCache.java @@ -0,0 +1,54 @@ +package net.kdt.pojavlaunch.profiles; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.util.Base64; +import android.util.Log; + +import net.kdt.pojavlaunch.R; + +import java.util.HashMap; +import java.util.Map; + +public class ProfileIconCache { + private static final String BASE64_PNG_HEADER = "data:image/png;base64,"; + private static final Map sIconCache = new HashMap<>(); + private static Bitmap sDefaultIcon; + public static void initDefault(Context context) { + if(sDefaultIcon == null) + sDefaultIcon = BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_menu_java); + } + public static void clearIconCache() { + for(String key : sIconCache.keySet()) { + Bitmap bitmap = sIconCache.get(key); + if(bitmap != sDefaultIcon && bitmap != null) { + bitmap.recycle(); + } + } + sIconCache.clear(); + } + public static Bitmap getCachedIcon(String key) { + return sIconCache.get(key); + } + public static Bitmap submitIcon(String key, String base64) { + byte[] pngBytes = Base64.decode(base64, Base64.DEFAULT); + Bitmap cachedIcon = BitmapFactory.decodeByteArray(pngBytes,0,pngBytes.length); + sIconCache.put(key, cachedIcon); + return cachedIcon; + } + public static Bitmap tryResolveIcon(String profileName, String b64Icon) { + Bitmap icon; + if (b64Icon != null && b64Icon.startsWith(BASE64_PNG_HEADER)) { + icon = ProfileIconCache.submitIcon(profileName, b64Icon.substring(BASE64_PNG_HEADER.length())); + }else{ + Log.i("IconParser","Unsupported icon: "+b64Icon); + icon = ProfileIconCache.pushDefaultIcon(profileName); + } + return icon; + } + public static Bitmap pushDefaultIcon(String key) { + sIconCache.put(key, sDefaultIcon); + return sDefaultIcon; + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/scoped/GameFolderProvider.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/scoped/GameFolderProvider.java index cdf997062..c8a92ba1f 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/scoped/GameFolderProvider.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/scoped/GameFolderProvider.java @@ -23,7 +23,7 @@ import java.util.Collections; import java.util.LinkedList; public class GameFolderProvider extends DocumentsProvider { - static File baseDir = new File(Tools.DIR_GAME_HOME); + static File sBaseDir = new File(Tools.DIR_GAME_HOME); private static final String[] DEFAULT_ROOT_PROJECTION = new String[]{ Root.COLUMN_ROOT_ID, Root.COLUMN_MIME_TYPES, @@ -47,13 +47,13 @@ public class GameFolderProvider extends DocumentsProvider { public Cursor queryRoots(String[] projection) throws FileNotFoundException { final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_ROOT_PROJECTION); final MatrixCursor.RowBuilder row = result.newRow(); - row.add(Root.COLUMN_ROOT_ID, baseDir.getAbsolutePath()); - row.add(Root.COLUMN_DOCUMENT_ID, baseDir.getAbsolutePath()); + row.add(Root.COLUMN_ROOT_ID, sBaseDir.getAbsolutePath()); + row.add(Root.COLUMN_DOCUMENT_ID, sBaseDir.getAbsolutePath()); row.add(Root.COLUMN_SUMMARY, null); row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_RECENTS | Root.FLAG_SUPPORTS_SEARCH); row.add(Root.COLUMN_TITLE, getContext().getString(R.string.app_name)); row.add(Root.COLUMN_MIME_TYPES, "*/*"); - row.add(Root.COLUMN_AVAILABLE_BYTES, baseDir.getFreeSpace()); + row.add(Root.COLUMN_AVAILABLE_BYTES, sBaseDir.getFreeSpace()); row.add(Root.COLUMN_ICON, R.mipmap.ic_launcher); return result; } @@ -112,7 +112,7 @@ public class GameFolderProvider extends DocumentsProvider { final File file = pending.removeFirst(); boolean isInsideGameDir; try { - isInsideGameDir = file.getCanonicalPath().startsWith(baseDir.getCanonicalPath()); + isInsideGameDir = file.getCanonicalPath().startsWith(sBaseDir.getCanonicalPath()); } catch (IOException e) { isInsideGameDir = true; } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/tasks/MinecraftDownloaderTask.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/tasks/MinecraftDownloaderTask.java index 98b24549e..7aafecc78 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/tasks/MinecraftDownloaderTask.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/tasks/MinecraftDownloaderTask.java @@ -2,20 +2,20 @@ package net.kdt.pojavlaunch.tasks; import android.app.*; import android.content.*; -import android.content.res.AssetManager; -import android.graphics.*; import android.os.*; import android.util.*; -import com.google.gson.*; + import java.io.*; import java.util.*; import java.util.concurrent.*; import net.kdt.pojavlaunch.*; import net.kdt.pojavlaunch.multirt.MultiRTUtils; +import net.kdt.pojavlaunch.multirt.Runtime; 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.io.*; @@ -91,23 +91,24 @@ public class MinecraftDownloaderTask extends AsyncTask{ AlertDialog.Builder bldr = new AlertDialog.Builder(mActivity); bldr.setTitle(R.string.global_error); - bldr.setMessage(R.string.multirt_nocompartiblert); + bldr.setMessage(mActivity.getString(R.string.multirt_nocompartiblert, verInfo.javaVersion.majorVersion)); bldr.setPositiveButton(android.R.string.ok,(dialog, which)->{ dialog.dismiss(); }); @@ -130,6 +131,36 @@ public class MinecraftDownloaderTask extends AsyncTask assetsObjects = assets.objects; int assetsSizeBytes=0; @@ -411,7 +444,7 @@ public class MinecraftDownloaderTask extends AsyncTask{ try { - if (!assets.map_to_resources) { + if (!assets.mapToResources) { downloadAsset(asset, objectsDir, downloadedSize); } else { downloadAssetMapped(asset, assetKey, outputDir, downloadedSize); @@ -446,8 +479,8 @@ public class MinecraftDownloaderTask extends AsyncTask> { @@ -41,16 +32,18 @@ public class RefreshVersionListTask extends AsyncTask versionStringList = filter(mActivity.mVersionList.versions, new File(Tools.DIR_HOME_VERSION).listFiles()); - return versionStringList; + return filter(mActivity.mVersionList.versions, new File(Tools.DIR_HOME_VERSION).listFiles()); } catch (Exception e){ + System.out.println("Refreshing version list failed !"); e.printStackTrace(); } return null; @@ -60,62 +53,7 @@ public class RefreshVersionListTask extends AsyncTask result) { super.onPostExecute(result); - final PopupMenu popup = new PopupMenu(mActivity, mActivity.mVersionSelector); - popup.getMenuInflater().inflate(R.menu.menu_versionopt, popup.getMenu()); - - if(result != null && result.size() > 0) { - ArrayAdapter adapter = new ArrayAdapter(mActivity, android.R.layout.simple_spinner_item, result); - adapter.setDropDownViewResource(android.R.layout.simple_list_item_single_choice); - mActivity.mVersionSelector.setAdapter(adapter); - mActivity.mVersionSelector.setSelection(selectAt(result.toArray(new String[0]), mActivity.mProfile.selectedVersion)); - } else { - mActivity.mVersionSelector.setSelection(selectAt(mActivity.mAvailableVersions, mActivity.mProfile.selectedVersion)); - } - PerVersionConfigDialog dialog = new PerVersionConfigDialog(this.mActivity); - mActivity.mVersionSelector.setOnLongClickListener((v)->dialog.openConfig(mActivity.mProfile.selectedVersion)); - mActivity.mVersionSelector.setOnItemSelectedListener(new OnItemSelectedListener(){ - @Override - public void onItemSelected(AdapterView p1, View p2, int p3, long p4) - { - String version = p1.getItemAtPosition(p3).toString(); - mActivity.mProfile.selectedVersion = version; - - PojavProfile.setCurrentProfile(mActivity, mActivity.mProfile); - if (PojavProfile.isFileType(mActivity)) { - try { - PojavProfile.setCurrentProfile(mActivity, mActivity.mProfile.save()); - } catch (IOException e) { - Tools.showError(mActivity, e); - } - } - - mActivity.mTextVersion.setText(mActivity.getString(R.string.mcl_version_msg, version)); - } - - @Override - public void onNothingSelected(AdapterView p1) - { - // TODO: Implement this method - } - }); - /*mActivity.mVersionSelector.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener(){ - @Override - public boolean onItemLongClick(AdapterView p1, View p2, int p3, long p4) - { - // Implement copy, remove, reinstall,... - - - return true; - } - }); - */ - popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { - public boolean onMenuItemClick(MenuItem item) { - return true; - } - }); - - mActivity.mTextVersion.setText(mActivity.getString(R.string.mcl_version_msg,mActivity.mVersionSelector.getSelectedItem())); + ExtraCore.setValue(ExtraConstants.VERSION_LIST,result); } private ArrayList filter(JMinecraftVersionList.Version[] list1, File[] list2) { @@ -140,15 +78,24 @@ public class RefreshVersionListTask extends AsyncTask strArr, String select) { + int count = 0; + for(String str : strArr){ + if (str.equals(select)) { + return count; + } + count++; + } + return -1; + } } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/JREUtils.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/JREUtils.java index 0656801e5..e2c7a720c 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/JREUtils.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/JREUtils.java @@ -1,9 +1,9 @@ package net.kdt.pojavlaunch.utils; import static net.kdt.pojavlaunch.Architecture.ARCH_X86; -import static net.kdt.pojavlaunch.Architecture.archAsString; import static net.kdt.pojavlaunch.Architecture.is64BitsDevice; import static net.kdt.pojavlaunch.Tools.LOCAL_RENDERER; +import static net.kdt.pojavlaunch.Tools.currentDisplayMetrics; import android.app.*; import android.content.*; @@ -16,6 +16,8 @@ import com.oracle.dalvik.*; import java.io.*; import java.util.*; import net.kdt.pojavlaunch.*; +import net.kdt.pojavlaunch.extra.ExtraConstants; +import net.kdt.pojavlaunch.extra.ExtraCore; import net.kdt.pojavlaunch.prefs.*; import org.lwjgl.glfw.*; @@ -28,34 +30,20 @@ public class JREUtils { private JREUtils() {} public static String LD_LIBRARY_PATH; - private static String nativeLibDir; + public static Map jreReleaseList; + public static String jvmLibraryPath; + private static String sNativeLibDir; - /** - * Checks if the java architecture is correct for the device architecture. - * @param act An Activity with logging capabilities - * @param jreArch The java architecture to compare as a String. - */ - public static void checkJavaArchitecture(LoggableActivity act, String jreArch) { - act.appendlnToLog("Architecture: " + archAsString(Tools.DEVICE_ARCHITECTURE)); - if(Tools.DEVICE_ARCHITECTURE == Architecture.archAsInt(jreArch)) return; - - act.appendlnToLog("Architecture " + archAsString(Tools.DEVICE_ARCHITECTURE) + " is incompatible with Java Runtime " + jreArch); - throw new RuntimeException(act.getString(R.string.mcn_check_fail_incompatiblearch, archAsString(Tools.DEVICE_ARCHITECTURE), jreArch)); - - } - public static String findInLdLibPath(String libName) { if(Os.getenv("LD_LIBRARY_PATH")==null) { try { if (LD_LIBRARY_PATH != null) { Os.setenv("LD_LIBRARY_PATH", LD_LIBRARY_PATH, true); - }else{ - return libName; } }catch (ErrnoException e) { e.printStackTrace(); - return libName; } + return libName; } for (String libPath : Os.getenv("LD_LIBRARY_PATH").split(":")) { File f = new File(libPath, libName); @@ -65,18 +53,22 @@ public class JREUtils { } return libName; } + public static ArrayList locateLibs(File path) { - ArrayList ret = new ArrayList<>(); + ArrayList returnValue = new ArrayList<>(); File[] list = path.listFiles(); - if(list != null) {for(File f : list) { - if(f.isFile() && f.getName().endsWith(".so")) { - ret.add(f); - }else if(f.isDirectory()) { - ret.addAll(locateLibs(f)); + if(list != null) { + for(File f : list) { + if(f.isFile() && f.getName().endsWith(".so")) { + returnValue.add(f); + }else if(f.isDirectory()) { + returnValue.addAll(locateLibs(f)); + } } - }} - return ret; + } + return returnValue; } + public static void initJavaRuntime() { dlopen(findInLdLibPath("libjli.so")); if(!dlopen("libjvm.so")){ @@ -95,13 +87,19 @@ public class JREUtils { for(File f : locateLibs(new File(Tools.DIR_HOME_JRE + "/" + Tools.DIRNAME_HOME_JRE))) { dlopen(f.getAbsolutePath()); } - dlopen(nativeLibDir + "/libopenal.so"); - + dlopen(sNativeLibDir + "/libopenal.so"); } public static Map readJREReleaseProperties() throws IOException { + return readJREReleaseProperties(Tools.DIR_HOME_JRE); + } + + public static Map readJREReleaseProperties(String name) throws IOException { Map jreReleaseMap = new ArrayMap<>(); - BufferedReader jreReleaseReader = new BufferedReader(new FileReader(Tools.DIR_HOME_JRE + "/release")); + if (!name.contains("/")) { + name = Tools.MULTIRT_HOME + "/" + name; + } + BufferedReader jreReleaseReader = new BufferedReader(new FileReader(name + "/release")); String currLine; while ((currLine = jreReleaseReader.readLine()) != null) { if (!currLine.isEmpty() || currLine.contains("=")) { @@ -112,11 +110,11 @@ public class JREUtils { jreReleaseReader.close(); return jreReleaseMap; } - public static String jvmLibraryPath; - public static void redirectAndPrintJRELog(final LoggableActivity act) { + + public static void redirectAndPrintJRELog() { Log.v("jrelog","Log starts here"); - JREUtils.logToActivity(act); - Thread t = new Thread(new Runnable(){ + JREUtils.logToLogger(Logger.getInstance()); + new Thread(new Runnable(){ int failTime = 0; ProcessBuilder logcatPb; @Override @@ -131,21 +129,11 @@ public class JREUtils { Log.i("jrelog-logcat","Starting logcat"); java.lang.Process p = logcatPb.start(); - // idk which better, both have a bug that printf(\n) in a single line - /* - BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream())); - String line; - while ((line = reader.readLine()) != null) { - act.appendlnToLog(line); - } - reader.close(); - */ - byte[] buf = new byte[1024]; int len; while ((len = p.getInputStream().read(buf)) != -1) { String currStr = new String(buf, 0, len); - act.appendToLog(currStr); + Logger.getInstance().appendToLog(currStr); } if (p.waitFor() != 0) { @@ -155,17 +143,16 @@ public class JREUtils { if (failTime <= 10) { run(); } else { - act.appendlnToLog("ERROR: Unable to get more log."); + Logger.getInstance().appendToLog("ERROR: Unable to get more log."); } return; } } catch (Throwable e) { Log.e("jrelog-logcat", "Exception on logging thread", e); - act.appendlnToLog("Exception on logging thread:\n" + Log.getStackTraceString(e)); + Logger.getInstance().appendToLog("Exception on logging thread:\n" + Log.getStackTraceString(e)); } } - }); - t.start(); + }).start(); Log.i("jrelog-logcat","Logcat thread started"); } @@ -175,7 +162,7 @@ public class JREUtils { JRE_ARCHITECTURE = "i386/i486/i586"; } - nativeLibDir = ctx.getApplicationInfo().nativeLibraryDir; + sNativeLibDir = ctx.getApplicationInfo().nativeLibraryDir; for (String arch : JRE_ARCHITECTURE.split("/")) { File f = new File(Tools.DIR_HOME_JRE, "lib/" + arch); @@ -194,28 +181,38 @@ public class JREUtils { "/system/" + libName + ":" + "/vendor/" + libName + ":" + "/vendor/" + libName + "/hw:" + - nativeLibDir + sNativeLibDir ); LD_LIBRARY_PATH = ldLibraryPath.toString(); } - public static void setJavaEnvironment(LoggableActivity ctx) throws Throwable { + public static void setJavaEnvironment(Activity activity) throws Throwable { Map envMap = new ArrayMap<>(); + envMap.put("POJAV_NATIVEDIR", activity.getApplicationInfo().nativeLibraryDir); envMap.put("JAVA_HOME", Tools.DIR_HOME_JRE); envMap.put("HOME", Tools.DIR_GAME_NEW); - envMap.put("TMPDIR", ctx.getCacheDir().getAbsolutePath()); + envMap.put("TMPDIR", activity.getCacheDir().getAbsolutePath()); envMap.put("LIBGL_MIPMAP", "3"); - + + // On certain GLES drivers, overloading default functions shader hack fails, so disable it + envMap.put("LIBGL_NOINTOVLHACK", "1"); + // Fix white color on banner and sheep, since GL4ES 1.1.5 envMap.put("LIBGL_NORMALIZE", "1"); + + // The OPEN GL version is changed according + envMap.put("LIBGL_ES", (String) ExtraCore.getValue(ExtraConstants.OPEN_GL_VERSION)); - envMap.put("MESA_GLSL_CACHE_DIR", ctx.getCacheDir().getAbsolutePath()); - envMap.put("MESA_GL_VERSION_OVERRIDE", "4.6"); - envMap.put("MESA_GLSL_VERSION_OVERRIDE", "460"); + envMap.put("MESA_GLSL_CACHE_DIR", activity.getCacheDir().getAbsolutePath()); + if (LOCAL_RENDERER != null) { + envMap.put("MESA_GL_VERSION_OVERRIDE", LOCAL_RENDERER.equals("opengles3_virgl")?"4.3":"4.6"); + envMap.put("MESA_GLSL_VERSION_OVERRIDE", LOCAL_RENDERER.equals("opengles3_virgl")?"430":"460"); + } envMap.put("force_glsl_extensions_warn", "true"); envMap.put("allow_higher_compat_version", "true"); envMap.put("allow_glsl_extension_directive_midshader", "true"); envMap.put("MESA_LOADER_DRIVER_OVERRIDE", "zink"); + envMap.put("VTEST_SOCKET_NAME", activity.getCacheDir().getAbsolutePath() + "/.virgl_test"); envMap.put("LD_LIBRARY_PATH", LD_LIBRARY_PATH); envMap.put("PATH", Tools.DIR_HOME_JRE + "/bin:" + Os.getenv("PATH")); @@ -256,8 +253,12 @@ public class JREUtils { } } for (Map.Entry env : envMap.entrySet()) { - ctx.appendlnToLog("Added custom env: " + env.getKey() + "=" + env.getValue()); - Os.setenv(env.getKey(), env.getValue(), true); + Logger.getInstance().appendToLog("Added custom env: " + env.getKey() + "=" + env.getValue()); + try { + Os.setenv(env.getKey(), env.getValue(), true); + }catch (NullPointerException exception){ + Log.e("JREUtils", exception.toString()); + } } File serverFile = new File(Tools.DIR_HOME_JRE + "/" + Tools.DIRNAME_HOME_JRE + "/server/libjvm.so"); @@ -269,8 +270,8 @@ public class JREUtils { // return ldLibraryPath; } - public static int launchJavaVM(final LoggableActivity ctx,final List JVMArgs) throws Throwable { - JREUtils.relocateLibPath(ctx); + public static int launchJavaVM(final Activity activity,final List JVMArgs) throws Throwable { + JREUtils.relocateLibPath(activity); // For debugging only! /* StringBuilder sbJavaArgs = new StringBuilder(); @@ -280,10 +281,10 @@ public class JREUtils { ctx.appendlnToLog("Executing JVM: \"" + sbJavaArgs.toString() + "\""); */ - setJavaEnvironment(ctx); + setJavaEnvironment(activity); final String graphicsLib = loadGraphicsLibrary(); - List userArgs = getJavaArgs(ctx); + List userArgs = getJavaArgs(activity); //Remove arguments that can interfere with the good working of the launcher purgeArg(userArgs,"-Xms"); @@ -298,19 +299,20 @@ public class JREUtils { if(LOCAL_RENDERER != null) userArgs.add("-Dorg.lwjgl.opengl.libname=" + graphicsLib); userArgs.addAll(JVMArgs); - ctx.runOnUiThread(() -> Toast.makeText(ctx, ctx.getString(R.string.autoram_info_msg,LauncherPreferences.PREF_RAM_ALLOCATION), Toast.LENGTH_SHORT).show()); + activity.runOnUiThread(() -> Toast.makeText(activity, activity.getString(R.string.autoram_info_msg,LauncherPreferences.PREF_RAM_ALLOCATION), Toast.LENGTH_SHORT).show()); System.out.println(JVMArgs); initJavaRuntime(); - setupExitTrap(ctx.getApplication()); + setupExitTrap(activity.getApplication()); chdir(Tools.DIR_GAME_NEW); + userArgs.add(0,"java"); //argv[0] is the program name according to C standard. final int exitCode = VMLauncher.launchJVM(userArgs.toArray(new String[0])); - ctx.appendlnToLog("Java Exit code: " + exitCode); + Logger.getInstance().appendToLog("Java Exit code: " + exitCode); if (exitCode != 0) { - ctx.runOnUiThread(() -> { - AlertDialog.Builder dialog = new AlertDialog.Builder(ctx); - dialog.setMessage(ctx.getString(R.string.mcn_exit_title, exitCode)); + activity.runOnUiThread(() -> { + AlertDialog.Builder dialog = new AlertDialog.Builder(activity); + dialog.setMessage(activity.getString(R.string.mcn_exit_title, exitCode)); dialog.setPositiveButton(android.R.string.ok, (p1, p2) -> BaseMainActivity.fullyExit()); dialog.show(); @@ -327,7 +329,10 @@ public class JREUtils { */ public static List getJavaArgs(Context ctx) { List userArguments = parseJavaArguments(LauncherPreferences.PREF_CUSTOM_JAVA_ARGS); - String[] overridableArguments = new String[]{ + String resolvFile; + resolvFile = new File(Tools.DIR_DATA,"resolv.conf").getAbsolutePath(); + + ArrayList overridableArguments = new ArrayList<>(Arrays.asList( "-Djava.home=" + Tools.DIR_HOME_JRE, "-Djava.io.tmpdir=" + ctx.getCacheDir().getAbsolutePath(), "-Duser.home=" + new File(Tools.DIR_GAME_NEW).getParent(), @@ -342,30 +347,36 @@ public class JREUtils { //"-Dorg.lwjgl.util.DebugFunctions=true", //"-Dorg.lwjgl.util.DebugLoader=true", // GLFW Stub width height - "-Dglfwstub.windowWidth=" + CallbackBridge.windowWidth, - "-Dglfwstub.windowHeight=" + CallbackBridge.windowHeight, + "-Dglfwstub.windowWidth=" + Tools.getDisplayFriendlyRes(currentDisplayMetrics.widthPixels, LauncherPreferences.PREF_SCALE_FACTOR/100F), + "-Dglfwstub.windowHeight=" + Tools.getDisplayFriendlyRes(currentDisplayMetrics.heightPixels, LauncherPreferences.PREF_SCALE_FACTOR/100F), "-Dglfwstub.initEgl=false", - - "-Dext.net.resolvPath=" +new File(Tools.DIR_DATA,"resolv.conf").getAbsolutePath(), + "-Dext.net.resolvPath=" +resolvFile, + "-Dlog4j2.formatMsgNoLookups=true", //Log4j RCE mitigation "-Dnet.minecraft.clientmodname=" + Tools.APP_NAME, "-Dfml.earlyprogresswindow=false" //Forge 1.14+ workaround - }; - - - for (String userArgument : userArguments) { - for(int i=0; i < overridableArguments.length; ++i){ - String overridableArgument = overridableArguments[i]; - //Only java properties are considered overridable for now - if(userArgument.startsWith("-D") && userArgument.startsWith(overridableArgument.substring(0, overridableArgument.indexOf("=")))){ - overridableArguments[i] = ""; //Remove the argument since it is overridden + )); + if(LauncherPreferences.PREF_ARC_CAPES) { + overridableArguments.add("-javaagent:"+new File(Tools.DIR_DATA,"arc_dns_injector.jar").getAbsolutePath()+"=23.95.137.176"); + } + List additionalArguments = new ArrayList<>(); + for(String arg : overridableArguments) { + String strippedArg = arg.substring(0,arg.indexOf('=')); + boolean add = true; + for(String uarg : userArguments) { + if(uarg.startsWith(strippedArg)) { + add = false; break; } } + if(add) + additionalArguments.add(arg); + else + Log.i("ArgProcessor","Arg skipped: "+arg); } //Add all the arguments - userArguments.addAll(Arrays.asList(overridableArguments)); + userArguments.addAll(additionalArguments); return userArguments; } @@ -381,20 +392,32 @@ public class JREUtils { ArrayList parsedArguments = new ArrayList<>(0); args = args.trim().replace(" ", ""); //For each prefixes, we separate args. - for(String prefix : new String[]{"-XX:-","-XX:+", "-XX:","-"}){ + for(String prefix : new String[]{"-XX:-","-XX:+", "-XX:","--","-"}){ while (true){ int start = args.indexOf(prefix); if(start == -1) break; //Get the end of the current argument int end = args.indexOf("-", start + prefix.length()); if(end == -1) end = args.length(); + //Extract it String parsedSubString = args.substring(start, end); args = args.replace(parsedSubString, ""); //Check if two args aren't bundled together by mistake - if(parsedSubString.indexOf('=') == parsedSubString.lastIndexOf('=')) + if(parsedSubString.indexOf('=') == parsedSubString.lastIndexOf('=')) { + int arraySize = parsedArguments.size(); + if(arraySize > 0){ + String lastString = parsedArguments.get(arraySize - 1); + // Looking for list elements + if(lastString.charAt(lastString.length() - 1) == ',' || + parsedSubString.contains(",")){ + parsedArguments.set(arraySize - 1, lastString + parsedSubString); + continue; + } + } parsedArguments.add(parsedSubString); + } else Log.w("JAVA ARGS PARSER", "Removed improper arguments: " + parsedSubString); } } @@ -410,11 +433,12 @@ public class JREUtils { if(LOCAL_RENDERER == null) return null; String renderLibrary; switch (LOCAL_RENDERER){ - case "opengles2": renderLibrary = "libgl4es_114.so"; break; - case "opengles2_5": renderLibrary = "libgl4es_115.so"; break; - case "opengles3": renderLibrary = "libgl4es_115.so"; break; + case "opengles2": + case "opengles2_5": + case "opengles3": + renderLibrary = "libgl4es_114.so"; break; + case "opengles3_virgl": case "vulkan_zink": renderLibrary = "libOSMesa_8.so"; break; - case "opengles3_vgpu" : renderLibrary = "libvgpu.so"; break; default: Log.w("RENDER_LIBRARY", "No renderer selected, defaulting to opengles2"); renderLibrary = "libgl4es_114.so"; @@ -425,7 +449,7 @@ public class JREUtils { Log.e("RENDER_LIBRARY","Failed to load renderer " + renderLibrary + ". Falling back to GL4ES 1.1.4"); LOCAL_RENDERER = "opengles2"; renderLibrary = "libgl4es_114.so"; - dlopen(nativeLibDir + "/libgl4es_114.so"); + dlopen(sNativeLibDir + "/libgl4es_114.so"); } return renderLibrary; } @@ -459,7 +483,8 @@ public class JREUtils { } return false; } - private static int getDetectedVersion() { + + public static int getDetectedVersion() { /* * Get all the device configurations and check the EGL_RENDERABLE_TYPE attribute * to determine the highest ES version supported by any config. The @@ -517,14 +542,14 @@ public class JREUtils { } } public static native int chdir(String path); - public static native void logToActivity(final LoggableActivity a); + public static native void logToLogger(final Logger logger); public static native boolean dlopen(String libPath); public static native void setLdLibraryPath(String ldLibraryPath); public static native void setupBridgeWindow(Object surface); + public static native void releaseBridgeWindow(); public static native void setupExitTrap(Context context); // Obtain AWT screen pixels to render on Android SurfaceView public static native int[] renderAWTScreenFrame(/* Object canvas, int width, int height */); - static { System.loadLibrary("pojavexec"); System.loadLibrary("pojavexec_awt"); diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/MCOptionUtils.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/MCOptionUtils.java index 1e302c923..51114b68d 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/MCOptionUtils.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/MCOptionUtils.java @@ -1,15 +1,40 @@ package net.kdt.pojavlaunch.utils; -import java.io.*; -import java.util.*; -import android.util.*; -import net.kdt.pojavlaunch.*; +import static org.lwjgl.glfw.CallbackBridge.windowHeight; +import static org.lwjgl.glfw.CallbackBridge.windowWidth; -public class MCOptionUtils -{ - private static final HashMap parameterMap = new HashMap<>(); +import android.os.Build; +import android.os.FileObserver; +import android.util.Log; + +import androidx.annotation.Nullable; + +import net.kdt.pojavlaunch.Tools; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; + +public class MCOptionUtils { + private static final HashMap sParameterMap = new HashMap<>(); + private static final ArrayList> sOptionListeners = new ArrayList<>(); + private static FileObserver sFileObserver; + public interface MCOptionListener { + /** Called when an option is changed. Don't know which one though */ + void onOptionChanged(); + } public static void load() { - parameterMap.clear(); + if(sFileObserver == null){ + setupFileObserver(); + } + + sParameterMap.clear(); try { BufferedReader reader = new BufferedReader(new FileReader(Tools.DIR_GAME_NEW + "/options.txt")); @@ -20,7 +45,7 @@ public class MCOptionUtils Log.w(Tools.APP_NAME, "No colon on line \""+line+"\", skipping"); continue; } - parameterMap.put(line.substring(0,firstColonIndex), line.substring(firstColonIndex+1)); + sParameterMap.put(line.substring(0,firstColonIndex), line.substring(firstColonIndex+1)); } reader.close(); } catch (IOException e) { @@ -29,19 +54,38 @@ public class MCOptionUtils } public static void set(String key, String value) { - parameterMap.put(key,value); + sParameterMap.put(key,value); + } + + /** Set an array of String, instead of a simple value. Not supported on all options */ + public static void set(String key, List values){ + sParameterMap.put(key, values.toString()); } public static String get(String key){ - return parameterMap.get(key); + return sParameterMap.get(key); + } + + /** @return A list of values from an array stored as a string */ + public static List getAsList(String key){ + String value = get(key); + + // Fallback if the value doesn't exist + if (value == null) return new ArrayList<>(); + + // Remove the edges + value = value.replace("[", "").replace("]", ""); + if (value.isEmpty()) return new ArrayList<>(); + + return Arrays.asList(value.split(",")); } public static void save() { StringBuilder result = new StringBuilder(); - for(String key : parameterMap.keySet()) + for(String key : sParameterMap.keySet()) result.append(key) .append(':') - .append(parameterMap.get(key)) + .append(sParameterMap.get(key)) .append('\n'); try { @@ -50,4 +94,70 @@ public class MCOptionUtils Log.w(Tools.APP_NAME, "Could not save options.txt", e); } } + + /** @return The stored Minecraft GUI scale, also auto-computed if on auto-mode or improper setting */ + public static int getMcScale() { + MCOptionUtils.load(); + String str = MCOptionUtils.get("guiScale"); + int guiScale = (str == null ? 0 :Integer.parseInt(str)); + + int scale = Math.max(Math.min(windowWidth / 320, windowHeight / 240), 1); + if(scale < guiScale || guiScale == 0){ + guiScale = scale; + } + + return guiScale; + } + + /** Add a file observer to reload options on file change + * Listeners get notified of the change */ + private static void setupFileObserver(){ + if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q){ + sFileObserver = new FileObserver(new File(Tools.DIR_GAME_NEW + "/options.txt"), FileObserver.MODIFY) { + @Override + public void onEvent(int i, @Nullable String s) { + MCOptionUtils.load(); + notifyListeners(); + } + }; + }else{ + sFileObserver = new FileObserver(Tools.DIR_GAME_NEW + "/options.txt", FileObserver.MODIFY) { + @Override + public void onEvent(int i, @Nullable String s) { + MCOptionUtils.load(); + notifyListeners(); + } + }; + } + + sFileObserver.startWatching(); + } + + /** Notify the option listeners */ + public static void notifyListeners(){ + for(WeakReference weakReference : sOptionListeners){ + MCOptionListener optionListener = weakReference.get(); + if(optionListener == null) continue; + + optionListener.onOptionChanged(); + } + } + + /** Add an option listener, notice how we don't have a reference to it */ + public static void addMCOptionListener(MCOptionListener listener){ + sOptionListeners.add(new WeakReference<>(listener)); + } + + /** Remove a listener from existence, or at least, its reference here */ + public static void removeMCOptionListener(MCOptionListener listener){ + for(WeakReference weakReference : sOptionListeners){ + MCOptionListener optionListener = weakReference.get(); + if(optionListener == null) continue; + if(optionListener == listener){ + sOptionListeners.remove(weakReference); + return; + } + } + } + } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/MathUtils.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/MathUtils.java index c508b698c..e292f382c 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/MathUtils.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/MathUtils.java @@ -7,4 +7,11 @@ public class MathUtils { return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; } + /** Returns the distance between two points. */ + public static float dist(float x1, float y1, float x2, float y2) { + final float x = (x2 - x1); + final float y = (y2 - y1); + return (float) Math.hypot(x, y); + } + } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/OldVersionsUtils.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/OldVersionsUtils.java new file mode 100644 index 000000000..eb46e6beb --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/OldVersionsUtils.java @@ -0,0 +1,36 @@ +package net.kdt.pojavlaunch.utils; + +import android.util.Log; + +import net.kdt.pojavlaunch.JMinecraftVersionList; +import net.kdt.pojavlaunch.extra.ExtraConstants; +import net.kdt.pojavlaunch.extra.ExtraCore; +import net.kdt.pojavlaunch.value.launcherprofiles.MinecraftProfile; + +import java.sql.Date; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Locale; + +/** Class here to help with various stuff to help run lower versions smoothly */ +public class OldVersionsUtils { + /** Lower minecraft versions fare better with opengl 1 + * @param version The version about to be launched + */ + public static void selectOpenGlVersion(JMinecraftVersionList.Version version){ + // 1309989600 is 2011-07-07 2011-07-07T22:00:00+00:00 + String creationDate = version.time; + if(creationDate == null || creationDate.isEmpty()){ + ExtraCore.setValue(ExtraConstants.OPEN_GL_VERSION, "2"); + return; + } + + try { + String openGlVersion = new SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH).parse(creationDate.substring(0, creationDate.indexOf("T"))).before(new Date(2011-1900, 6, 7)) ? "1" : "2"; + ExtraCore.setValue(ExtraConstants.OPEN_GL_VERSION, openGlVersion); + }catch (ParseException exception){ + Log.e("OPENGL SELECTION", exception.toString()); + ExtraCore.setValue(ExtraConstants.OPEN_GL_VERSION, "2"); + } + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/V117CompatUtil.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/V117CompatUtil.java index 74bd60c49..fe8ae08a3 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/V117CompatUtil.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/V117CompatUtil.java @@ -1,17 +1,19 @@ package net.kdt.pojavlaunch.utils; import android.app.Activity; -import android.content.Context; import android.content.res.AssetManager; import android.util.Log; import androidx.appcompat.app.AlertDialog; +import net.kdt.pojavlaunch.BaseLauncherActivity; import net.kdt.pojavlaunch.R; import net.kdt.pojavlaunch.Tools; import net.kdt.pojavlaunch.prefs.LauncherPreferences; import net.kdt.pojavlaunch.tasks.MinecraftDownloaderTask; import net.kdt.pojavlaunch.value.PerVersionConfig; +import net.kdt.pojavlaunch.value.launcherprofiles.LauncherProfiles; +import net.kdt.pojavlaunch.value.launcherprofiles.MinecraftProfile; import org.apache.commons.io.IOUtils; @@ -22,7 +24,6 @@ import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; public class V117CompatUtil { @@ -92,48 +93,57 @@ public class V117CompatUtil { Log.i("V117CompatDebug",rawList); return new ArrayList<>(Arrays.asList(rawList.split(","))); } + private static String regenPackList(List packs) { if(packs.size()==0) return "[]"; - String ret = "["+packs.get(0); + StringBuilder ret = new StringBuilder("[" + packs.get(0)); for(int i = 1; i < packs.size(); i++) { - ret += ","+packs.get(i); + ret.append(",").append(packs.get(i)); } - ret += "]"; - return ret; + ret.append("]"); + return ret.toString(); } - public static void runCheck(String version, Activity ctx) throws Exception{ - PerVersionConfig.VersionConfig cfg = PerVersionConfig.configMap.get(version); + public static void runCheck(String version, Activity activity) throws Exception{ + + MCOptionUtils.load(); List packList =getTexturePackList(MCOptionUtils.get("resourcePacks")); - String renderer = cfg != null && cfg.renderer != null?cfg.renderer:LauncherPreferences.PREF_RENDERER; + String renderer; + String gamePath; + LauncherProfiles.update(); + String selectedProfile = LauncherPreferences.DEFAULT_PREF.getString(LauncherPreferences.PREF_KEY_CURRENT_PROFILE,""); + MinecraftProfile prof = LauncherProfiles.mainProfileJson.profiles.get(selectedProfile); + if(prof == null) throw new MinecraftDownloaderTask.SilentException(); + renderer = prof.pojavRendererName != null ? prof.pojavRendererName : LauncherPreferences.PREF_RENDERER; + gamePath = prof.gameDir != null && prof.gameDir.startsWith(Tools.LAUNCHERPROFILES_RTPREFIX) ? prof.gameDir.replace(Tools.LAUNCHERPROFILES_RTPREFIX,Tools.DIR_GAME_HOME + "/") : Tools.DIR_GAME_NEW; - if(renderer.equals("vulkan_zink")) return; //don't install for zink users; - if(packList.contains("\"assets-v0.zip\"") && renderer.equals("opengles3")) return; + //String + + if(renderer.equals("vulkan_zink") || renderer.equals("opengles3_virgl")) return; //don't install for zink/virgl users; + if(packList.contains("\"assets-v0.zip\"")) return; + if(JREUtils.getDetectedVersion() >= 3) return; // GL4ES_extra supports 1.17+ Object lock = new Object(); AtomicInteger proceed = new AtomicInteger(0); - ctx.runOnUiThread(() -> { - AlertDialog.Builder bldr = new AlertDialog.Builder(ctx); - bldr.setTitle(R.string.global_warinng); - bldr.setMessage(R.string.compat_117_message); - bldr.setPositiveButton(android.R.string.ok, (dialog, which) -> { + activity.runOnUiThread(() -> { + AlertDialog.Builder builder = new AlertDialog.Builder(activity); + builder.setTitle(R.string.global_warinng); + builder.setMessage(R.string.compat_117_message); + builder.setPositiveButton(android.R.string.ok, (dialog, which) -> { proceed.set(1); synchronized (lock) { lock.notifyAll(); } - dialog.dismiss(); }); - bldr.setNegativeButton(android.R.string.cancel, (dialog, which) -> { + builder.setNegativeButton(android.R.string.cancel, (dialog, which) -> { synchronized (lock) { lock.notifyAll(); } - dialog.dismiss(); }); - bldr.setNeutralButton(R.string.compat_11x_playanyway, (dialog, which) -> { + builder.setNeutralButton(R.string.compat_11x_playanyway, (dialog, which) -> { proceed.set(2); synchronized (lock) { lock.notifyAll(); } - dialog.dismiss(); }); - bldr.setCancelable(false); - bldr.show(); + builder.setCancelable(false); + builder.show(); }); synchronized (lock) { @@ -141,23 +151,20 @@ public class V117CompatUtil { } switch(proceed.get()) { case 1: - if (cfg == null) { - cfg = new PerVersionConfig.VersionConfig(); - PerVersionConfig.configMap.put(version, cfg); - } - cfg.renderer = "opengles3"; - String path = Tools.DIR_GAME_NEW; - if(cfg.gamePath != null && !cfg.gamePath.isEmpty()) path = cfg.gamePath; - copyResourcePack(path,ctx.getAssets()); + MinecraftProfile minecraftProfile = LauncherProfiles.mainProfileJson.profiles.get(selectedProfile); + if(minecraftProfile == null) throw new MinecraftDownloaderTask.SilentException(); + minecraftProfile.pojavRendererName = "opengles2"; + LauncherProfiles.update(); + copyResourcePack(gamePath,activity.getAssets()); if(!packList.contains("\"assets-v0.zip\"")) packList.add(0,"\"assets-v0.zip\""); MCOptionUtils.set("resourcePacks",regenPackList(packList)); MCOptionUtils.save(); - PerVersionConfig.update(); break; case 0: throw new MinecraftDownloaderTask.SilentException(); } } + public static void copyResourcePack(String gameDir, AssetManager am) throws IOException { File resourcepacksDir = new File(gameDir,"resourcepacks"); if(!resourcepacksDir.exists()) resourcepacksDir.mkdirs(); diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/value/DependentLibrary.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/value/DependentLibrary.java index 720f65b9c..5df6b410e 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/value/DependentLibrary.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/value/DependentLibrary.java @@ -1,18 +1,21 @@ package net.kdt.pojavlaunch.value; +import androidx.annotation.Keep; import net.kdt.pojavlaunch.JMinecraftVersionList.Arguments.ArgValue.ArgRules; +@Keep public class DependentLibrary { public ArgRules[] rules; public String name; public LibraryDownloads downloads; public String url; - - public static class LibraryDownloads { - public MinecraftLibraryArtifact artifact; - public LibraryDownloads(MinecraftLibraryArtifact artifact) { - this.artifact = artifact; - } - } + + @Keep + public static class LibraryDownloads { + public MinecraftLibraryArtifact artifact; + public LibraryDownloads(MinecraftLibraryArtifact artifact) { + this.artifact = artifact; + } + } } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/value/MinecraftAccount.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/value/MinecraftAccount.java index 14bbac780..0ff48ea3e 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/value/MinecraftAccount.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/value/MinecraftAccount.java @@ -1,4 +1,7 @@ package net.kdt.pojavlaunch.value; + + +import android.graphics.BitmapFactory; import android.util.Log; import net.kdt.pojavlaunch.*; @@ -49,8 +52,9 @@ public class MinecraftAccount public static MinecraftAccount parse(String content) throws JsonSyntaxException { return Tools.GLOBAL_GSON.fromJson(content, MinecraftAccount.class); } - + public static MinecraftAccount load(String name) throws JsonSyntaxException { + if(!accountExists(name)) return new MinecraftAccount(); try { MinecraftAccount acc = parse(Tools.read(Tools.DIR_ACCOUNT_NEW + "/" + name + ".json")); if (acc.accessToken == null) { @@ -80,7 +84,19 @@ public class MinecraftAccount return null; } } - + + public Bitmap getSkinFace(){ + if(skinFaceBase64 == null){ + return Bitmap.createBitmap(1,1, Bitmap.Config.ARGB_8888); + } + byte[] faceIconBytes = Base64.decode(skinFaceBase64, Base64.DEFAULT); + return BitmapFactory.decodeByteArray(faceIconBytes, 0, faceIconBytes.length); + } + + private static boolean accountExists(String username){ + return new File(Tools.DIR_ACCOUNT_NEW + "/" + username + ".json").exists(); + } + public static void clearTempAccount() { File tempAccFile = new File(Tools.DIR_DATA, "cache/tempacc.json"); tempAccFile.delete(); diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/value/PerVersionConfig.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/value/PerVersionConfig.java index 25b1a55e0..d657de552 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/value/PerVersionConfig.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/value/PerVersionConfig.java @@ -1,5 +1,6 @@ package net.kdt.pojavlaunch.value; +import com.google.gson.JsonSyntaxException; import com.google.gson.reflect.TypeToken; import net.kdt.pojavlaunch.Tools; @@ -16,7 +17,13 @@ public class PerVersionConfig { if(configMap == null) { pvcFile = new File(Tools.DIR_GAME_HOME,"per-version-config.json"); if(pvcFile.exists()) { - configMap = Tools.GLOBAL_GSON.fromJson(Tools.read(pvcFile.getAbsolutePath()), new TypeToken>() {}.getType()); + try { + configMap = Tools.GLOBAL_GSON.fromJson(Tools.read(pvcFile.getAbsolutePath()), new TypeToken>() { + }.getType()); + }catch(JsonSyntaxException ex) { + ex.printStackTrace(); + configMap = new HashMap<>(); + } }else{ configMap = new HashMap<>(); } @@ -24,6 +31,12 @@ public class PerVersionConfig { Tools.write(pvcFile.getAbsolutePath(),Tools.GLOBAL_GSON.toJson(configMap)); } } + public static boolean erase() { + return new File(Tools.DIR_GAME_HOME,"per-version-config.json").delete(); + } + public static boolean exists() { + return new File(Tools.DIR_GAME_HOME,"per-version-config.json").exists(); + } public static class VersionConfig { public String jvmArgs; public String gamePath; diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/value/launcherprofiles/MinecraftProfile.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/value/launcherprofiles/MinecraftProfile.java index d2fe3cf4c..6736cc89b 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/value/launcherprofiles/MinecraftProfile.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/value/launcherprofiles/MinecraftProfile.java @@ -13,5 +13,6 @@ public class MinecraftProfile public String javaArgs; public String logConfig; public boolean logConfigIsXML; + public String pojavRendererName; public MinecraftResolution[] resolution; } diff --git a/app_pojavlauncher/src/main/java/org/lwjgl/glfw/CallbackBridge.java b/app_pojavlauncher/src/main/java/org/lwjgl/glfw/CallbackBridge.java index 7582e7468..c9f5a928b 100644 --- a/app_pojavlauncher/src/main/java/org/lwjgl/glfw/CallbackBridge.java +++ b/app_pojavlauncher/src/main/java/org/lwjgl/glfw/CallbackBridge.java @@ -1,9 +1,8 @@ package org.lwjgl.glfw; -import java.io.*; -import java.nio.ByteBuffer; -import java.nio.FloatBuffer; -import java.util.*; -import android.widget.*; + +import android.os.Handler; +import android.os.Looper; + import net.kdt.pojavlaunch.*; import android.content.*; @@ -15,42 +14,35 @@ public class CallbackBridge { public static volatile int windowWidth, windowHeight; public static volatile int physicalWidth, physicalHeight; - public static int mouseX, mouseY; + public static float mouseX, mouseY; public static StringBuilder DEBUG_STRING = new StringBuilder(); - - // volatile private static boolean isGrabbing = false; - public static class PusherRunnable implements Runnable { - int button; int x; int y; - public PusherRunnable(int button, int x, int y) { - this.button = button; - this.x = x; - this.y = y; - } - @Override - public void run() { - putMouseEventWithCoords(button, true, x, y); - try { Thread.sleep(40); } catch (InterruptedException e) {} - putMouseEventWithCoords(button, false, x, y); - } - } - public static void putMouseEventWithCoords(int button, int x, int y /* , int dz, long nanos */) { - new Thread(new PusherRunnable(button,x,y)).run(); + private static boolean threadAttached; + public volatile static boolean holdingAlt, holdingCapslock, holdingCtrl, + holdingNumlock, holdingShift; + + + public static void putMouseEventWithCoords(int button, float x, float y) { + putMouseEventWithCoords(button, true, x, y); + Handler handler = new Handler(Looper.getMainLooper()); + handler.postDelayed(() -> putMouseEventWithCoords(button, false, x, y), 22); + } - public static void putMouseEventWithCoords(int button, boolean isDown, int x, int y /* , int dz, long nanos */) { + public static void putMouseEventWithCoords(int button, boolean isDown, float x, float y /* , int dz, long nanos */) { sendCursorPos(x, y); sendMouseKeycode(button, CallbackBridge.getCurrentMods(), isDown); } - private static boolean threadAttached; + public static void sendCursorPos(float x, float y) { if (!threadAttached) { + nativeSetUseInputStackQueue(BaseMainActivity.isInputStackCall); threadAttached = CallbackBridge.nativeAttachThreadToOther(true, BaseMainActivity.isInputStackCall); } - + DEBUG_STRING.append("CursorPos=").append(x).append(", ").append(y).append("\n"); - mouseX = (int) x; - mouseY = (int) y; + mouseX = x; + mouseY = y; nativeSendCursorPos(mouseX, mouseY); } @@ -84,6 +76,27 @@ public class CallbackBridge { nativeSendChar(keychar); } + public static void sendKeyPress(int keyCode, int modifiers, boolean status) { + sendKeyPress(keyCode, 0, modifiers, status); + } + + public static void sendKeyPress(int keyCode, int scancode, int modifiers, boolean status) { + sendKeyPress(keyCode, '\u0000', scancode, modifiers, status); + } + + public static void sendKeyPress(int keyCode, char keyChar, int scancode, int modifiers, boolean status) { + CallbackBridge.sendKeycode(keyCode, keyChar, scancode, modifiers, status); + } + + public static void sendKeyPress(int keyCode) { + sendKeyPress(keyCode, CallbackBridge.getCurrentMods(), true); + sendKeyPress(keyCode, CallbackBridge.getCurrentMods(), false); + } + + public static void sendMouseButton(int button, boolean status) { + CallbackBridge.sendMouseKeycode(button, CallbackBridge.getCurrentMods(), status); + } + public static void sendMouseKeycode(int button, int modifiers, boolean isDown) { DEBUG_STRING.append("MouseKey=").append(button).append(", down=").append(isDown).append("\n"); // if (isGrabbing()) DEBUG_STRING.append("MouseGrabStrace: " + android.util.Log.getStackTraceString(new Throwable()) + "\n"); @@ -126,13 +139,6 @@ public class CallbackBridge { default: return null; } } - public static void receiveCallback(int type, String data) { - switch (type) { - case ANDROID_TYPE_GRAB_STATE: - // isGrabbing = Boolean.parseBoolean(data); - break; - } - } /* private static String currData; public static void sendData(int type, Object... dataArr) { @@ -152,48 +158,48 @@ public class CallbackBridge { private static native void nativeSendData(boolean isAndroid, int type, String data); */ - public volatile static boolean holdingAlt, holdingCapslock, holdingCtrl, - holdingNumlock, holdingShift; + public static int getCurrentMods() { int currMods = 0; if (holdingAlt) { - currMods |= LWJGLGLFWKeycode.GLFW_MOD_ALT; + currMods |= LwjglGlfwKeycode.GLFW_MOD_ALT; } if (holdingCapslock) { - currMods |= LWJGLGLFWKeycode.GLFW_MOD_CAPS_LOCK; + currMods |= LwjglGlfwKeycode.GLFW_MOD_CAPS_LOCK; } if (holdingCtrl) { - currMods |= LWJGLGLFWKeycode.GLFW_MOD_CONTROL; + currMods |= LwjglGlfwKeycode.GLFW_MOD_CONTROL; } if (holdingNumlock) { - currMods |= LWJGLGLFWKeycode.GLFW_MOD_NUM_LOCK; + currMods |= LwjglGlfwKeycode.GLFW_MOD_NUM_LOCK; } if (holdingShift) { - currMods |= LWJGLGLFWKeycode.GLFW_MOD_SHIFT; + currMods |= LwjglGlfwKeycode.GLFW_MOD_SHIFT; } return currMods; } public static void setModifiers(int keyCode, boolean isDown){ switch (keyCode){ - case LWJGLGLFWKeycode.GLFW_KEY_LEFT_SHIFT: + case LwjglGlfwKeycode.GLFW_KEY_LEFT_SHIFT: CallbackBridge.holdingShift = isDown; return; - case LWJGLGLFWKeycode.GLFW_KEY_LEFT_CONTROL: + case LwjglGlfwKeycode.GLFW_KEY_LEFT_CONTROL: CallbackBridge.holdingCtrl = isDown; return; - case LWJGLGLFWKeycode.GLFW_KEY_LEFT_ALT: + case LwjglGlfwKeycode.GLFW_KEY_LEFT_ALT: CallbackBridge.holdingAlt = isDown; return; - case LWJGLGLFWKeycode.GLFW_KEY_CAPS_LOCK: + case LwjglGlfwKeycode.GLFW_KEY_CAPS_LOCK: CallbackBridge.holdingCapslock = isDown; return; - case LWJGLGLFWKeycode.GLFW_KEY_NUM_LOCK: + case LwjglGlfwKeycode.GLFW_KEY_NUM_LOCK: CallbackBridge.holdingNumlock = isDown; return; } } + public static native void nativeSetUseInputStackQueue(boolean useInputStackQueue); public static native boolean nativeAttachThreadToOther(boolean isAndroid, boolean isUsePushPoll); private static native boolean nativeSendChar(char codepoint); @@ -201,11 +207,12 @@ public class CallbackBridge { private static native boolean nativeSendCharMods(char codepoint, int mods); private static native void nativeSendKey(int key, int scancode, int action, int mods); // private static native void nativeSendCursorEnter(int entered); - private static native void nativeSendCursorPos(int x, int y); + private static native void nativeSendCursorPos(float x, float y); private static native void nativeSendMouseButton(int button, int action, int mods); private static native void nativeSendScroll(double xoffset, double yoffset); private static native void nativeSendScreenSize(int width, int height); - + public static native void nativeSetWindowAttrib(int attrib, int value); + public static native boolean nativeIsGrabbing(); static { System.loadLibrary("pojavexec"); diff --git a/app_pojavlauncher/src/main/jni/egl_bridge.c b/app_pojavlauncher/src/main/jni/egl_bridge.c index 0aeca6752..687cf8550 100644 --- a/app_pojavlauncher/src/main/jni/egl_bridge.c +++ b/app_pojavlauncher/src/main/jni/egl_bridge.c @@ -579,15 +579,15 @@ typedef struct osmesa_context }; // endregion OSMESA internals struct PotatoBridge { - /*ANativeWindow */ void* androidWindow; - - /* EGLContext */ void* eglContextOld; - /* EGLContext */ void* eglContext; - /* EGLDisplay */ void* eglDisplay; - /* EGLSurface */ void* eglSurface; + /*ANativeWindow */ void* androidWindow; + + /* EGLContext */ void* eglContextOld; + /* EGLContext */ void* eglContext; + /* EGLDisplay */ void* eglDisplay; + /* EGLSurface */ void* eglSurface; /* - void* eglSurfaceRead; - void* eglSurfaceDraw; + void* eglSurfaceRead; + void* eglSurfaceDraw; */ }; EGLConfig config; @@ -604,6 +604,7 @@ GLubyte* (*glGetString_p) (GLenum name); void (*glFinish_p) (void); void (*glClearColor_p) (GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); void (*glClear_p) (GLbitfield mask); +void (*glReadPixels_p) (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void * data); /*EGL functions */ EGLBoolean (*eglMakeCurrent_p) (EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx); @@ -617,25 +618,32 @@ EGLBoolean (*eglInitialize_p) (EGLDisplay dpy, EGLint *major, EGLint *minor); EGLBoolean (*eglChooseConfig_p) (EGLDisplay dpy, const EGLint *attrib_list, EGLConfig *configs, EGLint config_size, EGLint *num_config); EGLBoolean (*eglGetConfigAttrib_p) (EGLDisplay dpy, EGLConfig config, EGLint attribute, EGLint *value); EGLBoolean (*eglBindAPI_p) (EGLenum api); +EGLSurface (*eglCreatePbufferSurface_p) (EGLDisplay dpy, EGLConfig config, const EGLint *attrib_list); EGLSurface (*eglCreateWindowSurface_p) (EGLDisplay dpy, EGLConfig config, NativeWindowType window, const EGLint *attrib_list); EGLBoolean (*eglSwapBuffers_p) (EGLDisplay dpy, EGLSurface draw); EGLint (*eglGetError_p) (void); EGLContext (*eglCreateContext_p) (EGLDisplay dpy, EGLConfig config, EGLContext share_list, const EGLint *attrib_list); EGLBoolean (*eglSwapInterval_p) (EGLDisplay dpy, EGLint interval); EGLSurface (*eglGetCurrentSurface_p) (EGLint readdraw); +int (*vtest_main_p) (int argc, char** argv); +void (*vtest_swap_buffers_p) (void); + #define RENDERER_GL4ES 1 #define RENDERER_VK_ZINK 2 +#define RENDERER_VIRGL 3 int config_renderer; +void* gbuffer; + +void* egl_make_current(void* window); void pojav_openGLOnLoad() { } void pojav_openGLOnUnload() { } -void* gbuffer; -void terminateEgl() { +void pojavTerminate() { printf("EGLBridge: Terminating\n"); switch (config_renderer) { @@ -651,6 +659,7 @@ void terminateEgl() { potatoBridge.eglSurface = EGL_NO_SURFACE; } break; + //case RENDERER_VIRGL: case RENDERER_VK_ZINK: { // Nothing to do here } break; @@ -659,31 +668,78 @@ void terminateEgl() { JNIEXPORT void JNICALL Java_net_kdt_pojavlaunch_utils_JREUtils_setupBridgeWindow(JNIEnv* env, jclass clazz, jobject surface) { potatoBridge.androidWindow = ANativeWindow_fromSurface(env, surface); + char *ptrStr; + asprintf(&ptrStr, "%ld", (long) potatoBridge.androidWindow); + setenv("POJAV_WINDOW_PTR", ptrStr, 1); + free(ptrStr); } -JNIEXPORT jlong JNICALL Java_org_lwjgl_glfw_GLFW_nativeEglGetCurrentContext(JNIEnv* env, jclass clazz) { + +JNIEXPORT void JNICALL +Java_net_kdt_pojavlaunch_utils_JREUtils_releaseBridgeWindow(JNIEnv *env, jclass clazz) { + ANativeWindow_release(potatoBridge.androidWindow); +} + +void* pojavGetCurrentContext() { switch (config_renderer) { case RENDERER_GL4ES: - return (jlong) eglGetCurrentContext_p(); - + return (void *)eglGetCurrentContext_p(); + case RENDERER_VIRGL: case RENDERER_VK_ZINK: - return (jlong) OSMesaGetCurrentContext_p(); + return (void *)OSMesaGetCurrentContext_p(); - default: return (jlong) 0; + default: return NULL; } } + +void dlsym_EGL(void* dl_handle) { + eglBindAPI_p = dlsym(dl_handle,"eglBindAPI"); + eglChooseConfig_p = dlsym(dl_handle, "eglChooseConfig"); + eglCreateContext_p = dlsym(dl_handle, "eglCreateContext"); + eglCreatePbufferSurface_p = dlsym(dl_handle, "eglCreatePbufferSurface"); + eglCreateWindowSurface_p = dlsym(dl_handle, "eglCreateWindowSurface"); + eglDestroyContext_p = dlsym(dl_handle, "eglDestroyContext"); + eglDestroySurface_p = dlsym(dl_handle, "eglDestroySurface"); + eglGetConfigAttrib_p = dlsym(dl_handle, "eglGetConfigAttrib"); + eglGetCurrentContext_p = dlsym(dl_handle, "eglGetCurrentContext"); + eglGetDisplay_p = dlsym(dl_handle, "eglGetDisplay"); + eglGetError_p = dlsym(dl_handle, "eglGetError"); + eglInitialize_p = dlsym(dl_handle, "eglInitialize"); + eglMakeCurrent_p = dlsym(dl_handle, "eglMakeCurrent"); + eglSwapBuffers_p = dlsym(dl_handle, "eglSwapBuffers"); + eglReleaseThread_p = dlsym(dl_handle, "eglReleaseThread"); + eglSwapInterval_p = dlsym(dl_handle, "eglSwapInterval"); + eglTerminate_p = dlsym(dl_handle, "eglTerminate"); + eglGetCurrentSurface_p = dlsym(dl_handle,"eglGetCurrentSurface"); +} + +void dlsym_OSMesa(void* dl_handle) { + OSMesaMakeCurrent_p = dlsym(dl_handle,"OSMesaMakeCurrent"); + OSMesaGetCurrentContext_p = dlsym(dl_handle,"OSMesaGetCurrentContext"); + OSMesaCreateContext_p = dlsym(dl_handle, "OSMesaCreateContext"); + OSMesaDestroyContext_p = dlsym(dl_handle, "OSMesaDestroyContext"); + OSMesaPixelStore_p = dlsym(dl_handle,"OSMesaPixelStore"); + glGetString_p = dlsym(dl_handle,"glGetString"); + glClearColor_p = dlsym(dl_handle, "glClearColor"); + glClear_p = dlsym(dl_handle,"glClear"); + glFinish_p = dlsym(dl_handle,"glFinish"); + glReadPixels_p = dlsym(dl_handle,"glReadPixels"); +} + bool loadSymbols() { - char* fileName; - char* fileNameExt; + char* fileName = calloc(1, 1024); + char* fileNameExt = calloc(1, 1024); switch (config_renderer) { case RENDERER_VK_ZINK: - fileName = "libOSMesa_8.so"; - fileNameExt = "libOSMesa.so.8"; + sprintf(fileName, "%s/libOSMesa_8.so", getenv("POJAV_NATIVEDIR")); + sprintf(fileNameExt, "%s/libOSMesa.so.8", getenv("POJAV_NATIVEDIR")); break; case RENDERER_GL4ES: - fileName = "libEGL.so"; + sprintf(fileName, "libEGL.so"); char* eglLib = getenv("POJAVEXEC_EGL"); - fileNameExt = eglLib == NULL?"":eglLib; + if (eglLib) { + sprintf(fileNameExt, "%s", eglLib); + } break; } void* dl_handle = dlopen(fileNameExt,RTLD_NOW|RTLD_GLOBAL|RTLD_NODELETE); @@ -706,52 +762,70 @@ bool loadSymbols() { } switch(config_renderer) { case RENDERER_VK_ZINK: - OSMesaMakeCurrent_p = dlsym(dl_handle,"OSMesaMakeCurrent"); - OSMesaGetCurrentContext_p = dlsym(dl_handle,"OSMesaGetCurrentContext"); - OSMesaCreateContext_p = dlsym(dl_handle, "OSMesaCreateContext"); - OSMesaDestroyContext_p = dlsym(dl_handle, "OSMesaDestroyContext"); - OSMesaPixelStore_p = dlsym(dl_handle,"OSMesaPixelStore"); - glGetString_p = dlsym(dl_handle,"glGetString"); - glClearColor_p = dlsym(dl_handle, "glClearColor"); - glClear_p = dlsym(dl_handle,"glClear"); - glFinish_p = dlsym(dl_handle,"glFinish"); + dlsym_OSMesa(dl_handle); break; case RENDERER_GL4ES: - eglBindAPI_p = dlsym(dl_handle,"eglBindAPI"); - eglChooseConfig_p = dlsym(dl_handle, "eglChooseConfig"); - eglCreateContext_p = dlsym(dl_handle, "eglCreateContext"); - eglCreateWindowSurface_p = dlsym(dl_handle, "eglCreateWindowSurface"); - eglDestroyContext_p = dlsym(dl_handle, "eglDestroyContext"); - eglDestroySurface_p = dlsym(dl_handle, "eglDestroySurface"); - eglGetConfigAttrib_p = dlsym(dl_handle, "eglGetConfigAttrib"); - eglGetCurrentContext_p = dlsym(dl_handle, "eglGetCurrentContext"); - eglGetDisplay_p = dlsym(dl_handle, "eglGetDisplay"); - eglGetError_p = dlsym(dl_handle, "eglGetError"); - eglInitialize_p = dlsym(dl_handle, "eglInitialize"); - eglMakeCurrent_p = dlsym(dl_handle, "eglMakeCurrent"); - eglSwapBuffers_p = dlsym(dl_handle, "eglSwapBuffers"); - eglReleaseThread_p = dlsym(dl_handle, "eglReleaseThread"); - eglSwapInterval_p = dlsym(dl_handle, "eglSwapInterval"); - eglTerminate_p = dlsym(dl_handle, "eglTerminate"); - eglGetCurrentSurface_p = dlsym(dl_handle,"eglGetCurrentSurface"); + dlsym_EGL(dl_handle); break; } + + free(fileName); + free(fileNameExt); } -JNIEXPORT jboolean JNICALL Java_org_lwjgl_glfw_GLFW_nativeEglInit(JNIEnv* env, jclass clazz) { + +bool loadSymbolsVirGL() { + config_renderer = RENDERER_GL4ES; + loadSymbols(); + config_renderer = RENDERER_VK_ZINK; + loadSymbols(); + config_renderer = RENDERER_VIRGL; + + char* fileName = calloc(1, 1024); + + sprintf(fileName, "%s/libvirgl_test_server.so", getenv("POJAV_NATIVEDIR")); + void *handle = dlopen(fileName, RTLD_LAZY); + printf("VirGL: libvirgl_test_server = %p\n", handle); + if (!handle) { + printf("VirGL: %s\n", dlerror()); + } + vtest_main_p = dlsym(handle, "vtest_main"); + vtest_swap_buffers_p = dlsym(handle, "vtest_swap_buffers"); + + free(fileName); +} + +int pojavInit() { + potatoBridge.androidWindow = (void *)atol(getenv("POJAV_WINDOW_PTR")); ANativeWindow_acquire(potatoBridge.androidWindow); savedWidth = ANativeWindow_getWidth(potatoBridge.androidWindow); savedHeight = ANativeWindow_getHeight(potatoBridge.androidWindow); ANativeWindow_setBuffersGeometry(potatoBridge.androidWindow,savedWidth,savedHeight,AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM); + // NOTE: Override for now. const char *renderer = getenv("POJAV_RENDERER"); - if (strncmp("opengles", renderer, 8) == 0) { + if (strncmp("opengles3_virgl", renderer, 15) == 0) { + config_renderer = RENDERER_VIRGL; + setenv("GALLIUM_DRIVER","virpipe",1); + setenv("OSMESA_NO_FLUSH_FRONTBUFFER","1",false); + if(strcmp(getenv("OSMESA_NO_FLUSH_FRONTBUFFER"),"1") == 0) { + printf("VirGL: OSMesa buffer flush is DISABLED!\n"); + } + loadSymbolsVirGL(); + } else if (strncmp("opengles", renderer, 8) == 0) { config_renderer = RENDERER_GL4ES; loadSymbols(); + } else if (strcmp(renderer, "vulkan_zink") == 0) { + config_renderer = RENDERER_VK_ZINK; + setenv("GALLIUM_DRIVER","zink",1); + loadSymbols(); + } + + if (config_renderer == RENDERER_GL4ES || config_renderer == RENDERER_VIRGL) { if (potatoBridge.eglDisplay == NULL || potatoBridge.eglDisplay == EGL_NO_DISPLAY) { potatoBridge.eglDisplay = eglGetDisplay_p(EGL_DEFAULT_DISPLAY); if (potatoBridge.eglDisplay == EGL_NO_DISPLAY) { printf("EGLBridge: Error eglGetDefaultDisplay() failed: %p\n", eglGetError_p()); - return JNI_FALSE; + return 0; } } @@ -760,7 +834,7 @@ JNIEXPORT jboolean JNICALL Java_org_lwjgl_glfw_GLFW_nativeEglInit(JNIEnv* env, j //(*env)->ThrowNew(env,(*env)->FindClass(env,"java/lang/Exception"),"Trace exception"); if (!eglInitialize_p(potatoBridge.eglDisplay, NULL, NULL)) { printf("EGLBridge: Error eglInitialize() failed: %s\n", eglGetError_p()); - return JNI_FALSE; + return 0; } static const EGLint attribs[] = { @@ -779,7 +853,7 @@ JNIEXPORT jboolean JNICALL Java_org_lwjgl_glfw_GLFW_nativeEglInit(JNIEnv* env, j if (!eglChooseConfig_p(potatoBridge.eglDisplay, attribs, &config, 1, &num_configs)) { printf("EGLBridge: Error couldn't get an EGL visual config: %s\n", eglGetError_p()); - return JNI_FALSE; + return 0; } assert(config); @@ -787,7 +861,7 @@ JNIEXPORT jboolean JNICALL Java_org_lwjgl_glfw_GLFW_nativeEglInit(JNIEnv* env, j if (!eglGetConfigAttrib_p(potatoBridge.eglDisplay, config, EGL_NATIVE_VISUAL_ID, &vid)) { printf("EGLBridge: Error eglGetConfigAttrib() failed: %s\n", eglGetError_p()); - return JNI_FALSE; + return 0; } ANativeWindow_setBuffersGeometry(potatoBridge.androidWindow, 0, 0, vid); @@ -799,7 +873,7 @@ JNIEXPORT jboolean JNICALL Java_org_lwjgl_glfw_GLFW_nativeEglInit(JNIEnv* env, j if (!potatoBridge.eglSurface) { printf("EGLBridge: Error eglCreateWindowSurface failed: %p\n", eglGetError_p()); //(*env)->ThrowNew(env,(*env)->FindClass(env,"java/lang/Exception"),"Trace exception"); - return JNI_FALSE; + return 0; } // sanity checks @@ -816,15 +890,29 @@ JNIEXPORT jboolean JNICALL Java_org_lwjgl_glfw_GLFW_nativeEglInit(JNIEnv* env, j potatoBridge.eglDisplay, potatoBridge.eglSurface ); - return JNI_TRUE; - } else if (strcmp(renderer, "vulkan_zink") == 0) { - config_renderer = RENDERER_VK_ZINK; - - setenv("GALLIUM_DRIVER","zink",1); - loadSymbols(); + if (config_renderer != RENDERER_VIRGL) { + return 1; + } + } + + if (config_renderer == RENDERER_VIRGL) { + // Init EGL context and vtest server + const EGLint ctx_attribs[] = { + EGL_CONTEXT_CLIENT_VERSION, 3, + EGL_NONE + }; + EGLContext* ctx = eglCreateContext_p(potatoBridge.eglDisplay, config, NULL, ctx_attribs); + printf("VirGL: created EGL context %p\n", ctx); + + pthread_t t; + pthread_create(&t, NULL, egl_make_current, (void *)ctx); + usleep(100*1000); // need enough time for the server to init + } + + if (config_renderer == RENDERER_VK_ZINK || config_renderer == RENDERER_VIRGL) { if(OSMesaCreateContext_p == NULL) { printf("OSMDroid: %s\n",dlerror()); - return JNI_FALSE; + return 0; } printf("OSMDroid: width=%i;height=%i, reserving %i bytes for frame buffer\n", savedWidth, savedHeight, @@ -832,19 +920,22 @@ JNIEXPORT jboolean JNICALL Java_org_lwjgl_glfw_GLFW_nativeEglInit(JNIEnv* env, j gbuffer = malloc(savedWidth * 4 * savedHeight+1); if (gbuffer) { printf("OSMDroid: created frame buffer\n"); - return JNI_TRUE; + return 1; } else { printf("OSMDroid: can't generate frame buffer\n"); - return JNI_FALSE; + return 0; } } - return JNI_FALSE; + return 0; } ANativeWindow_Buffer buf; int32_t stride; bool stopSwapBuffers; -void flipFrame() { +void pojavSwapBuffers() { + if (stopSwapBuffers) { + return; + } switch (config_renderer) { case RENDERER_GL4ES: { if (!eglSwapBuffers_p(potatoBridge.eglDisplay, eglGetCurrentSurface_p(EGL_DRAW))) { @@ -855,34 +946,55 @@ void flipFrame() { } } break; + case RENDERER_VIRGL: { + glFinish_p(); + vtest_swap_buffers_p(); + } break; + case RENDERER_VK_ZINK: { - ((struct osmesa_context)*OSMesaGetCurrentContext_p()) - .current_buffer->map = buf.bits; + OSMesaContext ctx = OSMesaGetCurrentContext_p(); + if(ctx == NULL) { + printf("Zink: attempted to swap buffers without context!"); + break; + } + OSMesaMakeCurrent_p(ctx,buf.bits,GL_UNSIGNED_BYTE,savedWidth,savedHeight); glFinish_p(); ANativeWindow_unlockAndPost(potatoBridge.androidWindow); + //OSMesaMakeCurrent_p(ctx,gbuffer,GL_UNSIGNED_BYTE,savedWidth,savedHeight); ANativeWindow_lock(potatoBridge.androidWindow,&buf,NULL); } break; } } -JNIEXPORT jboolean JNICALL Java_org_lwjgl_glfw_GLFW_nativeEglSwapBuffers(JNIEnv *env, jclass clazz) { - if (stopSwapBuffers) { - return JNI_FALSE; +void* egl_make_current(void* window) { + EGLBoolean success = eglMakeCurrent_p( + potatoBridge.eglDisplay, + window==0 ? (EGLSurface *) 0 : potatoBridge.eglSurface, + window==0 ? (EGLSurface *) 0 : potatoBridge.eglSurface, + /* window==0 ? EGL_NO_CONTEXT : */ (EGLContext *) window + ); + + if (success == EGL_FALSE) { + printf("EGLBridge: Error: eglMakeCurrent() failed: %p\n", eglGetError_p()); + } else { + printf("EGLBridge: eglMakeCurrent() succeed!\n"); + } + + if (config_renderer == RENDERER_VIRGL) { + printf("VirGL: vtest_main = %p\n", vtest_main_p); + printf("VirGL: Calling VTest server's main function\n"); + vtest_main_p(3, (const char*[]){"vtest", "--no-loop-or-fork", "--use-gles", NULL, NULL}); } - flipFrame(); - - return JNI_TRUE; } bool locked = false; -JNIEXPORT jboolean JNICALL Java_org_lwjgl_glfw_GLFW_nativeEglMakeCurrent(JNIEnv* env, jclass clazz, jlong window) { +void pojavMakeCurrent(void* window) { //if(OSMesaGetCurrentContext_p() != NULL) { // printf("OSMDroid: skipped context reset\n"); // return JNI_TRUE; //} - switch (config_renderer) { - case RENDERER_GL4ES: { + if (config_renderer == RENDERER_GL4ES) { EGLContext *currCtx = eglGetCurrentContext_p(); printf("EGLBridge: Comparing: thr=%d, this=%p, curr=%p\n", gettid(), window, currCtx); if (currCtx == NULL || window == 0) { @@ -903,17 +1015,7 @@ JNIEXPORT jboolean JNICALL Java_org_lwjgl_glfw_GLFW_nativeEglMakeCurrent(JNIEnv* //potatoBridge.eglContextOld = (void *) window; // eglMakeCurrent(potatoBridge.eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); printf("EGLBridge: Making current on window %p on thread %d\n", window, gettid()); - EGLBoolean success = eglMakeCurrent_p( - potatoBridge.eglDisplay, - window==0 ? (EGLSurface *) 0 : potatoBridge.eglSurface, - window==0 ? (EGLSurface *) 0 : potatoBridge.eglSurface, - /* window==0 ? EGL_NO_CONTEXT : */ (EGLContext *) window - ); - if (success == EGL_FALSE) { - printf("EGLBridge: Error: eglMakeCurrent() failed: %p\n", eglGetError_p()); - } else { - printf("EGLBridge: eglMakeCurrent() succeed!\n"); - } + egl_make_current((void *)window); // Test #ifdef GLES_TEST @@ -924,42 +1026,39 @@ JNIEXPORT jboolean JNICALL Java_org_lwjgl_glfw_GLFW_nativeEglMakeCurrent(JNIEnv* #endif // idk this should convert or just `return success;`... - return success == EGL_TRUE ? JNI_TRUE : JNI_FALSE; + return; //success == EGL_TRUE ? JNI_TRUE : JNI_FALSE; } else { // (*env)->ThrowNew(env,(*env)->FindClass(env,"java/lang/Exception"),"Trace exception"); - return JNI_FALSE; + return; } - } - - case RENDERER_VK_ZINK: { + } + + if (config_renderer == RENDERER_VK_ZINK || config_renderer == RENDERER_VIRGL) { printf("OSMDroid: making current\n"); OSMesaMakeCurrent_p((OSMesaContext)window,gbuffer,GL_UNSIGNED_BYTE,savedWidth,savedHeight); - ANativeWindow_lock(potatoBridge.androidWindow,&buf,NULL); - OSMesaPixelStore_p(OSMESA_ROW_LENGTH,buf.stride); - stride = buf.stride; - //ANativeWindow_unlockAndPost(potatoBridge.androidWindow); - - if (getenv("ZINK_FORCEGL") == "1") { - printf("OSMDroid: Forcing enable Vulkan extensions for Zink\n"); - struct zink_screen *zscreen = ((struct st_manager*)((OSMesaContext)window)->stctx->st_context_private)->screen; - - // OpenGL 3.0 - zscreen->info.have_EXT_transform_feedback = true; - zscreen->info.have_EXT_conditional_rendering = true; - // zscreen->info.feats.features.independentBlend = true; + if (config_renderer == RENDERER_VK_ZINK) { + ANativeWindow_lock(potatoBridge.androidWindow,&buf,NULL); + OSMesaPixelStore_p(OSMESA_ROW_LENGTH,buf.stride); + stride = buf.stride; + //ANativeWindow_unlockAndPost(potatoBridge.androidWindow); + OSMesaPixelStore_p(OSMESA_Y_UP,0); } - OSMesaPixelStore_p(OSMESA_Y_UP,0); printf("OSMDroid: vendor: %s\n",glGetString_p(GL_VENDOR)); printf("OSMDroid: renderer: %s\n",glGetString_p(GL_RENDERER)); glClear_p(GL_COLOR_BUFFER_BIT); glClearColor_p(0.4f, 0.4f, 0.4f, 1.0f); - flipFrame(); - return JNI_TRUE; - } + + // Trigger a texture creation, which then set VIRGL_TEXTURE_ID + int pixelsArr[4]; + glReadPixels_p(0, 0, 1, 1, GL_RGB, GL_INT, &pixelsArr); + + pojavSwapBuffers(); + return; } } +/* JNIEXPORT void JNICALL Java_org_lwjgl_glfw_GLFW_nativeEglDetachOnCurrentThread(JNIEnv *env, jclass clazz) { //Obstruct the context on the current thread @@ -968,44 +1067,36 @@ Java_org_lwjgl_glfw_GLFW_nativeEglDetachOnCurrentThread(JNIEnv *env, jclass claz case RENDERER_GL4ES: { eglMakeCurrent_p(potatoBridge.eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); } break; - + + case RENDERER_VIRGL: case RENDERER_VK_ZINK: { // Nothing to do here } break; } } +*/ -JNIEXPORT jlong JNICALL -Java_org_lwjgl_glfw_GLFW_nativeEglCreateContext(JNIEnv *env, jclass clazz, jlong contextSrc) { - switch (config_renderer) { - case RENDERER_GL4ES: { +void* pojavCreateContext(void* contextSrc) { + if (config_renderer == RENDERER_GL4ES) { const EGLint ctx_attribs[] = { EGL_CONTEXT_CLIENT_VERSION, atoi(getenv("LIBGL_ES")), EGL_NONE }; EGLContext* ctx = eglCreateContext_p(potatoBridge.eglDisplay, config, (void*)contextSrc, ctx_attribs); - potatoBridge.eglContext = ctx; - printf("EGLBridge: Created CTX pointer = %p\n",ctx); //(*env)->ThrowNew(env,(*env)->FindClass(env,"java/lang/Exception"),"Trace exception"); return (long)ctx; - } - - case RENDERER_VK_ZINK: { + } + + if (config_renderer == RENDERER_VK_ZINK || config_renderer == RENDERER_VIRGL) { printf("OSMDroid: generating context\n"); void* ctx = OSMesaCreateContext_p(OSMESA_RGBA,contextSrc); - printf("OSMDroid: context=%p",ctx); + printf("OSMDroid: context=%p\n",ctx); return ctx; - } } } -JNIEXPORT jboolean JNICALL Java_org_lwjgl_glfw_GLFW_nativeEglTerminate(JNIEnv* env, jclass clazz) { - terminateEgl(); - return JNI_TRUE; -} - JNIEXPORT void JNICALL Java_org_lwjgl_opengl_GL_nativeRegalMakeCurrent(JNIEnv *env, jclass clazz) { /*printf("Regal: making current"); @@ -1026,12 +1117,13 @@ Java_org_lwjgl_opengl_GL_getNativeWidthHeight(JNIEnv *env, jobject thiz) { (*env)->SetIntArrayRegion(env,ret,0,2,arr); return ret; } -JNIEXPORT jboolean JNICALL Java_org_lwjgl_glfw_GLFW_nativeEglSwapInterval(JNIEnv *env, jclass clazz, jint interval) { +void pojavSwapInterval(int interval) { switch (config_renderer) { - case RENDERER_GL4ES: { - return eglSwapInterval_p(potatoBridge.eglDisplay, interval); + case RENDERER_GL4ES: + case RENDERER_VIRGL: { + eglSwapInterval_p(potatoBridge.eglDisplay, interval); } break; - + case RENDERER_VK_ZINK: { printf("eglSwapInterval: NOT IMPLEMENTED YET!\n"); // Nothing to do here diff --git a/app_pojavlauncher/src/main/jni/input_bridge_v3.c b/app_pojavlauncher/src/main/jni/input_bridge_v3.c index 4c53c5055..12069bffb 100644 --- a/app_pojavlauncher/src/main/jni/input_bridge_v3.c +++ b/app_pojavlauncher/src/main/jni/input_bridge_v3.c @@ -37,7 +37,7 @@ typedef void GLFW_invoke_MouseButton_func(void* window, int button, int action, typedef void GLFW_invoke_Scroll_func(void* window, double xoffset, double yoffset); typedef void GLFW_invoke_WindowSize_func(void* window, int width, int height); -static int grabCursorX, grabCursorY, lastCursorX, lastCursorY; +static float grabCursorX, grabCursorY, lastCursorX, lastCursorY; jclass inputBridgeClass_ANDROID, inputBridgeClass_JRE; jmethodID inputBridgeMethod_ANDROID, inputBridgeMethod_JRE; @@ -159,14 +159,20 @@ void closeGLFWWindow() { exit(-1); } +JNIEXPORT void JNICALL +Java_org_lwjgl_glfw_CallbackBridge_nativeSetUseInputStackQueue(JNIEnv *env, jclass clazz, + jboolean use_input_stack_queue) { + isUseStackQueueCall = (int) use_input_stack_queue; +} + JNIEXPORT jboolean JNICALL Java_org_lwjgl_glfw_CallbackBridge_nativeAttachThreadToOther(JNIEnv* env, jclass clazz, jboolean isAndroid, jboolean isUseStackQueueBool) { #ifdef DEBUG - LOGD("Debug: JNI attaching thread, isUseStackQueue=%d\n", isUseStackQueue); + LOGD("Debug: JNI attaching thread, isUseStackQueue=%d\n", isUseStackQueueBool); #endif jboolean result; - isUseStackQueueCall = (int) isUseStackQueueBool; + //isUseStackQueueCall = (int) isUseStackQueueBool; if (isAndroid) { result = attachThread(true, &runtimeJNIEnvPtr_ANDROID); } else { @@ -255,8 +261,14 @@ JNIEXPORT void JNICALL Java_org_lwjgl_glfw_CallbackBridge_nativeSendCursorEnter( } } */ -JNIEXPORT void JNICALL Java_org_lwjgl_glfw_CallbackBridge_nativeSendCursorPos(JNIEnv* env, jclass clazz, jint x, jint y) { +JNIEXPORT void JNICALL Java_org_lwjgl_glfw_CallbackBridge_nativeSendCursorPos(JNIEnv* env, jclass clazz, jfloat x, jfloat y) { +#ifdef DEBUG + LOGD("Sending cursor position \n"); +#endif if (GLFW_invoke_CursorPos && isInputReady) { +#ifdef DEBUG + LOGD("GLFW_invoke_CursorPos && isInputReady \n"); +#endif if (!isCursorEntered) { if (GLFW_invoke_CursorEnter) { isCursorEntered = true; @@ -363,3 +375,20 @@ JNIEXPORT void JNICALL Java_org_lwjgl_glfw_CallbackBridge_nativeSendScroll(JNIEn JNIEXPORT void JNICALL Java_org_lwjgl_glfw_GLFW_nglfwSetShowingWindow(JNIEnv* env, jclass clazz, jlong window) { showingWindow = (long) window; } + +JNIEXPORT void JNICALL Java_org_lwjgl_glfw_CallbackBridge_nativeSetWindowAttrib(JNIEnv* env, jclass clazz, jint attrib, jint value) { + if (!showingWindow) { + return; // nothing to do yet + } + + jclass glfwClazz = (*runtimeJNIEnvPtr_JRE)->FindClass(runtimeJNIEnvPtr_JRE, "org/lwjgl/glfw/GLFW"); + assert(glfwClazz != NULL); + jmethodID glfwMethod = (*runtimeJNIEnvPtr_JRE)->GetStaticMethodID(runtimeJNIEnvPtr_JRE, glfwClazz, "glfwSetWindowAttrib", "(JII)V"); + assert(glfwMethod != NULL); + + (*runtimeJNIEnvPtr_JRE)->CallStaticVoidMethod( + runtimeJNIEnvPtr_JRE, + glfwClazz, glfwMethod, + (jlong) showingWindow, attrib, value + ); +} diff --git a/app_pojavlauncher/src/main/jni/jre_launcher.c b/app_pojavlauncher/src/main/jni/jre_launcher.c index d8595a05c..aa740a5af 100644 --- a/app_pojavlauncher/src/main/jni/jre_launcher.c +++ b/app_pojavlauncher/src/main/jni/jre_launcher.c @@ -85,7 +85,7 @@ typedef jint JLI_Launch_func(int argc, char ** argv, /* main argc, argc */ static jint launchJVM(int margc, char** margv) { void* libjli = dlopen("libjli.so", RTLD_LAZY | RTLD_GLOBAL); - + // Boardwalk: silence // LOGD("JLI lib = %x", (int)libjli); if (NULL == libjli) { @@ -170,15 +170,15 @@ JNIEXPORT jint JNICALL Java_com_oracle_dalvik_VMLauncher_launchJVM(JNIEnv *env, int argc = (*env)->GetArrayLength(env, argsArray); char **argv = convert_to_char_array(env, argsArray); - + LOGD("Done processing args"); res = launchJVM(argc, argv); LOGD("Going to free args"); free_char_array(env, argsArray, argv); - + LOGD("Free done"); - + return res; } diff --git a/app_pojavlauncher/src/main/jni/stdio_is.c b/app_pojavlauncher/src/main/jni/stdio_is.c index 8edbe11a9..a5ab94b1f 100644 --- a/app_pojavlauncher/src/main/jni/stdio_is.c +++ b/app_pojavlauncher/src/main/jni/stdio_is.c @@ -38,12 +38,12 @@ static void *logger_thread() { (*_______jvm)->DetachCurrentThread(_______jvm); } JNIEXPORT void JNICALL -Java_net_kdt_pojavlaunch_utils_JREUtils_logToActivity(JNIEnv *env, jclass clazz, jobject a) { +Java_net_kdt_pojavlaunch_utils_JREUtils_logToLogger(JNIEnv *env, jclass clazz, jobject javaLogger) { // TODO: implement logToActivity() - jclass loggableActivityClass = (*env)->FindClass(env,"net/kdt/pojavlaunch/LoggableActivity"); - _______method = (*env)->GetMethodID(env,loggableActivityClass,"appendlnToLog", "(Ljava/lang/String;)V"); + jclass loggableActivityClass = (*env)->FindClass(env,"net/kdt/pojavlaunch/Logger"); + _______method = (*env)->GetMethodID(env,loggableActivityClass,"appendToLog", "(Ljava/lang/String;)V"); (*env)->GetJavaVM(env,&_______jvm); - _______obj = (*env)->NewGlobalRef(env,a); + _______obj = (*env)->NewGlobalRef(env, javaLogger); setvbuf(stdout, 0, _IOLBF, 0); // make stdout line-buffered setvbuf(stderr, 0, _IONBF, 0); // make stderr unbuffered diff --git a/app_pojavlauncher/src/main/jniLibs/arm64-v8a/libOSMesa_8.so b/app_pojavlauncher/src/main/jniLibs/arm64-v8a/libOSMesa_8.so index 1b68efa1f..5a790eeeb 100644 Binary files a/app_pojavlauncher/src/main/jniLibs/arm64-v8a/libOSMesa_8.so and b/app_pojavlauncher/src/main/jniLibs/arm64-v8a/libOSMesa_8.so differ diff --git a/app_pojavlauncher/src/main/jniLibs/arm64-v8a/libgl4es_114.so b/app_pojavlauncher/src/main/jniLibs/arm64-v8a/libgl4es_114.so old mode 100644 new mode 100755 index bcf34a958..b8e9de91b Binary files a/app_pojavlauncher/src/main/jniLibs/arm64-v8a/libgl4es_114.so and b/app_pojavlauncher/src/main/jniLibs/arm64-v8a/libgl4es_114.so differ diff --git a/app_pojavlauncher/src/main/jniLibs/arm64-v8a/libgl4es_115.so b/app_pojavlauncher/src/main/jniLibs/arm64-v8a/libgl4es_115.so deleted file mode 100644 index 1377f7ccd..000000000 Binary files a/app_pojavlauncher/src/main/jniLibs/arm64-v8a/libgl4es_115.so and /dev/null differ diff --git a/app_pojavlauncher/src/main/jniLibs/arm64-v8a/libvgpu.so b/app_pojavlauncher/src/main/jniLibs/arm64-v8a/libvgpu.so deleted file mode 100644 index a98c8df65..000000000 Binary files a/app_pojavlauncher/src/main/jniLibs/arm64-v8a/libvgpu.so and /dev/null differ diff --git a/app_pojavlauncher/src/main/jniLibs/arm64-v8a/libvirgl_test_server.so b/app_pojavlauncher/src/main/jniLibs/arm64-v8a/libvirgl_test_server.so new file mode 100644 index 000000000..7fa43b7d9 Binary files /dev/null and b/app_pojavlauncher/src/main/jniLibs/arm64-v8a/libvirgl_test_server.so differ diff --git a/app_pojavlauncher/src/main/jniLibs/arm64-v8a/unpack200.so b/app_pojavlauncher/src/main/jniLibs/arm64-v8a/unpack200.so new file mode 100644 index 000000000..4307c6765 Binary files /dev/null and b/app_pojavlauncher/src/main/jniLibs/arm64-v8a/unpack200.so differ diff --git a/app_pojavlauncher/src/main/jniLibs/armeabi-v7a/libOSMesa_8.so b/app_pojavlauncher/src/main/jniLibs/armeabi-v7a/libOSMesa_8.so index a9d0ddf71..647871624 100644 Binary files a/app_pojavlauncher/src/main/jniLibs/armeabi-v7a/libOSMesa_8.so and b/app_pojavlauncher/src/main/jniLibs/armeabi-v7a/libOSMesa_8.so differ diff --git a/app_pojavlauncher/src/main/jniLibs/armeabi-v7a/libepoxy_0.so b/app_pojavlauncher/src/main/jniLibs/armeabi-v7a/libepoxy_0.so new file mode 100644 index 000000000..953c8860b Binary files /dev/null and b/app_pojavlauncher/src/main/jniLibs/armeabi-v7a/libepoxy_0.so differ diff --git a/app_pojavlauncher/src/main/jniLibs/armeabi-v7a/libgl4es_114.so b/app_pojavlauncher/src/main/jniLibs/armeabi-v7a/libgl4es_114.so index 9fe65e77d..c35aecee3 100644 Binary files a/app_pojavlauncher/src/main/jniLibs/armeabi-v7a/libgl4es_114.so and b/app_pojavlauncher/src/main/jniLibs/armeabi-v7a/libgl4es_114.so differ diff --git a/app_pojavlauncher/src/main/jniLibs/armeabi-v7a/libgl4es_115.so b/app_pojavlauncher/src/main/jniLibs/armeabi-v7a/libgl4es_115.so deleted file mode 100644 index bcc8c00be..000000000 Binary files a/app_pojavlauncher/src/main/jniLibs/armeabi-v7a/libgl4es_115.so and /dev/null differ diff --git a/app_pojavlauncher/src/main/jniLibs/armeabi-v7a/libvgpu.so b/app_pojavlauncher/src/main/jniLibs/armeabi-v7a/libvgpu.so deleted file mode 100644 index 11aa633ea..000000000 Binary files a/app_pojavlauncher/src/main/jniLibs/armeabi-v7a/libvgpu.so and /dev/null differ diff --git a/app_pojavlauncher/src/main/jniLibs/armeabi-v7a/libvirgl_test_server.so b/app_pojavlauncher/src/main/jniLibs/armeabi-v7a/libvirgl_test_server.so new file mode 100644 index 000000000..79a7c2353 Binary files /dev/null and b/app_pojavlauncher/src/main/jniLibs/armeabi-v7a/libvirgl_test_server.so differ diff --git a/app_pojavlauncher/src/main/jniLibs/armeabi-v7a/libvirglrenderer_1.so b/app_pojavlauncher/src/main/jniLibs/armeabi-v7a/libvirglrenderer_1.so new file mode 100644 index 000000000..dd009149d Binary files /dev/null and b/app_pojavlauncher/src/main/jniLibs/armeabi-v7a/libvirglrenderer_1.so differ diff --git a/app_pojavlauncher/src/main/jniLibs/armeabi-v7a/unpack200.so b/app_pojavlauncher/src/main/jniLibs/armeabi-v7a/unpack200.so new file mode 100644 index 000000000..de04a68f1 Binary files /dev/null and b/app_pojavlauncher/src/main/jniLibs/armeabi-v7a/unpack200.so differ diff --git a/app_pojavlauncher/src/main/jniLibs/x86/libgl4es_114.so b/app_pojavlauncher/src/main/jniLibs/x86/libgl4es_114.so index 80f7e29a5..687a773a0 100644 Binary files a/app_pojavlauncher/src/main/jniLibs/x86/libgl4es_114.so and b/app_pojavlauncher/src/main/jniLibs/x86/libgl4es_114.so differ diff --git a/app_pojavlauncher/src/main/jniLibs/x86/libgl4es_115.so b/app_pojavlauncher/src/main/jniLibs/x86/libgl4es_115.so deleted file mode 100644 index ab74e6379..000000000 Binary files a/app_pojavlauncher/src/main/jniLibs/x86/libgl4es_115.so and /dev/null differ diff --git a/app_pojavlauncher/src/main/jniLibs/x86/libvgpu.so b/app_pojavlauncher/src/main/jniLibs/x86/libvgpu.so deleted file mode 100644 index b3561c26e..000000000 Binary files a/app_pojavlauncher/src/main/jniLibs/x86/libvgpu.so and /dev/null differ diff --git a/app_pojavlauncher/src/main/jniLibs/x86/unpack200.so b/app_pojavlauncher/src/main/jniLibs/x86/unpack200.so new file mode 100644 index 000000000..ba83d63e0 Binary files /dev/null and b/app_pojavlauncher/src/main/jniLibs/x86/unpack200.so differ diff --git a/app_pojavlauncher/src/main/jniLibs/x86_64/libOSMesa_8.so b/app_pojavlauncher/src/main/jniLibs/x86_64/libOSMesa_8.so index f5a808d90..9ee7b0124 100644 Binary files a/app_pojavlauncher/src/main/jniLibs/x86_64/libOSMesa_8.so and b/app_pojavlauncher/src/main/jniLibs/x86_64/libOSMesa_8.so differ diff --git a/app_pojavlauncher/src/main/jniLibs/x86_64/libepoxy_0.so b/app_pojavlauncher/src/main/jniLibs/x86_64/libepoxy_0.so new file mode 100644 index 000000000..ad7997cab Binary files /dev/null and b/app_pojavlauncher/src/main/jniLibs/x86_64/libepoxy_0.so differ diff --git a/app_pojavlauncher/src/main/jniLibs/x86_64/libgl4es_114.so b/app_pojavlauncher/src/main/jniLibs/x86_64/libgl4es_114.so index de9f5adad..ba1857b0c 100644 Binary files a/app_pojavlauncher/src/main/jniLibs/x86_64/libgl4es_114.so and b/app_pojavlauncher/src/main/jniLibs/x86_64/libgl4es_114.so differ diff --git a/app_pojavlauncher/src/main/jniLibs/x86_64/libgl4es_115.so b/app_pojavlauncher/src/main/jniLibs/x86_64/libgl4es_115.so deleted file mode 100644 index cca8e461e..000000000 Binary files a/app_pojavlauncher/src/main/jniLibs/x86_64/libgl4es_115.so and /dev/null differ diff --git a/app_pojavlauncher/src/main/jniLibs/x86_64/libvgpu.so b/app_pojavlauncher/src/main/jniLibs/x86_64/libvgpu.so deleted file mode 100644 index b15128f84..000000000 Binary files a/app_pojavlauncher/src/main/jniLibs/x86_64/libvgpu.so and /dev/null differ diff --git a/app_pojavlauncher/src/main/jniLibs/x86_64/libvirgl_test_server.so b/app_pojavlauncher/src/main/jniLibs/x86_64/libvirgl_test_server.so new file mode 100644 index 000000000..6c96e6a68 Binary files /dev/null and b/app_pojavlauncher/src/main/jniLibs/x86_64/libvirgl_test_server.so differ diff --git a/app_pojavlauncher/src/main/jniLibs/x86_64/libvirglrenderer_1.so b/app_pojavlauncher/src/main/jniLibs/x86_64/libvirglrenderer_1.so new file mode 100644 index 000000000..19e66c7cb Binary files /dev/null and b/app_pojavlauncher/src/main/jniLibs/x86_64/libvirglrenderer_1.so differ diff --git a/app_pojavlauncher/src/main/jniLibs/x86_64/unpack200.so b/app_pojavlauncher/src/main/jniLibs/x86_64/unpack200.so new file mode 100644 index 000000000..8faef78aa Binary files /dev/null and b/app_pojavlauncher/src/main/jniLibs/x86_64/unpack200.so differ diff --git a/app_pojavlauncher/src/main/res/anim/translate_left_side.xml b/app_pojavlauncher/src/main/res/anim/translate_left_side.xml deleted file mode 100644 index 962be1cc6..000000000 --- a/app_pojavlauncher/src/main/res/anim/translate_left_side.xml +++ /dev/null @@ -1,5 +0,0 @@ - - diff --git a/app_pojavlauncher/src/main/res/anim/translate_right_side.xml b/app_pojavlauncher/src/main/res/anim/translate_right_side.xml deleted file mode 100644 index 607d4f8d0..000000000 --- a/app_pojavlauncher/src/main/res/anim/translate_right_side.xml +++ /dev/null @@ -1,5 +0,0 @@ - - diff --git a/app_pojavlauncher/src/main/res/drawable-hdpi/ic_flatstyle.png b/app_pojavlauncher/src/main/res/drawable-hdpi/ic_flatstyle.png deleted file mode 100644 index 777730532..000000000 Binary files a/app_pojavlauncher/src/main/res/drawable-hdpi/ic_flatstyle.png and /dev/null differ diff --git a/app_pojavlauncher/src/main/res/drawable-hdpi/ic_map_rmb.png b/app_pojavlauncher/src/main/res/drawable-hdpi/ic_map_rmb.png deleted file mode 100644 index 5bdaa3296..000000000 Binary files a/app_pojavlauncher/src/main/res/drawable-hdpi/ic_map_rmb.png and /dev/null differ diff --git a/app_pojavlauncher/src/main/res/drawable-hdpi/ic_disable_gestures.png b/app_pojavlauncher/src/main/res/drawable-hdpi/ic_setting_disable_gestures.png similarity index 100% rename from app_pojavlauncher/src/main/res/drawable-hdpi/ic_disable_gestures.png rename to app_pojavlauncher/src/main/res/drawable-hdpi/ic_setting_disable_gestures.png diff --git a/app_pojavlauncher/src/main/res/drawable-hdpi/freeform_opt.png b/app_pojavlauncher/src/main/res/drawable-hdpi/ic_setting_freeform.png similarity index 100% rename from app_pojavlauncher/src/main/res/drawable-hdpi/freeform_opt.png rename to app_pojavlauncher/src/main/res/drawable-hdpi/ic_setting_freeform.png diff --git a/app_pojavlauncher/src/main/res/drawable-hdpi/ic_lib_check.png b/app_pojavlauncher/src/main/res/drawable-hdpi/ic_setting_lib_check.png similarity index 100% rename from app_pojavlauncher/src/main/res/drawable-hdpi/ic_lib_check.png rename to app_pojavlauncher/src/main/res/drawable-hdpi/ic_setting_lib_check.png diff --git a/app_pojavlauncher/src/main/res/drawable-hdpi/ic_auto_ram.png b/app_pojavlauncher/src/main/res/drawable-hdpi/ic_setting_ram.png similarity index 100% rename from app_pojavlauncher/src/main/res/drawable-hdpi/ic_auto_ram.png rename to app_pojavlauncher/src/main/res/drawable-hdpi/ic_setting_ram.png diff --git a/app_pojavlauncher/src/main/res/drawable-hdpi/tap_len.png b/app_pojavlauncher/src/main/res/drawable-hdpi/ic_setting_tap_length.png old mode 100755 new mode 100644 similarity index 100% rename from app_pojavlauncher/src/main/res/drawable-hdpi/tap_len.png rename to app_pojavlauncher/src/main/res/drawable-hdpi/ic_setting_tap_length.png diff --git a/app_pojavlauncher/src/main/res/drawable-hdpi/text_select_handle_left_mtrl_alpha.png b/app_pojavlauncher/src/main/res/drawable-hdpi/text_select_handle_left_mtrl_alpha.png deleted file mode 100644 index 9cdc25b45..000000000 Binary files a/app_pojavlauncher/src/main/res/drawable-hdpi/text_select_handle_left_mtrl_alpha.png and /dev/null differ diff --git a/app_pojavlauncher/src/main/res/drawable-hdpi/text_select_handle_right_mtrl_alpha.png b/app_pojavlauncher/src/main/res/drawable-hdpi/text_select_handle_right_mtrl_alpha.png deleted file mode 100644 index 276d48091..000000000 Binary files a/app_pojavlauncher/src/main/res/drawable-hdpi/text_select_handle_right_mtrl_alpha.png and /dev/null differ diff --git a/app_pojavlauncher/src/main/res/drawable-mdpi/btn_scale.png b/app_pojavlauncher/src/main/res/drawable-mdpi/ic_setting_btn_scale.png old mode 100755 new mode 100644 similarity index 100% rename from app_pojavlauncher/src/main/res/drawable-mdpi/btn_scale.png rename to app_pojavlauncher/src/main/res/drawable-mdpi/ic_setting_btn_scale.png diff --git a/app_pojavlauncher/src/main/res/drawable-mdpi/jre_args.png b/app_pojavlauncher/src/main/res/drawable-mdpi/ic_setting_jre_args.png old mode 100755 new mode 100644 similarity index 100% rename from app_pojavlauncher/src/main/res/drawable-mdpi/jre_args.png rename to app_pojavlauncher/src/main/res/drawable-mdpi/ic_setting_jre_args.png diff --git a/app_pojavlauncher/src/main/res/drawable-mdpi/mouse_pointer_1.png b/app_pojavlauncher/src/main/res/drawable-mdpi/ic_setting_mouse_pointer.png old mode 100755 new mode 100644 similarity index 100% rename from app_pojavlauncher/src/main/res/drawable-mdpi/mouse_pointer_1.png rename to app_pojavlauncher/src/main/res/drawable-mdpi/ic_setting_mouse_pointer.png diff --git a/app_pojavlauncher/src/main/res/drawable-mdpi/rm_jre.png b/app_pojavlauncher/src/main/res/drawable-mdpi/rm_jre.png deleted file mode 100755 index dec16982a..000000000 Binary files a/app_pojavlauncher/src/main/res/drawable-mdpi/rm_jre.png and /dev/null differ diff --git a/app_pojavlauncher/src/main/res/drawable-mdpi/text_select_handle_left_mtrl_alpha.png b/app_pojavlauncher/src/main/res/drawable-mdpi/text_select_handle_left_mtrl_alpha.png deleted file mode 100644 index 95c0168cd..000000000 Binary files a/app_pojavlauncher/src/main/res/drawable-mdpi/text_select_handle_left_mtrl_alpha.png and /dev/null differ diff --git a/app_pojavlauncher/src/main/res/drawable-mdpi/text_select_handle_right_mtrl_alpha.png b/app_pojavlauncher/src/main/res/drawable-mdpi/text_select_handle_right_mtrl_alpha.png deleted file mode 100644 index 569332abf..000000000 Binary files a/app_pojavlauncher/src/main/res/drawable-mdpi/text_select_handle_right_mtrl_alpha.png and /dev/null differ diff --git a/app_pojavlauncher/src/main/res/drawable-xhdpi/hide_sidebar.png b/app_pojavlauncher/src/main/res/drawable-xhdpi/ic_setting_hide_sidebar.png old mode 100755 new mode 100644 similarity index 100% rename from app_pojavlauncher/src/main/res/drawable-xhdpi/hide_sidebar.png rename to app_pojavlauncher/src/main/res/drawable-xhdpi/ic_setting_hide_sidebar.png diff --git a/app_pojavlauncher/src/main/res/drawable-xhdpi/text_select_handle_left_mtrl_alpha.png b/app_pojavlauncher/src/main/res/drawable-xhdpi/text_select_handle_left_mtrl_alpha.png deleted file mode 100644 index a01ac1030..000000000 Binary files a/app_pojavlauncher/src/main/res/drawable-xhdpi/text_select_handle_left_mtrl_alpha.png and /dev/null differ diff --git a/app_pojavlauncher/src/main/res/drawable-xhdpi/text_select_handle_right_mtrl_alpha.png b/app_pojavlauncher/src/main/res/drawable-xhdpi/text_select_handle_right_mtrl_alpha.png deleted file mode 100644 index d3602d988..000000000 Binary files a/app_pojavlauncher/src/main/res/drawable-xhdpi/text_select_handle_right_mtrl_alpha.png and /dev/null differ diff --git a/app_pojavlauncher/src/main/res/drawable-xxhdpi/text_select_handle_left_mtrl_alpha.png b/app_pojavlauncher/src/main/res/drawable-xxhdpi/text_select_handle_left_mtrl_alpha.png deleted file mode 100644 index 75085ce6d..000000000 Binary files a/app_pojavlauncher/src/main/res/drawable-xxhdpi/text_select_handle_left_mtrl_alpha.png and /dev/null differ diff --git a/app_pojavlauncher/src/main/res/drawable-xxhdpi/text_select_handle_right_mtrl_alpha.png b/app_pojavlauncher/src/main/res/drawable-xxhdpi/text_select_handle_right_mtrl_alpha.png deleted file mode 100644 index e2eb5bed2..000000000 Binary files a/app_pojavlauncher/src/main/res/drawable-xxhdpi/text_select_handle_right_mtrl_alpha.png and /dev/null differ diff --git a/app_pojavlauncher/src/main/res/drawable/control_button.xml b/app_pojavlauncher/src/main/res/drawable/control_button.xml index 1584cc507..3ace1776b 100644 --- a/app_pojavlauncher/src/main/res/drawable/control_button.xml +++ b/app_pojavlauncher/src/main/res/drawable/control_button.xml @@ -1,6 +1,6 @@ - - + + diff --git a/app_pojavlauncher/src/main/res/drawable/control_button_normal.xml b/app_pojavlauncher/src/main/res/drawable/control_button_normal.xml deleted file mode 100644 index b40a1e69b..000000000 --- a/app_pojavlauncher/src/main/res/drawable/control_button_normal.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app_pojavlauncher/src/main/res/drawable/control_button_pressed.xml b/app_pojavlauncher/src/main/res/drawable/control_button_pressed.xml deleted file mode 100644 index 7cba97e87..000000000 --- a/app_pojavlauncher/src/main/res/drawable/control_button_pressed.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app_pojavlauncher/src/main/res/drawable/ic_arrow_back_white.xml b/app_pojavlauncher/src/main/res/drawable/ic_arrow_back_white.xml new file mode 100644 index 000000000..9b1f144be --- /dev/null +++ b/app_pojavlauncher/src/main/res/drawable/ic_arrow_back_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/app_pojavlauncher/src/main/res/drawable/pointer.png b/app_pojavlauncher/src/main/res/drawable/ic_gamepad_pointer.png similarity index 100% rename from app_pojavlauncher/src/main/res/drawable/pointer.png rename to app_pojavlauncher/src/main/res/drawable/ic_gamepad_pointer.png diff --git a/app_pojavlauncher/src/main/res/drawable/logo.png b/app_pojavlauncher/src/main/res/drawable/ic_logo.png similarity index 100% rename from app_pojavlauncher/src/main/res/drawable/logo.png rename to app_pojavlauncher/src/main/res/drawable/ic_logo.png diff --git a/app_pojavlauncher/src/main/res/drawable/mouse_pointer.png b/app_pojavlauncher/src/main/res/drawable/ic_mouse_pointer.png similarity index 100% rename from app_pojavlauncher/src/main/res/drawable/mouse_pointer.png rename to app_pojavlauncher/src/main/res/drawable/ic_mouse_pointer.png diff --git a/app_pojavlauncher/src/main/res/drawable/ignore_notch.png b/app_pojavlauncher/src/main/res/drawable/ic_setting_ignore_notch.png old mode 100755 new mode 100644 similarity index 100% rename from app_pojavlauncher/src/main/res/drawable/ignore_notch.png rename to app_pojavlauncher/src/main/res/drawable/ic_setting_ignore_notch.png diff --git a/app_pojavlauncher/src/main/res/drawable/mouse_pointer_spd.png b/app_pojavlauncher/src/main/res/drawable/ic_setting_mouse_pointer_spd.png similarity index 100% rename from app_pojavlauncher/src/main/res/drawable/mouse_pointer_spd.png rename to app_pojavlauncher/src/main/res/drawable/ic_setting_mouse_pointer_spd.png diff --git a/app_pojavlauncher/src/main/res/drawable/resolution_scaler.png b/app_pojavlauncher/src/main/res/drawable/ic_setting_resolution_scaler.png old mode 100755 new mode 100644 similarity index 100% rename from app_pojavlauncher/src/main/res/drawable/resolution_scaler.png rename to app_pojavlauncher/src/main/res/drawable/ic_setting_resolution_scaler.png diff --git a/app_pojavlauncher/src/main/res/drawable/sign_in_background.png b/app_pojavlauncher/src/main/res/drawable/ic_setting_sign_in_background.png similarity index 100% rename from app_pojavlauncher/src/main/res/drawable/sign_in_background.png rename to app_pojavlauncher/src/main/res/drawable/ic_setting_sign_in_background.png diff --git a/app_pojavlauncher/src/main/res/drawable/ic_view_handle.xml b/app_pojavlauncher/src/main/res/drawable/ic_view_handle.xml new file mode 100644 index 000000000..f85c50284 --- /dev/null +++ b/app_pojavlauncher/src/main/res/drawable/ic_view_handle.xml @@ -0,0 +1,4 @@ + + + diff --git a/app_pojavlauncher/src/main/res/drawable/mouse_pointer_1.png b/app_pojavlauncher/src/main/res/drawable/mouse_pointer_1.png deleted file mode 100755 index e5bb2041d..000000000 Binary files a/app_pojavlauncher/src/main/res/drawable/mouse_pointer_1.png and /dev/null differ diff --git a/app_pojavlauncher/src/main/res/drawable/text_select_handle_left_material.xml b/app_pojavlauncher/src/main/res/drawable/text_select_handle_left_material.xml deleted file mode 100644 index b228d3f5a..000000000 --- a/app_pojavlauncher/src/main/res/drawable/text_select_handle_left_material.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - diff --git a/app_pojavlauncher/src/main/res/drawable/text_select_handle_right_material.xml b/app_pojavlauncher/src/main/res/drawable/text_select_handle_right_material.xml deleted file mode 100644 index acacbf678..000000000 --- a/app_pojavlauncher/src/main/res/drawable/text_select_handle_right_material.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - diff --git a/app_pojavlauncher/src/main/res/layout/activity_basemain.xml b/app_pojavlauncher/src/main/res/layout/activity_basemain.xml new file mode 100644 index 000000000..8d2452ed9 --- /dev/null +++ b/app_pojavlauncher/src/main/res/layout/activity_basemain.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app_pojavlauncher/src/main/res/layout/control_mapping.xml b/app_pojavlauncher/src/main/res/layout/activity_custom_controls.xml similarity index 85% rename from app_pojavlauncher/src/main/res/layout/control_mapping.xml rename to app_pojavlauncher/src/main/res/layout/activity_custom_controls.xml index 51ef1ffa4..907adfe14 100644 --- a/app_pojavlauncher/src/main/res/layout/control_mapping.xml +++ b/app_pojavlauncher/src/main/res/layout/activity_custom_controls.xml @@ -26,13 +26,13 @@ - + android:background="?attr/colorBackgroundFloating" + android:fitsSystemWindows="false" /> diff --git a/app_pojavlauncher/src/main/res/layout/import_control_layout.xml b/app_pojavlauncher/src/main/res/layout/activity_import_control.xml similarity index 97% rename from app_pojavlauncher/src/main/res/layout/import_control_layout.xml rename to app_pojavlauncher/src/main/res/layout/activity_import_control.xml index 4e5704f38..5aac9e3d1 100644 --- a/app_pojavlauncher/src/main/res/layout/import_control_layout.xml +++ b/app_pojavlauncher/src/main/res/layout/activity_import_control.xml @@ -12,7 +12,7 @@ android:layout_height="match_parent" android:elevation="-10dp" android:scaleType="centerCrop" - android:src="@drawable/sign_in_background" /> + android:src="@drawable/ic_setting_sign_in_background" /> + + + + + + + + + + + + + + +