diff --git a/.github/workflows/Android_Build.yml b/.github/workflows/Android_Build.yml new file mode 100644 index 000000000..c94140d70 --- /dev/null +++ b/.github/workflows/Android_Build.yml @@ -0,0 +1,89 @@ +name: Android Build + +on: + push: + branches: + - master + pull_request: + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + x64: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Fetch submodules + run: git submodule update --init --recursive + + - name: Setup Vulkan SDK + uses: humbletim/setup-vulkan-sdk@v1.2.0 + with: + vulkan-query-version: latest + vulkan-use-cache: true + vulkan-components: Vulkan-Headers, Vulkan-Loader, SPIRV-Tools, Glslang + + - name: Setup Java + uses: actions/setup-java@v3 + with: + distribution: 'zulu' # See 'Supported distributions' for available options + java-version: '17' + + - name: Configure CMake + run: cmake -B ${{github.workspace}}/build -DBUILD_HYDRA_CORE=1 -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_TOOLCHAIN_FILE=${ANDROID_NDK_ROOT}/build/cmake/android.toolchain.cmake -DANDROID_ABI=x86_64 -DENABLE_VULKAN=0 + + - name: Build + run: | + cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + mv ./build/libAlber.so ./src/pandroid/app/src/main/jniLibs/x86_64/ + cd src/pandroid + ./gradlew assembleDebug + cd ../.. + + - name: Upload executable + uses: actions/upload-artifact@v2 + with: + name: Android APK (x86-64) + path: './src/pandroid/app/build/outputs/apk/debug/app-debug.apk' + + arm64: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Fetch submodules + run: git submodule update --init --recursive + + - name: Setup Vulkan SDK + uses: humbletim/setup-vulkan-sdk@v1.2.0 + with: + vulkan-query-version: latest + vulkan-use-cache: true + vulkan-components: Vulkan-Headers, Vulkan-Loader, SPIRV-Tools, Glslang + + - name: Setup Java + uses: actions/setup-java@v3 + with: + distribution: 'zulu' # See 'Supported distributions' for available options + java-version: '17' + + - name: Configure CMake + run: cmake -B ${{github.workspace}}/build -DBUILD_HYDRA_CORE=1 -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_TOOLCHAIN_FILE=${ANDROID_NDK_ROOT}/build/cmake/android.toolchain.cmake -DANDROID_ABI=arm64-v8a -DENABLE_VULKAN=0 -DCMAKE_CXX_FLAGS="-march=armv8.1-a+crypto" + + - name: Build + run: | + cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + mv ./build/libAlber.so ./src/pandroid/app/src/main/jniLibs/arm64-v8a/ + cd src/pandroid + ./gradlew assembleDebug + cd ../.. + + - name: Upload executable + uses: actions/upload-artifact@v2 + with: + name: Android APK (arm64) + path: './src/pandroid/app/build/outputs/apk/debug/app-debug.apk' + diff --git a/CMakeLists.txt b/CMakeLists.txt index c202f80cc..4aa4456ab 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -184,16 +184,18 @@ set(APPLET_SOURCE_FILES src/core/applets/applet.cpp src/core/applets/mii_selecto set(RENDERER_SW_SOURCE_FILES src/core/renderer_sw/renderer_sw.cpp) # Frontend source files -if(ENABLE_QT_GUI) - set(FRONTEND_SOURCE_FILES src/panda_qt/main.cpp src/panda_qt/screen.cpp src/panda_qt/main_window.cpp src/panda_qt/about_window.cpp) - set(FRONTEND_HEADER_FILES include/panda_qt/screen.hpp include/panda_qt/main_window.hpp include/panda_qt/about_window.hpp) - - source_group("Source Files\\Qt" FILES ${FRONTEND_SOURCE_FILES}) - source_group("Header Files\\Qt" FILES ${FRONTEND_HEADER_FILES}) - include_directories(${Qt6Gui_PRIVATE_INCLUDE_DIRS}) -else() - set(FRONTEND_SOURCE_FILES src/panda_sdl/main.cpp src/panda_sdl/frontend_sdl.cpp) - set(FRONTEND_HEADER_FILES "") +if(NOT ANDROID) + if(ENABLE_QT_GUI) + set(FRONTEND_SOURCE_FILES src/panda_qt/main.cpp src/panda_qt/screen.cpp src/panda_qt/main_window.cpp src/panda_qt/about_window.cpp) + set(FRONTEND_HEADER_FILES include/panda_qt/screen.hpp include/panda_qt/main_window.hpp include/panda_qt/about_window.hpp) + + source_group("Source Files\\Qt" FILES ${FRONTEND_SOURCE_FILES}) + source_group("Header Files\\Qt" FILES ${FRONTEND_HEADER_FILES}) + include_directories(${Qt6Gui_PRIVATE_INCLUDE_DIRS}) + else() + set(FRONTEND_SOURCE_FILES src/panda_sdl/main.cpp src/panda_sdl/frontend_sdl.cpp) + set(FRONTEND_HEADER_FILES "") + endif() endif() set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/termcolor.hpp @@ -378,6 +380,10 @@ if(ENABLE_VULKAN) set(ALL_SOURCES ${ALL_SOURCES} ${RENDERER_VK_SOURCE_FILES}) endif() +if(ANDROID) + set(ALL_SOURCES ${ALL_SOURCES} src/jni_driver.cpp) +endif() + if(BUILD_HYDRA_CORE) include_directories(third_party/hydra_core/include) add_library(Alber SHARED ${ALL_SOURCES} src/hydra_core.cpp) @@ -386,11 +392,19 @@ else() add_executable(Alber ${ALL_SOURCES}) endif() +if(ANDROID) + target_link_libraries(Alber PRIVATE EGL log) +endif() + if(ENABLE_LTO OR ENABLE_USER_BUILD) set_target_properties(Alber PROPERTIES INTERPROCEDURAL_OPTIMIZATION TRUE) endif() -target_link_libraries(Alber PRIVATE dynarmic SDL2-static cryptopp glad resources_console_fonts) +target_link_libraries(Alber PRIVATE dynarmic cryptopp glad resources_console_fonts) + +if(NOT ANDROID) + target_link_libraries(Alber PRIVATE SDL2-static) +endif() if(ENABLE_DISCORD_RPC AND NOT ANDROID) target_compile_definitions(Alber PUBLIC "PANDA3DS_ENABLE_DISCORD_RPC=1") diff --git a/src/emulator.cpp b/src/emulator.cpp index 81d3ac5c6..cd6bbdcb6 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -1,6 +1,8 @@ #include "emulator.hpp" +#ifndef __ANDROID__ #include +#endif #include diff --git a/src/jni_driver.cpp b/src/jni_driver.cpp new file mode 100644 index 000000000..422096d88 --- /dev/null +++ b/src/jni_driver.cpp @@ -0,0 +1,74 @@ +#include +#include +#include + +#include + +#include "emulator.hpp" +#include "renderer_gl/renderer_gl.hpp" +#include "services/hid.hpp" + +std::unique_ptr emulator = nullptr; +HIDService* hidService = nullptr; +RendererGL* renderer = nullptr; +bool romLoaded = false; + +extern "C" JNIEXPORT void JNICALL Java_com_panda3ds_pandroid_AlberDriver_Initialize(JNIEnv* env, jobject obj) { + emulator = std::make_unique(); + + if (emulator->getRendererType() != RendererType::OpenGL) { + throw std::runtime_error("Renderer is not OpenGL"); + } + + renderer = static_cast(emulator->getRenderer()); + hidService = &emulator->getServiceManager().getHID(); + + if (!gladLoadGLES2Loader(reinterpret_cast(eglGetProcAddress))) { + throw std::runtime_error("OpenGL ES init failed"); + } + + __android_log_print(ANDROID_LOG_INFO, "AlberDriver", "OpenGL ES %d.%d", GLVersion.major, GLVersion.minor); + emulator->initGraphicsContext(nullptr); +} + +extern "C" JNIEXPORT void JNICALL Java_com_panda3ds_pandroid_AlberDriver_RunFrame(JNIEnv* env, jobject obj, jint fbo) { + renderer->setFBO(fbo); + renderer->resetStateManager(); + emulator->runFrame(); + + hidService->updateInputs(emulator->getTicks()); +} + +extern "C" JNIEXPORT void JNICALL Java_com_panda3ds_pandroid_AlberDriver_Finalize(JNIEnv* env, jobject obj) { + emulator = nullptr; + hidService = nullptr; + renderer = nullptr; +} + +extern "C" JNIEXPORT jboolean JNICALL Java_com_panda3ds_pandroid_AlberDriver_HasRomLoaded(JNIEnv* env, jobject obj) { return romLoaded; } + +extern "C" JNIEXPORT void JNICALL Java_com_panda3ds_pandroid_AlberDriver_LoadRom(JNIEnv* env, jobject obj, jstring path) { + const char* pathStr = env->GetStringUTFChars(path, nullptr); + __android_log_print(ANDROID_LOG_INFO, "AlberDriver", "Loading ROM %s", pathStr); + romLoaded = emulator->loadROM(pathStr); + env->ReleaseStringUTFChars(path, pathStr); +} + +extern "C" JNIEXPORT void JNICALL Java_com_panda3ds_pandroid_AlberDriver_TouchScreenDown(JNIEnv* env, jobject obj, jint x, jint y) { + hidService->setTouchScreenPress((u16)x, (u16)y); +} + +extern "C" JNIEXPORT void JNICALL Java_com_panda3ds_pandroid_AlberDriver_TouchScreenUp(JNIEnv* env, jobject obj) { hidService->releaseTouchScreen(); } + +extern "C" JNIEXPORT void JNICALL Java_com_panda3ds_pandroid_AlberDriver_KeyUp(JNIEnv* env, jobject obj, jint keyCode) { + hidService->releaseKey((u32)keyCode); +} + +extern "C" JNIEXPORT void JNICALL Java_com_panda3ds_pandroid_AlberDriver_KeyDown(JNIEnv* env, jobject obj, jint keyCode) { + hidService->pressKey((u32)keyCode); +} + +extern "C" JNIEXPORT void JNICALL Java_com_panda3ds_pandroid_AlberDriver_SetCirclepadAxis(JNIEnv* env, jobject obj, jint x, jint y) { + hidService->setCirclepadX((s16)x); + hidService->setCirclepadY((s16)y); +} diff --git a/src/pandroid/.gitignore b/src/pandroid/.gitignore new file mode 100644 index 000000000..aa724b770 --- /dev/null +++ b/src/pandroid/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/src/pandroid/app/.gitignore b/src/pandroid/app/.gitignore new file mode 100644 index 000000000..42afabfd2 --- /dev/null +++ b/src/pandroid/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/src/pandroid/app/build.gradle.kts b/src/pandroid/app/build.gradle.kts new file mode 100644 index 000000000..ff8debcbd --- /dev/null +++ b/src/pandroid/app/build.gradle.kts @@ -0,0 +1,46 @@ +plugins { + id("com.android.application") +} + +android { + namespace = "com.panda3ds.pandroid" + compileSdk = 33 + + defaultConfig { + applicationId = "com.panda3ds.pandroid" + minSdk = 24 + targetSdk = 33 + versionCode = 1 + versionName = "1.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + + ndk { + abiFilters += listOf("x86_64", "arm64-v8a") + } + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } +} + +dependencies { + + implementation("androidx.appcompat:appcompat:1.6.1") + implementation("com.google.android.material:material:1.8.0") + implementation("androidx.constraintlayout:constraintlayout:2.1.4") + testImplementation("junit:junit:4.13.2") + androidTestImplementation("androidx.test.ext:junit:1.1.5") + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") +} \ No newline at end of file diff --git a/src/pandroid/app/proguard-rules.pro b/src/pandroid/app/proguard-rules.pro new file mode 100644 index 000000000..481bb4348 --- /dev/null +++ b/src/pandroid/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/src/pandroid/app/src/main/AndroidManifest.xml b/src/pandroid/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..01931edcf --- /dev/null +++ b/src/pandroid/app/src/main/AndroidManifest.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/AlberDriver.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/AlberDriver.java new file mode 100644 index 000000000..92d276a2d --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/AlberDriver.java @@ -0,0 +1,24 @@ +package com.panda3ds.pandroid; + +public class AlberDriver { + + AlberDriver() { + super(); + } + + public static native void Initialize(); + public static native void RunFrame(int fbo); + public static native boolean HasRomLoaded(); + public static native void LoadRom(String path); + public static native void Finalize(); + + public static native void KeyDown(int code); + public static native void KeyUp(int code); + public static native void SetCirclepadAxis(int x, int y); + public static native void TouchScreenUp(); + public static native void TouchScreenDown(int x, int y); + + static { + System.loadLibrary("Alber"); + } +} \ No newline at end of file diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/BaseActivity.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/BaseActivity.java new file mode 100644 index 000000000..597d664bd --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/BaseActivity.java @@ -0,0 +1,6 @@ +package com.panda3ds.pandroid.app; + +import androidx.appcompat.app.AppCompatActivity; + +public class BaseActivity extends AppCompatActivity { +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/GameActivity.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/GameActivity.java new file mode 100644 index 000000000..60fe87248 --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/GameActivity.java @@ -0,0 +1,56 @@ +package com.panda3ds.pandroid.app; + +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.FrameLayout; +import android.widget.Toast; + +import androidx.annotation.Nullable; + +import com.panda3ds.pandroid.AlberDriver; +import com.panda3ds.pandroid.R; +import com.panda3ds.pandroid.utils.Constants; +import com.panda3ds.pandroid.view.PandaGlSurfaceView; +import com.panda3ds.pandroid.view.PandaLayoutController; +import com.panda3ds.pandroid.view.controller.ControllerLayout; + +public class GameActivity extends BaseActivity { + private PandaGlSurfaceView pandaSurface; + private PandaLayoutController controllerLayout; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Intent intent = getIntent(); + if(!intent.hasExtra(Constants.EXTRA_PATH)){ + + setContentView(new FrameLayout(this)); + Toast.makeText(this, "INVALID ROM PATH", Toast.LENGTH_LONG).show(); + finish(); + return; + } + + pandaSurface = new PandaGlSurfaceView(this, intent.getStringExtra(Constants.EXTRA_PATH)); + + setContentView(R.layout.game_activity); + + ((FrameLayout)findViewById(R.id.panda_gl_frame)) + .addView(pandaSurface, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + + controllerLayout = findViewById(R.id.controller_layout); + controllerLayout.initialize(); + + ((CheckBox)findViewById(R.id.hide_screen_controller)) + .setOnCheckedChangeListener((buttonView, isChecked) -> { + controllerLayout.setVisibility(isChecked ? View.VISIBLE : View.INVISIBLE); + }); + } +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/MainActivity.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/MainActivity.java new file mode 100644 index 000000000..b98bf7236 --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/MainActivity.java @@ -0,0 +1,56 @@ +package com.panda3ds.pandroid.app; + +import static android.provider.Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION; + +import android.content.Intent; +import android.os.Build; +import android.os.Bundle; +import android.os.Environment; +import android.widget.Toast; + +import androidx.appcompat.app.AppCompatActivity; + +import com.panda3ds.pandroid.utils.Constants; +import com.panda3ds.pandroid.R; +import com.panda3ds.pandroid.utils.PathUtils; + +public class MainActivity extends BaseActivity { + private static final int PICK_3DS_ROM = 2; + + private void openFile() { + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("*/*"); + startActivityForResult(intent, PICK_3DS_ROM); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + if (!Environment.isExternalStorageManager()) { + Intent intent = new Intent(ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION); + startActivity(intent); + } + } + + setContentView(R.layout.activity_main); + + findViewById(R.id.load_rom).setOnClickListener(v->{ + openFile(); + }); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == PICK_3DS_ROM) { + if (resultCode == RESULT_OK) { + String path = PathUtils.getPath(getApplicationContext(), data.getData()); + Toast.makeText(getApplicationContext(), "pandroid opening " + path, Toast.LENGTH_LONG).show(); + startActivity(new Intent(this, GameActivity.class) + .putExtra(Constants.EXTRA_PATH, path)); + } + super.onActivityResult(requestCode, resultCode, data); + } + } +} \ No newline at end of file diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/math/Vector2.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/math/Vector2.java new file mode 100644 index 000000000..8d4132032 --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/math/Vector2.java @@ -0,0 +1,23 @@ +package com.panda3ds.pandroid.math; + +public class Vector2 { + public float x,y; + public Vector2(){ + this(0.0f); + } + public Vector2(float value){ + this(value,value); + } + + public Vector2(float x, float y){ + this.x = x; + this.y = y; + } + + public float distanceTo(Vector2 vec){ + return distance(x,y,vec.x, vec.y); + } + public static float distance(float x, float y, float x2, float y2){ + return (float) Math.sqrt((x - x2) * (x - x2) + (y - y2) * (y - y2)); + } +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/Constants.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/Constants.java new file mode 100644 index 000000000..c8ba68cd7 --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/Constants.java @@ -0,0 +1,23 @@ +package com.panda3ds.pandroid.utils; + +public class Constants { + + public static final int INPUT_KEY_UP = 1 << 6; + public static final int INPUT_KEY_DOWN = 1 << 7; + public static final int INPUT_KEY_LEFT = 1 << 5; + public static final int INPUT_KEY_RIGHT = 1 << 4; + + public static final int INPUT_KEY_A = 1 << 0; + public static final int INPUT_KEY_B = 1 << 1; + public static final int INPUT_KEY_X = 1 << 10; + public static final int INPUT_KEY_Y = 1 << 11; + + public static final int INPUT_KEY_R = 1 << 8; + public static final int INPUT_KEY_L = 1 << 9; + + public static final int INPUT_KEY_START = 1 << 3; + public static final int INPUT_KEY_SELECT = 1 << 2; + + public static final String EXTRA_PATH = "path"; + public static final String LOG_TAG = "Alber"; +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/PathUtils.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/PathUtils.java new file mode 100644 index 000000000..0a24603cc --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/PathUtils.java @@ -0,0 +1,125 @@ +package com.panda3ds.pandroid.utils; + +import android.content.ContentUris; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.os.Build; +import android.os.Environment; +import android.provider.DocumentsContract; +import android.provider.MediaStore; + +public class PathUtils { + + public static String getPath(final Context context, final Uri uri) { + + // DocumentProvider + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && DocumentsContract.isDocumentUri(context, uri)) { + + if (isExternalStorageDocument(uri)) {// ExternalStorageProvider + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + String storageDefinition; + + + if("primary".equalsIgnoreCase(type)){ + + return Environment.getExternalStorageDirectory() + "/" + split[1]; + + } else { + + if(Environment.isExternalStorageRemovable()){ + storageDefinition = "EXTERNAL_STORAGE"; + + } else{ + storageDefinition = "SECONDARY_STORAGE"; + } + + return System.getenv(storageDefinition) + "/" + split[1]; + } + + } else if (isDownloadsDocument(uri)) {// DownloadsProvider + + final String id = DocumentsContract.getDocumentId(uri); + final Uri contentUri = ContentUris.withAppendedId( + Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); + + return getDataColumn(context, contentUri, null, null); + + } else if (isMediaDocument(uri)) {// MediaProvider + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + + Uri contentUri = null; + if ("image".equals(type)) { + contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; + } else if ("video".equals(type)) { + contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; + } else if ("audio".equals(type)) { + contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; + } + + final String selection = "_id=?"; + final String[] selectionArgs = new String[]{ + split[1] + }; + + return getDataColumn(context, contentUri, selection, selectionArgs); + } + + } else if ("content".equalsIgnoreCase(uri.getScheme())) {// MediaStore (and general) + + // Return the remote address + if (isGooglePhotosUri(uri)) + return uri.getLastPathSegment(); + + return getDataColumn(context, uri, null, null); + + } else if ("file".equalsIgnoreCase(uri.getScheme())) {// File + return uri.getPath(); + } + + return null; + } + + public static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) { + + Cursor cursor = null; + final String column = "_data"; + final String[] projection = { + column + }; + + try { + cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null); + if (cursor != null && cursor.moveToFirst()) { + final int column_index = cursor.getColumnIndexOrThrow(column); + return cursor.getString(column_index); + } + } finally { + if (cursor != null) + cursor.close(); + } + return null; + } + + + public static boolean isExternalStorageDocument(Uri uri) { + return "com.android.externalstorage.documents".equals(uri.getAuthority()); + } + + + public static boolean isDownloadsDocument(Uri uri) { + return "com.android.providers.downloads.documents".equals(uri.getAuthority()); + } + + public static boolean isMediaDocument(Uri uri) { + return "com.android.providers.media.documents".equals(uri.getAuthority()); + } + + public static boolean isGooglePhotosUri(Uri uri) { + return "com.google.android.apps.photos.content".equals(uri.getAuthority()); + } +} \ No newline at end of file diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/PandaGlRenderer.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/PandaGlRenderer.java new file mode 100644 index 000000000..4adce5758 --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/PandaGlRenderer.java @@ -0,0 +1,111 @@ +package com.panda3ds.pandroid.view; + +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.opengles.GL10; + +import static android.opengl.GLES32.*; + +import android.content.res.Resources; +import android.opengl.GLSurfaceView; +import android.util.Log; + +import com.panda3ds.pandroid.AlberDriver; + +import java.util.ArrayList; + +public class PandaGlRenderer implements GLSurfaceView.Renderer { + + private final String romPath; + private int screenWidth, screenHeight; + private int screenTexture; + public int screenFbo; + + PandaGlRenderer(String romPath) { + super(); + this.romPath = romPath; + } + + @Override + protected void finalize() throws Throwable { + if (screenTexture != 0) { + glDeleteTextures(1, new int[]{screenTexture}, 0); + } + if (screenFbo != 0) { + glDeleteFramebuffers(1, new int[]{screenFbo}, 0); + } + super.finalize(); + } + + public void onSurfaceCreated(GL10 unused, EGLConfig config) { + Log.i("pandroid", glGetString(GL_EXTENSIONS)); + Log.w("pandroid", glGetString(GL_VERSION)); + screenWidth = Resources.getSystem().getDisplayMetrics().widthPixels; + screenHeight = Resources.getSystem().getDisplayMetrics().heightPixels; + + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + + int[] generateBuffer = new int[1]; + glGenTextures(1, generateBuffer, 0); + screenTexture = generateBuffer[0]; + glBindTexture(GL_TEXTURE_2D, screenTexture); + glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, screenWidth, screenHeight); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glBindTexture(GL_TEXTURE_2D, 0); + + glGenFramebuffers(1, generateBuffer, 0); + screenFbo = generateBuffer[0]; + glBindFramebuffer(GL_FRAMEBUFFER, screenFbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, screenTexture, 0); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + AlberDriver.Initialize(); + AlberDriver.LoadRom(romPath); + } + + public void onDrawFrame(GL10 unused) { + if (AlberDriver.HasRomLoaded()) { + AlberDriver.RunFrame(screenFbo); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + glBindFramebuffer(GL_READ_FRAMEBUFFER, screenFbo); + if (screenWidth > screenHeight) { + int topDisplayWidth = (int) ((screenHeight / 240.0) * 400); + int topDisplayHeight = screenHeight; + + if (topDisplayWidth > (screenWidth*0.7)){ + topDisplayWidth = (int) (screenWidth * 0.7); + topDisplayHeight = (int) ((topDisplayWidth/400.0)*240); + } + + int bottomDisplayHeight = (int) (((screenWidth-topDisplayWidth)/320)*240); + + int topDisplayY = screenHeight-topDisplayHeight; + int bottomDisplayY = screenHeight-bottomDisplayHeight; + + glBlitFramebuffer(0, 240, + 400, 480, + 0, topDisplayY, + topDisplayWidth,topDisplayY+topDisplayHeight, + GL_COLOR_BUFFER_BIT, GL_LINEAR); + + glBlitFramebuffer( + 40, 0, + 360, 240, + topDisplayWidth, bottomDisplayY, + screenWidth,bottomDisplayY+bottomDisplayHeight, + GL_COLOR_BUFFER_BIT, GL_LINEAR); + } else { + int h = (int) ((screenWidth / 400.0) * 480); + glBlitFramebuffer(0, 0, 400, 480, 0, screenHeight - h, screenWidth, screenHeight, GL_COLOR_BUFFER_BIT, GL_LINEAR); + } + } + } + + public void onSurfaceChanged(GL10 unused, int width, int height) { + glViewport(0, 0, width, height); + screenWidth = width; + screenHeight = height; + glDisable(GL_SCISSOR_TEST); + } +} \ No newline at end of file diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/PandaGlSurfaceView.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/PandaGlSurfaceView.java new file mode 100644 index 000000000..358056575 --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/PandaGlSurfaceView.java @@ -0,0 +1,20 @@ +package com.panda3ds.pandroid.view; + +import android.content.Context; +import android.opengl.GLSurfaceView; + +public class PandaGlSurfaceView extends GLSurfaceView { + final PandaGlRenderer renderer; + + public PandaGlSurfaceView(Context context, String romPath) { + super(context); + setEGLContextClientVersion(3); + setDebugFlags(DEBUG_LOG_GL_CALLS); + renderer = new PandaGlRenderer(romPath); + setRenderer(renderer); + } + + public PandaGlRenderer getRenderer() { + return renderer; + } +} \ No newline at end of file diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/PandaLayoutController.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/PandaLayoutController.java new file mode 100644 index 000000000..63f328da0 --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/PandaLayoutController.java @@ -0,0 +1,62 @@ +package com.panda3ds.pandroid.view; + +import android.content.Context; +import android.util.AttributeSet; + +import com.panda3ds.pandroid.AlberDriver; +import com.panda3ds.pandroid.R; +import com.panda3ds.pandroid.utils.Constants; +import com.panda3ds.pandroid.view.controller.ControllerLayout; +import com.panda3ds.pandroid.view.controller.nodes.Button; +import com.panda3ds.pandroid.view.controller.nodes.Joystick; + +public class PandaLayoutController extends ControllerLayout { + public PandaLayoutController(Context context) { + super(context); + } + + public PandaLayoutController(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public PandaLayoutController(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public PandaLayoutController(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + public void initialize(){ + int[] keyButtonList = { + R.id.button_a, Constants.INPUT_KEY_A, + R.id.button_b, Constants.INPUT_KEY_B, + R.id.button_y, Constants.INPUT_KEY_Y, + R.id.button_x, Constants.INPUT_KEY_X, + + R.id.button_left, Constants.INPUT_KEY_LEFT, + R.id.button_right, Constants.INPUT_KEY_RIGHT, + R.id.button_up, Constants.INPUT_KEY_UP, + R.id.button_down, Constants.INPUT_KEY_DOWN, + + R.id.button_start, Constants.INPUT_KEY_START, + R.id.button_select, Constants.INPUT_KEY_SELECT, + + R.id.button_l, Constants.INPUT_KEY_L, + R.id.button_r, Constants.INPUT_KEY_R + }; + + for (int i = 0; i < keyButtonList.length; i+=2){ + final int keyCode = keyButtonList[i+1]; + ((Button)findViewById(keyButtonList[i])).setStateListener((btn, pressed)->{ + if (pressed) AlberDriver.KeyDown(keyCode); + else AlberDriver.KeyUp(keyCode); + }); + } + + ((Joystick)findViewById(R.id.left_analog)) + .setJoystickListener((joystick, axisX, axisY) -> { + AlberDriver.SetCirclepadAxis((int)(axisX*0x9C), (int)(axisY*0x9C)*-1); + }); + } +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/controller/ControllerLayout.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/controller/ControllerLayout.java new file mode 100644 index 000000000..97c8eb120 --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/controller/ControllerLayout.java @@ -0,0 +1,146 @@ +package com.panda3ds.pandroid.view.controller; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.RelativeLayout; + +import com.panda3ds.pandroid.math.Vector2; + +import java.util.ArrayList; +import java.util.HashMap; + +public class ControllerLayout extends RelativeLayout { + + private final HashMap activeTouchEvents = new HashMap<>(); + private final ArrayList controllerNodes = new ArrayList<>(); + + public ControllerLayout(Context context) { + this(context,null); + } + + public ControllerLayout(Context context, AttributeSet attrs) { + this(context, attrs,0); + } + + public ControllerLayout(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr,0 ); + } + + public ControllerLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + public void refreshChildren(){ + controllerNodes.clear(); + populateNodesArray(this, controllerNodes); + } + + private void populateNodesArray(ViewGroup group, ArrayList list){ + for (int i = 0; i < group.getChildCount(); i++){ + View view = group.getChildAt(i); + if(view instanceof ControllerNode){ + list.add((ControllerNode) view); + } else if (view instanceof ViewGroup) { + populateNodesArray((ViewGroup) view, list); + } + } + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + int index = event.getActionIndex(); + + switch (event.getActionMasked()){ + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_POINTER_UP: { + int id = event.getPointerId(index); + processTouch(true, event.getX(index), event.getY(index), id); + }break; + case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_POINTER_DOWN: { + int id = event.getPointerId(index); + processTouch(false, event.getX(index), event.getY(index), id); + }break; + case MotionEvent.ACTION_MOVE: + for (int id = 0; id < event.getPointerCount(); id++){ + processTouch(false, event.getX(id), event.getY(id), id); + } + break; + } + return true; + } + + private void processTouch(boolean up, float x, float y, int index){ + int[] globalPosition = new int[2]; + getLocationInWindow(globalPosition); + + int action = TouchEvent.ACTION_MOVE; + + if ((!activeTouchEvents.containsKey(index))){ + if (up)return; + ControllerNode node = null; + for (ControllerNode item: controllerNodes){ + Vector2 pos = item.getPosition(); + Vector2 size= item.getSize(); + + float cx = (pos.x - globalPosition[0]); + float cy = (pos.y - globalPosition[1]); + if( x > cx && x < cx+size.x && y > cy && y < cy+size.y){ + node = item; + break; + } + } + if (node != null){ + activeTouchEvents.put(index, node); + action = TouchEvent.ACTION_DOWN; + } else { + return; + } + } + + if (up) action = TouchEvent.ACTION_UP; + + ControllerNode node = activeTouchEvents.get(index); + Vector2 pos = node.getPosition(); + pos.x -= globalPosition[0]; + pos.y -= globalPosition[1]; + + x -= pos.x; + y -= pos.y; + + node.onTouch(new TouchEvent(x,y,action)); + + if(up){ + activeTouchEvents.remove(index); + } + } + + @Override + public void onViewAdded(View child) { + super.onViewAdded(child); + refreshChildren(); + } + + @Override + public void onViewRemoved(View child) { + super.onViewRemoved(child); + refreshChildren(); + } + + /*@TODO: Need replace that methods for prevent Android send events directly to children*/ + + @Override + public ArrayList getTouchables() { + return new ArrayList<>(); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + return true; + } + +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/controller/ControllerNode.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/controller/ControllerNode.java new file mode 100644 index 000000000..8b01fa97b --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/controller/ControllerNode.java @@ -0,0 +1,24 @@ +package com.panda3ds.pandroid.view.controller; + + +import android.view.View; + +import androidx.annotation.NonNull; + +import com.panda3ds.pandroid.math.Vector2; + +public interface ControllerNode { + + @NonNull + default Vector2 getPosition(){ + View me = (View) this; + + int[] position = new int[2]; + me.getLocationInWindow(position); + return new Vector2(position[0], position[1]); + } + + @NonNull Vector2 getSize(); + + void onTouch(TouchEvent event); +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/controller/TouchEvent.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/controller/TouchEvent.java new file mode 100644 index 000000000..a804cda1d --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/controller/TouchEvent.java @@ -0,0 +1,28 @@ +package com.panda3ds.pandroid.view.controller; + +public class TouchEvent { + public static final int ACTION_DOWN = 0; + public static final int ACTION_MOVE = 1; + public static final int ACTION_UP = 2; + + private final int action; + private final float x,y; + + public float getX() { + return x; + } + + public float getY() { + return y; + } + + public int getAction() { + return action; + } + + public TouchEvent(float x, float y, int action){ + this.x = x; + this.y = y; + this.action = action; + } +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/controller/listeners/ButtonStateListener.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/controller/listeners/ButtonStateListener.java new file mode 100644 index 000000000..a658cdc03 --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/controller/listeners/ButtonStateListener.java @@ -0,0 +1,7 @@ +package com.panda3ds.pandroid.view.controller.listeners; + +import com.panda3ds.pandroid.view.controller.nodes.Button; + +public interface ButtonStateListener { + void onButtonPressedChange(Button button, boolean pressed); +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/controller/listeners/JoystickListener.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/controller/listeners/JoystickListener.java new file mode 100644 index 000000000..538e2cd82 --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/controller/listeners/JoystickListener.java @@ -0,0 +1,7 @@ +package com.panda3ds.pandroid.view.controller.listeners; + +import com.panda3ds.pandroid.view.controller.nodes.Joystick; + +public interface JoystickListener { + void onJoystickAxisChange(Joystick joystick, float axisX, float axisY); +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/controller/nodes/BasicControllerNode.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/controller/nodes/BasicControllerNode.java new file mode 100644 index 000000000..90d5f5f7c --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/controller/nodes/BasicControllerNode.java @@ -0,0 +1,24 @@ +package com.panda3ds.pandroid.view.controller.nodes; + +import android.content.Context; +import android.util.AttributeSet; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.AppCompatTextView; + +import com.panda3ds.pandroid.view.controller.ControllerNode; + +public abstract class BasicControllerNode extends AppCompatTextView implements ControllerNode { + public BasicControllerNode(@NonNull Context context) { + super(context); + } + + public BasicControllerNode(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public BasicControllerNode(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/controller/nodes/Button.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/controller/nodes/Button.java new file mode 100644 index 000000000..7e8d4e869 --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/controller/nodes/Button.java @@ -0,0 +1,73 @@ +package com.panda3ds.pandroid.view.controller.nodes; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.Canvas; +import android.util.AttributeSet; +import android.view.Gravity; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.AppCompatTextView; + +import com.panda3ds.pandroid.math.Vector2; +import com.panda3ds.pandroid.view.controller.ControllerNode; +import com.panda3ds.pandroid.view.controller.TouchEvent; +import com.panda3ds.pandroid.view.controller.listeners.ButtonStateListener; + +public class Button extends BasicControllerNode { + private boolean pressed = false; + private int width,height; + + private ButtonStateListener stateListener; + + public Button(@NonNull Context context) { + super(context); + init(); + } + + public Button(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + init(); + } + + public Button(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + width = getWidth(); + height = getHeight(); + } + + private void init(){ + setTextAlignment(TEXT_ALIGNMENT_CENTER); + setGravity(Gravity.CENTER); + } + + public void setStateListener(ButtonStateListener stateListener) { + this.stateListener = stateListener; + } + + public boolean isPressed() { + return pressed; + } + + @NonNull + @Override + public Vector2 getSize() { + return new Vector2(width,height); + } + + @Override + public void onTouch(TouchEvent event) { + pressed = event.getAction() != TouchEvent.ACTION_UP; + setAlpha(pressed ? 0.2F : 1.0F); + if (stateListener != null){ + stateListener.onButtonPressedChange(this, pressed); + } + } +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/controller/nodes/Joystick.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/controller/nodes/Joystick.java new file mode 100644 index 000000000..757ec3cba --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/controller/nodes/Joystick.java @@ -0,0 +1,132 @@ +package com.panda3ds.pandroid.view.controller.nodes; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; + +import androidx.annotation.NonNull; +import androidx.appcompat.widget.AppCompatTextView; + +import com.panda3ds.pandroid.math.Vector2; +import com.panda3ds.pandroid.view.controller.ControllerNode; +import com.panda3ds.pandroid.view.controller.TouchEvent; +import com.panda3ds.pandroid.view.controller.listeners.JoystickListener; + + +public class Joystick extends BasicControllerNode implements ControllerNode { + private float stick_x = 0; + private float stick_y = 0; + + private int size_width = 0; + private int size_height= 0; + + private JoystickListener joystickListener; + + public Joystick(Context context) { + this(context,null); + } + + public Joystick(Context context, AttributeSet attrs) { + this(context, attrs,0); + } + + public Joystick(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + + paint.setColor(Color.RED); + invalidate(); + } + + private final Paint paint = new Paint(); + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + } + + @Override + public void onDrawForeground(Canvas canvas) { + size_width = getWidth(); + size_height = getHeight(); + + int analogIconSize = size_width-getPaddingLeft(); + + float middleIconSize = analogIconSize / 2.0F; + float middle = size_width / 2.0F; + + float maxDistance = (middle - middleIconSize) * 0.9F; + + float tx = maxDistance * stick_x; + float ty = maxDistance * stick_y; + + float radius = Vector2.distance(0.0F, 0.0F, Math.abs(tx), Math.abs(ty)); + radius = Math.min(maxDistance, radius); + + double deg = Math.atan2(ty,tx) * (180.0/Math.PI); + float rx = (float) (radius*Math.cos(Math.PI * 2 * deg/360.0)); + float ry = (float) (radius*Math.sin(Math.PI * 2 * deg/360.0)); + + stick_x = Math.max(-1.0f, Math.min(1.0f, stick_x)); + stick_y = Math.max(-1.0f, Math.min(1.0f, stick_y)); + + + float x = middle-middleIconSize+rx; + float y = middle-middleIconSize+ry; + + + Drawable foreground = getForeground(); + if (foreground != null){ + foreground.setBounds((int) x, (int) y, (int) (x+analogIconSize), (int) (y+analogIconSize)); + foreground.draw(canvas); + } else { + canvas.drawOval(x, y, x+analogIconSize,y+analogIconSize,paint); + } + } + + public Vector2 getAxis() { + return new Vector2( + Math.max(-1.0F, Math.min(1.0F, stick_x)), + Math.max(-1.0F, Math.min(1.0F, stick_y)) + ); + } + + public void setJoystickListener(JoystickListener joystickListener) { + this.joystickListener = joystickListener; + } + + @NonNull + @Override + public Vector2 getSize() { + return new Vector2(size_width,size_height); + } + + @Override + public void onTouch(TouchEvent event) { + + float middle = size_width/2.0F; + + float x = event.getX(); + float y = event.getY(); + + x = Math.max(0, Math.min(middle*2, x)); + y = Math.max(0, Math.min(middle*2, y)); + + stick_x = ((x-middle)/middle); + + stick_y = ((y-middle)/middle); + + if (event.getAction() == TouchEvent.ACTION_UP){ + stick_x = 0; + stick_y = 0; + } + + if (joystickListener != null){ + joystickListener.onJoystickAxisChange(this, stick_x, stick_y); + } + + invalidate(); + } +} diff --git a/src/pandroid/app/src/main/jniLibs/.gitignore b/src/pandroid/app/src/main/jniLibs/.gitignore new file mode 100644 index 000000000..0b469178c --- /dev/null +++ b/src/pandroid/app/src/main/jniLibs/.gitignore @@ -0,0 +1,2 @@ +# Prebuilt Alber libraries will be placed in this directory, but we don't want to push them to the repo +libAlber.so \ No newline at end of file diff --git a/src/pandroid/app/src/main/jniLibs/arm64-v8a/.gitkeep b/src/pandroid/app/src/main/jniLibs/arm64-v8a/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/src/pandroid/app/src/main/jniLibs/x86_64/.gitkeep b/src/pandroid/app/src/main/jniLibs/x86_64/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/src/pandroid/app/src/main/res/drawable/ic_launcher_background.xml b/src/pandroid/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 000000000..07d5da9cb --- /dev/null +++ b/src/pandroid/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/pandroid/app/src/main/res/drawable/ic_launcher_foreground.xml b/src/pandroid/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 000000000..2b068d114 --- /dev/null +++ b/src/pandroid/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/pandroid/app/src/main/res/drawable/screen_gamepad_checkbox.xml b/src/pandroid/app/src/main/res/drawable/screen_gamepad_checkbox.xml new file mode 100644 index 000000000..8c61f4c88 --- /dev/null +++ b/src/pandroid/app/src/main/res/drawable/screen_gamepad_checkbox.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/pandroid/app/src/main/res/drawable/screen_gamepad_hide.xml b/src/pandroid/app/src/main/res/drawable/screen_gamepad_hide.xml new file mode 100644 index 000000000..f22e8c9d6 --- /dev/null +++ b/src/pandroid/app/src/main/res/drawable/screen_gamepad_hide.xml @@ -0,0 +1,9 @@ + + + diff --git a/src/pandroid/app/src/main/res/drawable/screen_gamepad_show.xml b/src/pandroid/app/src/main/res/drawable/screen_gamepad_show.xml new file mode 100644 index 000000000..133f2e70b --- /dev/null +++ b/src/pandroid/app/src/main/res/drawable/screen_gamepad_show.xml @@ -0,0 +1,5 @@ + + + diff --git a/src/pandroid/app/src/main/res/drawable/simple_analog_background.xml b/src/pandroid/app/src/main/res/drawable/simple_analog_background.xml new file mode 100644 index 000000000..81855e147 --- /dev/null +++ b/src/pandroid/app/src/main/res/drawable/simple_analog_background.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/pandroid/app/src/main/res/drawable/simple_card_button.xml b/src/pandroid/app/src/main/res/drawable/simple_card_button.xml new file mode 100644 index 000000000..d58e9c4fb --- /dev/null +++ b/src/pandroid/app/src/main/res/drawable/simple_card_button.xml @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/pandroid/app/src/main/res/drawable/simple_card_button_left.xml b/src/pandroid/app/src/main/res/drawable/simple_card_button_left.xml new file mode 100644 index 000000000..baf1f2931 --- /dev/null +++ b/src/pandroid/app/src/main/res/drawable/simple_card_button_left.xml @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/pandroid/app/src/main/res/drawable/simple_card_button_right.xml b/src/pandroid/app/src/main/res/drawable/simple_card_button_right.xml new file mode 100644 index 000000000..2f69341c2 --- /dev/null +++ b/src/pandroid/app/src/main/res/drawable/simple_card_button_right.xml @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/pandroid/app/src/main/res/drawable/simple_circle_button.xml b/src/pandroid/app/src/main/res/drawable/simple_circle_button.xml new file mode 100644 index 000000000..15879540b --- /dev/null +++ b/src/pandroid/app/src/main/res/drawable/simple_circle_button.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/pandroid/app/src/main/res/layout/activity_main.xml b/src/pandroid/app/src/main/res/layout/activity_main.xml new file mode 100644 index 000000000..89a17ce98 --- /dev/null +++ b/src/pandroid/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,22 @@ + + + + +