From 5e37b7f08309b34343540570e8eebc26330c3f23 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Wed, 25 Oct 2023 13:17:43 +0300 Subject: [PATCH 001/162] Test Vulkan CI fix (#329) * Hopefully find a fix to the Vulkan CI issue * Update HTTP_Build.yml * Update Hydra_Build.yml * Update Linux_Build.yml * Update MacOS_Build.yml * Update Qt_Build.yml * Update HTTP_Build.yml * Update Hydra_Build.yml * Update HTTP_Build.yml * Update Hydra_Build.yml * Update Linux_Build.yml --- .github/workflows/HTTP_Build.yml | 8 +++++++- .github/workflows/Hydra_Build.yml | 14 ++++++++++---- .github/workflows/Linux_Build.yml | 8 +++++++- .github/workflows/MacOS_Build.yml | 2 +- .github/workflows/Qt_Build.yml | 4 ++-- .github/workflows/Windows_Build.yml | 2 +- 6 files changed, 28 insertions(+), 10 deletions(-) diff --git a/.github/workflows/HTTP_Build.yml b/.github/workflows/HTTP_Build.yml index 24cd19bc..f80d16db 100644 --- a/.github/workflows/HTTP_Build.yml +++ b/.github/workflows/HTTP_Build.yml @@ -22,13 +22,19 @@ jobs: - uses: actions/checkout@v2 - name: Fetch submodules run: git submodule update --init --recursive + + - name: Install newer Clang + run: | + wget https://apt.llvm.org/llvm.sh + chmod +x ./llvm.sh + sudo ./llvm.sh 16 - 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, Glslang + vulkan-components: Vulkan-Headers, Vulkan-Loader, SPIRV-Tools, Glslang - name: Configure CMake # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. diff --git a/.github/workflows/Hydra_Build.yml b/.github/workflows/Hydra_Build.yml index 18194059..25c4f458 100644 --- a/.github/workflows/Hydra_Build.yml +++ b/.github/workflows/Hydra_Build.yml @@ -24,7 +24,7 @@ jobs: with: vulkan-query-version: latest vulkan-use-cache: true - vulkan-components: Vulkan-Headers, Vulkan-Loader, Glslang + vulkan-components: Vulkan-Headers, Vulkan-Loader, SPIRV-Tools, Glslang - name: Configure CMake run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DENABLE_USER_BUILD=ON -DBUILD_HYDRA_CORE=ON @@ -52,7 +52,7 @@ jobs: with: vulkan-query-version: latest vulkan-use-cache: true - vulkan-components: Vulkan-Headers, Vulkan-Loader, Glslang + vulkan-components: Vulkan-Headers, Vulkan-Loader, SPIRV-Tools, Glslang - name: Configure CMake run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DENABLE_USER_BUILD=ON -DBUILD_HYDRA_CORE=ON @@ -77,13 +77,19 @@ jobs: - name: Install misc packages run: | sudo apt-get update && sudo apt install libx11-dev libgl1-mesa-glx mesa-common-dev libfuse2 libwayland-dev + + - name: Install newer Clang + run: | + wget https://apt.llvm.org/llvm.sh + chmod +x ./llvm.sh + sudo ./llvm.sh 16 - 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, Glslang + vulkan-components: Vulkan-Headers, Vulkan-Loader, SPIRV-Tools, Glslang - name: Configure CMake run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DENABLE_USER_BUILD=ON -DBUILD_HYDRA_CORE=ON @@ -114,7 +120,7 @@ jobs: with: vulkan-query-version: latest vulkan-use-cache: true - vulkan-components: Vulkan-Headers, Vulkan-Loader, Glslang + vulkan-components: Vulkan-Headers, Vulkan-Loader, SPIRV-Tools, Glslang - name: Configure CMake run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_TOOLCHAIN_FILE=${ANDROID_NDK_ROOT}/build/cmake/android.toolchain.cmake -DANDROID_ABI=x86_64 -DBUILD_HYDRA_CORE=1 -DENABLE_VULKAN=0 diff --git a/.github/workflows/Linux_Build.yml b/.github/workflows/Linux_Build.yml index bbc79d81..0dc42673 100644 --- a/.github/workflows/Linux_Build.yml +++ b/.github/workflows/Linux_Build.yml @@ -26,12 +26,18 @@ jobs: - name: Install misc packages run: sudo apt-get update && sudo apt install libx11-dev libgl1-mesa-glx mesa-common-dev + - name: Install newer Clang + run: | + wget https://apt.llvm.org/llvm.sh + chmod +x ./llvm.sh + sudo ./llvm.sh 16 + - 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, Glslang + vulkan-components: Vulkan-Headers, Vulkan-Loader, SPIRV-Tools, Glslang - name: Configure CMake # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. diff --git a/.github/workflows/MacOS_Build.yml b/.github/workflows/MacOS_Build.yml index 7e54c5a5..b659e3fa 100644 --- a/.github/workflows/MacOS_Build.yml +++ b/.github/workflows/MacOS_Build.yml @@ -28,7 +28,7 @@ jobs: with: vulkan-query-version: latest vulkan-use-cache: true - vulkan-components: Vulkan-Headers, Vulkan-Loader, Glslang + vulkan-components: Vulkan-Headers, Vulkan-Loader, SPIRV-Tools, Glslang - name: Configure CMake # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. diff --git a/.github/workflows/Qt_Build.yml b/.github/workflows/Qt_Build.yml index 0cb007f4..f824d0aa 100644 --- a/.github/workflows/Qt_Build.yml +++ b/.github/workflows/Qt_Build.yml @@ -30,7 +30,7 @@ jobs: with: vulkan-query-version: latest vulkan-use-cache: true - vulkan-components: Vulkan-Headers, Vulkan-Loader, Glslang + vulkan-components: Vulkan-Headers, Vulkan-Loader, SPIRV-Tools, Glslang - name: Configure CMake run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DENABLE_USER_BUILD=ON -DENABLE_QT_GUI=ON @@ -63,7 +63,7 @@ jobs: with: vulkan-query-version: latest vulkan-use-cache: true - vulkan-components: Vulkan-Headers, Vulkan-Loader, Glslang + vulkan-components: Vulkan-Headers, Vulkan-Loader, SPIRV-Tools, Glslang - name: Install bundle dependencies run: | diff --git a/.github/workflows/Windows_Build.yml b/.github/workflows/Windows_Build.yml index 653692f3..ae9fd587 100644 --- a/.github/workflows/Windows_Build.yml +++ b/.github/workflows/Windows_Build.yml @@ -28,7 +28,7 @@ jobs: with: vulkan-query-version: latest vulkan-use-cache: true - vulkan-components: Vulkan-Headers, Vulkan-Loader, Glslang + vulkan-components: Vulkan-Headers, Vulkan-Loader, SPIRV-Tools, Glslang - name: Configure CMake # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. From e35251ac748974db1760d29161306d36d830dc92 Mon Sep 17 00:00:00 2001 From: offtkp Date: Tue, 24 Oct 2023 15:50:10 +0300 Subject: [PATCH 002/162] Minor hydra gl interface fixup --- src/hydra_core.cpp | 12 +++++++----- third_party/hydra_core | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/hydra_core.cpp b/src/hydra_core.cpp index 3c809dc3..25da6753 100644 --- a/src/hydra_core.cpp +++ b/src/hydra_core.cpp @@ -18,8 +18,8 @@ class HC_GLOBAL HydraCore final : public hydra::IBase, public hydra::IOpenGlRend void setOutputSize(hydra::Size size) override; // IOpenGlRendered + void resetContext() override; void setFbo(unsigned handle) override; - void setContext(void* context) override; void setGetProcAddress(void* function) override; // IFrontendDriven @@ -34,6 +34,7 @@ class HC_GLOBAL HydraCore final : public hydra::IBase, public hydra::IOpenGlRend RendererGL* renderer; void (*pollInputCallback)() = nullptr; int32_t (*checkButtonCallback)(uint32_t player, hydra::ButtonType button) = nullptr; + void* getProcAddress = nullptr; }; HydraCore::HydraCore() : emulator(new Emulator) { @@ -100,13 +101,13 @@ hydra::Size HydraCore::getNativeSize() { return {400, 480}; } // Size doesn't matter as the glBlitFramebuffer call is commented out for the core void HydraCore::setOutputSize(hydra::Size size) {} -void HydraCore::setGetProcAddress(void* function) { +void HydraCore::resetContext() { #ifdef __ANDROID__ - if (!gladLoadGLES2Loader(reinterpret_cast(function))) { + if (!gladLoadGLES2Loader(reinterpret_cast(getProcAddress))) { Helpers::panic("OpenGL ES init failed"); } #else - if (!gladLoadGLLoader(reinterpret_cast(function))) { + if (!gladLoadGLLoader(reinterpret_cast(getProcAddress))) { Helpers::panic("OpenGL init failed"); } #endif @@ -114,9 +115,10 @@ void HydraCore::setGetProcAddress(void* function) { emulator->initGraphicsContext(nullptr); } -void HydraCore::setContext(void*) {} void HydraCore::setFbo(unsigned handle) { renderer->setFBO(handle); } +void HydraCore::setGetProcAddress(void* function) { getProcAddress = function; } + void HydraCore::setPollInputCallback(void (*callback)()) { pollInputCallback = callback; } void HydraCore::setCheckButtonCallback(int32_t (*callback)(uint32_t player, hydra::ButtonType button)) { checkButtonCallback = callback; } diff --git a/third_party/hydra_core b/third_party/hydra_core index e4cc6b0f..28311efe 160000 --- a/third_party/hydra_core +++ b/third_party/hydra_core @@ -1 +1 @@ -Subproject commit e4cc6b0fc224583e509bc3472a4c11eafb69c041 +Subproject commit 28311efe6e96af0b192f07179fe87dadab479041 From 9e30e6a14c6625a2c2f88ffba54d131e9d119fef Mon Sep 17 00:00:00 2001 From: offtkp Date: Tue, 24 Oct 2023 18:34:27 +0300 Subject: [PATCH 003/162] destroyContext function stub --- src/hydra_core.cpp | 3 +++ third_party/hydra_core | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/hydra_core.cpp b/src/hydra_core.cpp index 25da6753..793e3abf 100644 --- a/src/hydra_core.cpp +++ b/src/hydra_core.cpp @@ -19,6 +19,7 @@ class HC_GLOBAL HydraCore final : public hydra::IBase, public hydra::IOpenGlRend // IOpenGlRendered void resetContext() override; + void destroyContext() override; void setFbo(unsigned handle) override; void setGetProcAddress(void* function) override; @@ -115,6 +116,8 @@ void HydraCore::resetContext() { emulator->initGraphicsContext(nullptr); } +void HydraCore::destroyContext() {} + void HydraCore::setFbo(unsigned handle) { renderer->setFBO(handle); } void HydraCore::setGetProcAddress(void* function) { getProcAddress = function; } diff --git a/third_party/hydra_core b/third_party/hydra_core index 28311efe..1cdb1eda 160000 --- a/third_party/hydra_core +++ b/third_party/hydra_core @@ -1 +1 @@ -Subproject commit 28311efe6e96af0b192f07179fe87dadab479041 +Subproject commit 1cdb1eda5f368481e216416a119c85664e8c72ab From 057436edc059513024a9d80f1f2ce9e64dba78e3 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Wed, 25 Oct 2023 15:41:56 +0300 Subject: [PATCH 004/162] Update to Clang 17 on CI (#330) * Update HTTP_Build.yml * Update Hydra_Build.yml * Update Linux_AppImage_Build.yml * Update Linux_Build.yml * Update Qt_Build.yml * Update Qt_Build.yml * Update Linux_AppImage_Build.yml --- .github/workflows/HTTP_Build.yml | 2 +- .github/workflows/Hydra_Build.yml | 2 +- .github/workflows/Linux_AppImage_Build.yml | 4 ++-- .github/workflows/Linux_Build.yml | 2 +- .github/workflows/Qt_Build.yml | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/HTTP_Build.yml b/.github/workflows/HTTP_Build.yml index f80d16db..1b7775aa 100644 --- a/.github/workflows/HTTP_Build.yml +++ b/.github/workflows/HTTP_Build.yml @@ -27,7 +27,7 @@ jobs: run: | wget https://apt.llvm.org/llvm.sh chmod +x ./llvm.sh - sudo ./llvm.sh 16 + sudo ./llvm.sh 17 - name: Setup Vulkan SDK uses: humbletim/setup-vulkan-sdk@v1.2.0 diff --git a/.github/workflows/Hydra_Build.yml b/.github/workflows/Hydra_Build.yml index 25c4f458..0d5778d3 100644 --- a/.github/workflows/Hydra_Build.yml +++ b/.github/workflows/Hydra_Build.yml @@ -82,7 +82,7 @@ jobs: run: | wget https://apt.llvm.org/llvm.sh chmod +x ./llvm.sh - sudo ./llvm.sh 16 + sudo ./llvm.sh 17 - name: Setup Vulkan SDK uses: humbletim/setup-vulkan-sdk@v1.2.0 diff --git a/.github/workflows/Linux_AppImage_Build.yml b/.github/workflows/Linux_AppImage_Build.yml index 0a304109..23aae405 100644 --- a/.github/workflows/Linux_AppImage_Build.yml +++ b/.github/workflows/Linux_AppImage_Build.yml @@ -30,7 +30,7 @@ jobs: run: | wget https://apt.llvm.org/llvm.sh chmod +x ./llvm.sh - sudo ./llvm.sh 16 + sudo ./llvm.sh 17 - name: Install newer CMake run: | @@ -50,7 +50,7 @@ jobs: - name: Configure CMake # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=clang-16 -DCMAKE_CXX_COMPILER=clang++-16 -DENABLE_USER_BUILD=ON + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=clang-17 -DCMAKE_CXX_COMPILER=clang++-17 -DENABLE_USER_BUILD=ON - name: Build # Build your program with the given configuration diff --git a/.github/workflows/Linux_Build.yml b/.github/workflows/Linux_Build.yml index 0dc42673..e43413ef 100644 --- a/.github/workflows/Linux_Build.yml +++ b/.github/workflows/Linux_Build.yml @@ -30,7 +30,7 @@ jobs: run: | wget https://apt.llvm.org/llvm.sh chmod +x ./llvm.sh - sudo ./llvm.sh 16 + sudo ./llvm.sh 17 - name: Setup Vulkan SDK uses: humbletim/setup-vulkan-sdk@v1.2.0 diff --git a/.github/workflows/Qt_Build.yml b/.github/workflows/Qt_Build.yml index f824d0aa..a112e529 100644 --- a/.github/workflows/Qt_Build.yml +++ b/.github/workflows/Qt_Build.yml @@ -114,7 +114,7 @@ jobs: run: | wget https://apt.llvm.org/llvm.sh chmod +x ./llvm.sh - sudo ./llvm.sh 16 + sudo ./llvm.sh 17 - name: Install newer CMake run: | @@ -132,7 +132,7 @@ jobs: sudo apt install vulkan-sdk - name: Configure CMake - run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=clang-16 -DCMAKE_CXX_COMPILER=clang++-16 -DENABLE_USER_BUILD=ON -DENABLE_QT_GUI=ON + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=clang-17 -DCMAKE_CXX_COMPILER=clang++-17 -DENABLE_USER_BUILD=ON -DENABLE_QT_GUI=ON - name: Build run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} From dddb784469e7837cd5a8981686a8809ae32ea80d Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Wed, 25 Oct 2023 20:29:59 +0300 Subject: [PATCH 005/162] Actually upgrade to clang 17 on CI (#331) * Update HTTP_Build.yml * Update Hydra_Build.yml * Update Linux_Build.yml --- .github/workflows/HTTP_Build.yml | 2 +- .github/workflows/Hydra_Build.yml | 2 +- .github/workflows/Linux_Build.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/HTTP_Build.yml b/.github/workflows/HTTP_Build.yml index 1b7775aa..7bfe9c7f 100644 --- a/.github/workflows/HTTP_Build.yml +++ b/.github/workflows/HTTP_Build.yml @@ -39,7 +39,7 @@ jobs: - name: Configure CMake # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DENABLE_USER_BUILD=ON -DENABLE_HTTP_SERVER=ON + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=clang-17 -DCMAKE_CXX_COMPILER=clang++-17 -DENABLE_USER_BUILD=ON -DENABLE_HTTP_SERVER=ON - name: Build # Build your program with the given configuration diff --git a/.github/workflows/Hydra_Build.yml b/.github/workflows/Hydra_Build.yml index 0d5778d3..3387d46d 100644 --- a/.github/workflows/Hydra_Build.yml +++ b/.github/workflows/Hydra_Build.yml @@ -92,7 +92,7 @@ jobs: vulkan-components: Vulkan-Headers, Vulkan-Loader, SPIRV-Tools, Glslang - name: Configure CMake - run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DENABLE_USER_BUILD=ON -DBUILD_HYDRA_CORE=ON + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=clang-17 -DCMAKE_CXX_COMPILER=clang++-17 -DENABLE_USER_BUILD=ON -DBUILD_HYDRA_CORE=ON - name: Build run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} diff --git a/.github/workflows/Linux_Build.yml b/.github/workflows/Linux_Build.yml index e43413ef..78e5cc5a 100644 --- a/.github/workflows/Linux_Build.yml +++ b/.github/workflows/Linux_Build.yml @@ -42,7 +42,7 @@ jobs: - name: Configure CMake # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DENABLE_USER_BUILD=ON + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=clang-17 -DCMAKE_CXX_COMPILER=clang++-17 -DENABLE_USER_BUILD=ON - name: Build # Build your program with the given configuration From 7345e2e2553c96055a0527f11abc9386cf1b7de4 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Wed, 25 Oct 2023 23:09:14 +0300 Subject: [PATCH 006/162] Update hydra_core.cpp --- src/hydra_core.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/hydra_core.cpp b/src/hydra_core.cpp index 793e3abf..6d1a9a1d 100644 --- a/src/hydra_core.cpp +++ b/src/hydra_core.cpp @@ -90,7 +90,6 @@ void HydraCore::runFrame() { } hid.updateInputs(emulator->getTicks()); - emulator->runFrame(); } @@ -116,16 +115,14 @@ void HydraCore::resetContext() { emulator->initGraphicsContext(nullptr); } -void HydraCore::destroyContext() {} - +void HydraCore::destroyContext() { emulator.deinitGraphicsContext(); } void HydraCore::setFbo(unsigned handle) { renderer->setFBO(handle); } - void HydraCore::setGetProcAddress(void* function) { getProcAddress = function; } void HydraCore::setPollInputCallback(void (*callback)()) { pollInputCallback = callback; } void HydraCore::setCheckButtonCallback(int32_t (*callback)(uint32_t player, hydra::ButtonType button)) { checkButtonCallback = callback; } -HC_API hydra::IBase* createEmulator() { return new HydraCore; } +HC_API hydra::IBase* createEmulator() { return new HydraCore(); } HC_API void destroyEmulator(hydra::IBase* emulator) { delete emulator; } HC_API const char* getInfo(hydra::InfoType type) { @@ -145,4 +142,4 @@ HC_API const char* getInfo(hydra::InfoType type) { default: return nullptr; } -} \ No newline at end of file +} From f48359f6304aec1264ae3019b7e11d010f79a374 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Wed, 25 Oct 2023 23:23:31 +0300 Subject: [PATCH 007/162] Fix derp --- src/hydra_core.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hydra_core.cpp b/src/hydra_core.cpp index 6d1a9a1d..3ecf7019 100644 --- a/src/hydra_core.cpp +++ b/src/hydra_core.cpp @@ -115,7 +115,7 @@ void HydraCore::resetContext() { emulator->initGraphicsContext(nullptr); } -void HydraCore::destroyContext() { emulator.deinitGraphicsContext(); } +void HydraCore::destroyContext() { emulator->deinitGraphicsContext(); } void HydraCore::setFbo(unsigned handle) { renderer->setFBO(handle); } void HydraCore::setGetProcAddress(void* function) { getProcAddress = function; } From d4a84c337e8afd4f49d3b125c78c3918cc537560 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Thu, 26 Oct 2023 18:09:01 +0300 Subject: [PATCH 008/162] Fix forward declaration of SDL_Window --- include/emulator.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/emulator.hpp b/include/emulator.hpp index 2b96b0cf..121c6b8d 100644 --- a/include/emulator.hpp +++ b/include/emulator.hpp @@ -23,7 +23,7 @@ #include "gl/context.h" #endif -class SDL_Window; +struct SDL_Window; enum class ROMType { None, From 7e7d4f7f1611cbd3bbcef2ddf07a425142785308 Mon Sep 17 00:00:00 2001 From: offtkp Date: Tue, 24 Oct 2023 16:52:08 +0300 Subject: [PATCH 009/162] Make cheats struct more versatile --- include/cheats.hpp | 7 ++++++- include/emulator.hpp | 1 + src/core/cheats.cpp | 42 ++++++++++++++++++++++++++++++++++++++++-- src/hydra_core.cpp | 37 +++++++++++++++++++++++++++++++++++-- 4 files changed, 82 insertions(+), 5 deletions(-) diff --git a/include/cheats.hpp b/include/cheats.hpp index c8d7c763..d995a883 100644 --- a/include/cheats.hpp +++ b/include/cheats.hpp @@ -12,17 +12,22 @@ class Memory; class Cheats { public: enum class CheatType { + None, ActionReplay, // CTRPF cheats // TODO: Other cheat devices and standards? }; struct Cheat { + bool enabled; CheatType type; std::vector instructions; }; Cheats(Memory& mem, HIDService& hid); - void addCheat(const Cheat& cheat); + uint32_t addCheat(const Cheat& cheat); + void removeCheat(uint32_t id); + void enableCheat(uint32_t id); + void disableCheat(uint32_t id); void reset(); void run(); diff --git a/include/emulator.hpp b/include/emulator.hpp index 121c6b8d..df930373 100644 --- a/include/emulator.hpp +++ b/include/emulator.hpp @@ -115,6 +115,7 @@ class Emulator { void deinitGraphicsContext() { gpu.deinitGraphicsContext(); } EmulatorConfig& getConfig() { return config; } + Cheats& getCheats() { return cheats; } ServiceManager& getServiceManager() { return kernel.getServiceManager(); } RendererType getRendererType() const { return config.rendererType; } Renderer* getRenderer() { return gpu.getRenderer(); } diff --git a/src/core/cheats.cpp b/src/core/cheats.cpp index 618460c5..6841cdce 100644 --- a/src/core/cheats.cpp +++ b/src/core/cheats.cpp @@ -7,9 +7,45 @@ void Cheats::reset() { ar.reset(); // Reset ActionReplay } -void Cheats::addCheat(const Cheat& cheat) { - cheats.push_back(cheat); +uint32_t Cheats::addCheat(const Cheat& cheat) { cheatsLoaded = true; + + // Find an empty slot if a cheat was previously removed + for (size_t i = 0; i < cheats.size(); i++) { + if (cheats[i].type == CheatType::None) { + cheats[i] = cheat; + return i; + } + } + + // Otherwise, just add a new slot + cheats.push_back(cheat); + return cheats.size() - 1; +} + +void Cheats::removeCheat(uint32_t id) { + if (id >= cheats.size()) return; + + // Not using std::erase because we don't want to invalidate cheat IDs + cheats[id].type = CheatType::None; + cheats[id].instructions.clear(); + + // Check if no cheats are loaded + for (const auto& cheat : cheats) { + if (cheat.type != CheatType::None) return; + } + + cheatsLoaded = false; +} + +void Cheats::enableCheat(uint32_t id) { + if (id >= cheats.size()) return; + cheats[id].enabled = true; +} + +void Cheats::disableCheat(uint32_t id) { + if (id >= cheats.size()) return; + cheats[id].enabled = false; } void Cheats::clear() { @@ -19,6 +55,8 @@ void Cheats::clear() { void Cheats::run() { for (const Cheat& cheat : cheats) { + if (!cheat.enabled) continue; + switch (cheat.type) { case CheatType::ActionReplay: { ar.runCheat(cheat.instructions); diff --git a/src/hydra_core.cpp b/src/hydra_core.cpp index 3ecf7019..1c711a02 100644 --- a/src/hydra_core.cpp +++ b/src/hydra_core.cpp @@ -4,8 +4,13 @@ #include #include "hydra_icon.hpp" +#include "swap.hpp" -class HC_GLOBAL HydraCore final : public hydra::IBase, public hydra::IOpenGlRendered, public hydra::IFrontendDriven, public hydra::IInput { +class HC_GLOBAL HydraCore final : public hydra::IBase, + public hydra::IOpenGlRendered, + public hydra::IFrontendDriven, + public hydra::IInput, + public hydra::ICheat { HYDRA_CLASS public: HydraCore(); @@ -31,6 +36,12 @@ class HC_GLOBAL HydraCore final : public hydra::IBase, public hydra::IOpenGlRend void setPollInputCallback(void (*callback)()) override; void setCheckButtonCallback(int32_t (*callback)(uint32_t player, hydra::ButtonType button)) override; + // ICheat + uint32_t addCheat(const uint8_t* data, uint32_t size) override; + void removeCheat(uint32_t id) override; + void enableCheat(uint32_t id) override; + void disableCheat(uint32_t id) override; + std::unique_ptr emulator; RendererGL* renderer; void (*pollInputCallback)() = nullptr; @@ -122,7 +133,29 @@ void HydraCore::setGetProcAddress(void* function) { getProcAddress = function; } void HydraCore::setPollInputCallback(void (*callback)()) { pollInputCallback = callback; } void HydraCore::setCheckButtonCallback(int32_t (*callback)(uint32_t player, hydra::ButtonType button)) { checkButtonCallback = callback; } -HC_API hydra::IBase* createEmulator() { return new HydraCore(); } +uint32_t HydraCore::addCheat(const uint8_t* data, uint32_t size) { + if ((size % 8) != 0) return hydra::BAD_CHEAT; + + Cheats::Cheat cheat; + cheat.enabled = true; + cheat.type = Cheats::CheatType::ActionReplay; + + for (uint32_t i = 0; i < size; i += 8) { + uint32_t first_word = Common::swap32(*reinterpret_cast(data + i)); + uint32_t second_word = Common::swap32(*reinterpret_cast(data + i + 4)); + cheat.instructions.insert(cheat.instructions.end(), {first_word, second_word}); + } + + return emulator->getCheats().addCheat(cheat); +}; + +void HydraCore::removeCheat(uint32_t id) { emulator->getCheats().removeCheat(id); } + +void HydraCore::enableCheat(uint32_t id) { emulator->getCheats().enableCheat(id); } + +void HydraCore::disableCheat(uint32_t id) { emulator->getCheats().disableCheat(id); } + +HC_API hydra::IBase* createEmulator() { return new HydraCore; } HC_API void destroyEmulator(hydra::IBase* emulator) { delete emulator; } HC_API const char* getInfo(hydra::InfoType type) { From 8502cd92854985b3c52f6ff520153a5bdf8fbfd9 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Tue, 24 Oct 2023 22:07:45 +0300 Subject: [PATCH 010/162] Moar cheats --- include/cheats.hpp | 8 ++++---- src/core/cheats.cpp | 23 ++++++++++++++--------- src/hydra_core.cpp | 44 +++++++++++++++++++++++++------------------- 3 files changed, 43 insertions(+), 32 deletions(-) diff --git a/include/cheats.hpp b/include/cheats.hpp index d995a883..1ffa83b7 100644 --- a/include/cheats.hpp +++ b/include/cheats.hpp @@ -24,10 +24,10 @@ class Cheats { }; Cheats(Memory& mem, HIDService& hid); - uint32_t addCheat(const Cheat& cheat); - void removeCheat(uint32_t id); - void enableCheat(uint32_t id); - void disableCheat(uint32_t id); + u32 addCheat(const Cheat& cheat); + void removeCheat(u32 id); + void enableCheat(u32 id); + void disableCheat(u32 id); void reset(); void run(); diff --git a/src/core/cheats.cpp b/src/core/cheats.cpp index 6841cdce..c359d432 100644 --- a/src/core/cheats.cpp +++ b/src/core/cheats.cpp @@ -7,7 +7,7 @@ void Cheats::reset() { ar.reset(); // Reset ActionReplay } -uint32_t Cheats::addCheat(const Cheat& cheat) { +u32 Cheats::addCheat(const Cheat& cheat) { cheatsLoaded = true; // Find an empty slot if a cheat was previously removed @@ -23,8 +23,10 @@ uint32_t Cheats::addCheat(const Cheat& cheat) { return cheats.size() - 1; } -void Cheats::removeCheat(uint32_t id) { - if (id >= cheats.size()) return; +void Cheats::removeCheat(u32 id) { + if (id >= cheats.size()) { + return; + } // Not using std::erase because we don't want to invalidate cheat IDs cheats[id].type = CheatType::None; @@ -38,14 +40,16 @@ void Cheats::removeCheat(uint32_t id) { cheatsLoaded = false; } -void Cheats::enableCheat(uint32_t id) { - if (id >= cheats.size()) return; - cheats[id].enabled = true; +void Cheats::enableCheat(u32 id) { + if (id < cheats.size()) { + cheats[id].enabled = true; + } } -void Cheats::disableCheat(uint32_t id) { - if (id >= cheats.size()) return; - cheats[id].enabled = false; +void Cheats::disableCheat(u32 id) { + if (id < cheats.size()) { + cheats[id].enabled = false; + } } void Cheats::clear() { @@ -63,6 +67,7 @@ void Cheats::run() { break; } + case CheatType::None: break; default: Helpers::panic("Unknown cheat device!"); } } diff --git a/src/hydra_core.cpp b/src/hydra_core.cpp index 1c711a02..25cecafc 100644 --- a/src/hydra_core.cpp +++ b/src/hydra_core.cpp @@ -30,17 +30,17 @@ class HC_GLOBAL HydraCore final : public hydra::IBase, // IFrontendDriven void runFrame() override; - uint16_t getFps() override; + u16 getFps() override; // IInput void setPollInputCallback(void (*callback)()) override; - void setCheckButtonCallback(int32_t (*callback)(uint32_t player, hydra::ButtonType button)) override; + void setCheckButtonCallback(s32 (*callback)(u32 player, hydra::ButtonType button)) override; // ICheat - uint32_t addCheat(const uint8_t* data, uint32_t size) override; - void removeCheat(uint32_t id) override; - void enableCheat(uint32_t id) override; - void disableCheat(uint32_t id) override; + u32 addCheat(const u8* data, u32 size) override; + void removeCheat(u32 id) override; + void enableCheat(u32 id) override; + void disableCheat(u32 id) override; std::unique_ptr emulator; RendererGL* renderer; @@ -104,7 +104,7 @@ void HydraCore::runFrame() { emulator->runFrame(); } -uint16_t HydraCore::getFps() { return 60; } +u16 HydraCore::getFps() { return 60; } void HydraCore::reset() { emulator->reset(Emulator::ReloadOption::Reload); } hydra::Size HydraCore::getNativeSize() { return {400, 480}; } @@ -131,29 +131,35 @@ void HydraCore::setFbo(unsigned handle) { renderer->setFBO(handle); } void HydraCore::setGetProcAddress(void* function) { getProcAddress = function; } void HydraCore::setPollInputCallback(void (*callback)()) { pollInputCallback = callback; } -void HydraCore::setCheckButtonCallback(int32_t (*callback)(uint32_t player, hydra::ButtonType button)) { checkButtonCallback = callback; } +void HydraCore::setCheckButtonCallback(s32 (*callback)(u32 player, hydra::ButtonType button)) { checkButtonCallback = callback; } -uint32_t HydraCore::addCheat(const uint8_t* data, uint32_t size) { - if ((size % 8) != 0) return hydra::BAD_CHEAT; +u32 HydraCore::addCheat(const u8* data, u32 size) { + // Every 3DS cheat is a multiple of 64 bits == 8 bytes + if ((size % 8) != 0) { + return hydra::BAD_CHEAT; + } Cheats::Cheat cheat; cheat.enabled = true; cheat.type = Cheats::CheatType::ActionReplay; - for (uint32_t i = 0; i < size; i += 8) { - uint32_t first_word = Common::swap32(*reinterpret_cast(data + i)); - uint32_t second_word = Common::swap32(*reinterpret_cast(data + i + 4)); - cheat.instructions.insert(cheat.instructions.end(), {first_word, second_word}); + for (u32 i = 0; i < size; i += 8) { + auto read32 = [](const u8* ptr) { + return (u32(ptr[3]) << 24) | (u32(ptr[2]) << 16) | (u32(ptr[1]) << 8) | u32(ptr[0]); + }; + + // Data is passed to us in big endian so we bswap + u32 firstWord = Common::swap32(read32(data + i)); + u32 secondWord = Common::swap32(read32(data + i + 4)); + cheat.instructions.insert(cheat.instructions.end(), {firstWord, secondWord}); } return emulator->getCheats().addCheat(cheat); }; -void HydraCore::removeCheat(uint32_t id) { emulator->getCheats().removeCheat(id); } - -void HydraCore::enableCheat(uint32_t id) { emulator->getCheats().enableCheat(id); } - -void HydraCore::disableCheat(uint32_t id) { emulator->getCheats().disableCheat(id); } +void HydraCore::removeCheat(u32 id) { emulator->getCheats().removeCheat(id); } +void HydraCore::enableCheat(u32 id) { emulator->getCheats().enableCheat(id); } +void HydraCore::disableCheat(u32 id) { emulator->getCheats().disableCheat(id); } HC_API hydra::IBase* createEmulator() { return new HydraCore; } HC_API void destroyEmulator(hydra::IBase* emulator) { delete emulator; } From f9228707854b71958d6804de63a7960757d18304 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Tue, 24 Oct 2023 22:10:40 +0300 Subject: [PATCH 011/162] format --- include/cheats.hpp | 3 +-- src/core/cheats.cpp | 14 +++++++------- src/hydra_core.cpp | 12 +++++------- 3 files changed, 13 insertions(+), 16 deletions(-) diff --git a/include/cheats.hpp b/include/cheats.hpp index 1ffa83b7..59e88e7a 100644 --- a/include/cheats.hpp +++ b/include/cheats.hpp @@ -12,9 +12,8 @@ class Memory; class Cheats { public: enum class CheatType { - None, + None, // Cheat has been removed by the frontend or is invalid ActionReplay, // CTRPF cheats - // TODO: Other cheat devices and standards? }; struct Cheat { diff --git a/src/core/cheats.cpp b/src/core/cheats.cpp index c359d432..83e7cdc4 100644 --- a/src/core/cheats.cpp +++ b/src/core/cheats.cpp @@ -25,8 +25,8 @@ u32 Cheats::addCheat(const Cheat& cheat) { void Cheats::removeCheat(u32 id) { if (id >= cheats.size()) { - return; - } + return; + } // Not using std::erase because we don't want to invalidate cheat IDs cheats[id].type = CheatType::None; @@ -42,14 +42,14 @@ void Cheats::removeCheat(u32 id) { void Cheats::enableCheat(u32 id) { if (id < cheats.size()) { - cheats[id].enabled = true; - } + cheats[id].enabled = true; + } } void Cheats::disableCheat(u32 id) { if (id < cheats.size()) { - cheats[id].enabled = false; - } + cheats[id].enabled = false; + } } void Cheats::clear() { @@ -67,7 +67,7 @@ void Cheats::run() { break; } - case CheatType::None: break; + case CheatType::None: break; default: Helpers::panic("Unknown cheat device!"); } } diff --git a/src/hydra_core.cpp b/src/hydra_core.cpp index 25cecafc..b7c084e2 100644 --- a/src/hydra_core.cpp +++ b/src/hydra_core.cpp @@ -135,20 +135,18 @@ void HydraCore::setCheckButtonCallback(s32 (*callback)(u32 player, hydra::Button u32 HydraCore::addCheat(const u8* data, u32 size) { // Every 3DS cheat is a multiple of 64 bits == 8 bytes - if ((size % 8) != 0) { - return hydra::BAD_CHEAT; - } + if ((size % 8) != 0) { + return hydra::BAD_CHEAT; + } Cheats::Cheat cheat; cheat.enabled = true; cheat.type = Cheats::CheatType::ActionReplay; for (u32 i = 0; i < size; i += 8) { - auto read32 = [](const u8* ptr) { - return (u32(ptr[3]) << 24) | (u32(ptr[2]) << 16) | (u32(ptr[1]) << 8) | u32(ptr[0]); - }; + auto read32 = [](const u8* ptr) { return (u32(ptr[3]) << 24) | (u32(ptr[2]) << 16) | (u32(ptr[1]) << 8) | u32(ptr[0]); }; - // Data is passed to us in big endian so we bswap + // Data is passed to us in big endian so we bswap u32 firstWord = Common::swap32(read32(data + i)); u32 secondWord = Common::swap32(read32(data + i + 4)); cheat.instructions.insert(cheat.instructions.end(), {firstWord, secondWord}); From 1c501aceef28e306ca11f40a2513785e561f5bba Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Thu, 26 Oct 2023 12:12:05 +0300 Subject: [PATCH 012/162] Cheats: Default to enabled + CTRPF --- include/cheats.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/cheats.hpp b/include/cheats.hpp index 59e88e7a..2be25827 100644 --- a/include/cheats.hpp +++ b/include/cheats.hpp @@ -17,8 +17,8 @@ class Cheats { }; struct Cheat { - bool enabled; - CheatType type; + bool enabled = true; + CheatType type = CheatType::ActionReplay; std::vector instructions; }; @@ -37,4 +37,4 @@ class Cheats { ActionReplay ar; // An ActionReplay cheat machine for executing CTRPF codes std::vector cheats; bool cheatsLoaded = false; -}; \ No newline at end of file +}; From 8b6008e1964db5cf65221308be8667b525c217c4 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Mon, 30 Oct 2023 00:15:13 +0200 Subject: [PATCH 013/162] Fix new syntax --- src/hydra_core.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hydra_core.cpp b/src/hydra_core.cpp index b7c084e2..d67ffe2f 100644 --- a/src/hydra_core.cpp +++ b/src/hydra_core.cpp @@ -159,7 +159,7 @@ void HydraCore::removeCheat(u32 id) { emulator->getCheats().removeCheat(id); } void HydraCore::enableCheat(u32 id) { emulator->getCheats().enableCheat(id); } void HydraCore::disableCheat(u32 id) { emulator->getCheats().disableCheat(id); } -HC_API hydra::IBase* createEmulator() { return new HydraCore; } +HC_API hydra::IBase* createEmulator() { return new HydraCore(); } HC_API void destroyEmulator(hydra::IBase* emulator) { delete emulator; } HC_API const char* getInfo(hydra::InfoType type) { From 8d5485fbeb60023e9c0c8823d3603ee032cd3103 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Mon, 30 Oct 2023 20:20:47 +0200 Subject: [PATCH 014/162] [Qt] Add proper message queue for thread communication --- include/panda_qt/main_window.hpp | 31 ++++++++++++++++--- src/panda_qt/main_window.cpp | 51 +++++++++++++++++++++----------- 2 files changed, 60 insertions(+), 22 deletions(-) diff --git a/include/panda_qt/main_window.hpp b/include/panda_qt/main_window.hpp index b5b93d56..0187b424 100644 --- a/include/panda_qt/main_window.hpp +++ b/include/panda_qt/main_window.hpp @@ -9,9 +9,11 @@ #include #include #include +#include #include "emulator.hpp" #include "panda_qt/screen.hpp" +#include "services/hid.hpp" class MainWindow : public QMainWindow { Q_OBJECT @@ -23,15 +25,34 @@ class MainWindow : public QMainWindow { Dark = 2, }; + // Types of messages we might send from the GUI thread to the emulator thread + enum class MessageType { + LoadROM, Reset, Pause, Resume, TogglePause, DumpRomFS, PressKey, ReleaseKey + }; + + // Tagged union representing our message queue messages + struct EmulatorMessage { + MessageType type; + + union { + struct { + std::filesystem::path* p; + } path; + + struct { + u32 key; + } key; + }; + }; + // This would normally be an std::unique_ptr but it's shared between threads so definitely not Emulator* emu = nullptr; std::thread emuThread; std::atomic appRunning = true; // Is the application itself running? - std::mutex messageQueueMutex; // Used for synchronizing messages between the emulator and UI - std::filesystem::path romToLoad = ""; - - bool needToLoadROM = false; + // Used for synchronizing messages between the emulator and UI + std::mutex messageQueueMutex; + std::vector messageQueue; ScreenWidget screen; QComboBox* themeSelect = nullptr; @@ -43,6 +64,8 @@ class MainWindow : public QMainWindow { void emuThreadMainLoop(); void selectROM(); void dumpRomFS(); + void sendMessage(const EmulatorMessage& message); + void dispatchMessage(const EmulatorMessage& message); // Tracks whether we are using an OpenGL-backed renderer or a Vulkan-backed renderer bool usingGL = false; diff --git a/src/panda_qt/main_window.cpp b/src/panda_qt/main_window.cpp index 2c2cc64f..8ffaf7b6 100644 --- a/src/panda_qt/main_window.cpp +++ b/src/panda_qt/main_window.cpp @@ -73,13 +73,13 @@ void MainWindow::emuThreadMainLoop() { { std::unique_lock lock(messageQueueMutex); - if (needToLoadROM) { - needToLoadROM = false; - - bool success = emu->loadROM(romToLoad); - if (!success) { - printf("Failed to load ROM"); + // Dispatch all messages in the message queue + if (!messageQueue.empty()) { + for (const auto& msg : messageQueue) { + dispatchMessage(msg); } + + messageQueue.clear(); } } @@ -102,23 +102,15 @@ void MainWindow::swapEmuBuffer() { } void MainWindow::selectROM() { - // Are we already waiting for a ROM to be loaded? Then complain about it! - { - std::unique_lock lock(messageQueueMutex); - if (needToLoadROM) { - QMessageBox::warning(this, tr("Already loading ROM"), tr("Panda3DS is already busy loading a ROM, please wait")); - return; - } - } - auto path = QFileDialog::getOpenFileName(this, tr("Select 3DS ROM to load"), "", tr("Nintendo 3DS ROMs (*.3ds *.cci *.cxi *.app *.3dsx *.elf *.axf)")); if (!path.isEmpty()) { - std::unique_lock lock(messageQueueMutex); + std::filesystem::path* p = new std::filesystem::path(path.toStdU16String()); - romToLoad = path.toStdU16String(); - needToLoadROM = true; + EmulatorMessage message{.type = MessageType::LoadROM}; + message.path.p = p; + sendMessage(message); } } @@ -135,6 +127,12 @@ MainWindow::~MainWindow() { delete themeSelect; } +// Send a message to the emulator thread. Lock the mutex and just push back to the vector. +void MainWindow::sendMessage(const EmulatorMessage& message) { + std::unique_lock lock(messageQueueMutex); + messageQueue.push_back(message); +} + void MainWindow::setTheme(Theme theme) { currentTheme = theme; @@ -225,4 +223,21 @@ void MainWindow::dumpRomFS() { QMessageBox::warning(this, tr("No RomFS found"), tr("No RomFS partition was found in the loaded app")); break; } +} + +void MainWindow::dispatchMessage(const EmulatorMessage& message) { + switch (message.type) { + case MessageType::LoadROM: + emu->loadROM(*message.path.p); + // Clean up the allocated path + delete message.path.p; + break; + + case MessageType::Pause: emu->pause(); break; + case MessageType::Resume: emu->resume(); break; + case MessageType::TogglePause: emu->togglePause(); break; + case MessageType::Reset: emu->reset(Emulator::ReloadOption::Reload); break; + case MessageType::PressKey: emu->getServiceManager().getHID().pressKey(message.key.key); break; + case MessageType::ReleaseKey: emu->getServiceManager().getHID().releaseKey(message.key.key); break; + } } \ No newline at end of file From a36fd6dd57bc45778290027af7a9eff0d9c1cb8f Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Mon, 30 Oct 2023 20:41:09 +0200 Subject: [PATCH 015/162] [Qt] Add basic input --- include/panda_qt/main_window.hpp | 3 ++ src/panda_qt/main_window.cpp | 56 ++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/include/panda_qt/main_window.hpp b/include/panda_qt/main_window.hpp index 0187b424..f9eb4f63 100644 --- a/include/panda_qt/main_window.hpp +++ b/include/panda_qt/main_window.hpp @@ -74,4 +74,7 @@ class MainWindow : public QMainWindow { public: MainWindow(QApplication* app, QWidget* parent = nullptr); ~MainWindow(); + + void keyPressEvent(QKeyEvent* event) override; + void keyReleaseEvent(QKeyEvent* event) override; }; \ No newline at end of file diff --git a/src/panda_qt/main_window.cpp b/src/panda_qt/main_window.cpp index 8ffaf7b6..8b70c04c 100644 --- a/src/panda_qt/main_window.cpp +++ b/src/panda_qt/main_window.cpp @@ -240,4 +240,60 @@ void MainWindow::dispatchMessage(const EmulatorMessage& message) { case MessageType::PressKey: emu->getServiceManager().getHID().pressKey(message.key.key); break; case MessageType::ReleaseKey: emu->getServiceManager().getHID().releaseKey(message.key.key); break; } +} + +void MainWindow::keyPressEvent(QKeyEvent* event) { + auto pressKey = [this](u32 key) { + EmulatorMessage message{.type = MessageType::PressKey}; + message.key.key = key; + + sendMessage(message); + }; + + switch (event->key()) { + case Qt::Key_L: pressKey(HID::Keys::A); break; + case Qt::Key_K: pressKey(HID::Keys::B); break; + case Qt::Key_O: pressKey(HID::Keys::X); break; + case Qt::Key_I: pressKey(HID::Keys::Y); break; + + case Qt::Key_Q: pressKey(HID::Keys::L); break; + case Qt::Key_P: pressKey(HID::Keys::R); break; + + case Qt::Key_Right: pressKey(HID::Keys::Right); break; + case Qt::Key_Left: pressKey(HID::Keys::Left); break; + case Qt::Key_Up: pressKey(HID::Keys::Up); break; + case Qt::Key_Down: pressKey(HID::Keys::Down); break; + + case Qt::Key_Return: pressKey(HID::Keys::Start); break; + case Qt::Key_Backspace: pressKey(HID::Keys::Select); break; + case Qt::Key_F4: sendMessage(EmulatorMessage{.type = MessageType::TogglePause}); break; + case Qt::Key_F5: sendMessage(EmulatorMessage{.type = MessageType::Reset}); break; + } +} + +void MainWindow::keyReleaseEvent(QKeyEvent* event) { + auto releaseKey = [this](u32 key) { + EmulatorMessage message{.type = MessageType::ReleaseKey}; + message.key.key = key; + + sendMessage(message); + }; + + switch (event->key()) { + case Qt::Key_L: releaseKey(HID::Keys::A); break; + case Qt::Key_K: releaseKey(HID::Keys::B); break; + case Qt::Key_O: releaseKey(HID::Keys::X); break; + case Qt::Key_I: releaseKey(HID::Keys::Y); break; + + case Qt::Key_Q: releaseKey(HID::Keys::L); break; + case Qt::Key_P: releaseKey(HID::Keys::R); break; + + case Qt::Key_Right: releaseKey(HID::Keys::Right); break; + case Qt::Key_Left: releaseKey(HID::Keys::Left); break; + case Qt::Key_Up: releaseKey(HID::Keys::Up); break; + case Qt::Key_Down: releaseKey(HID::Keys::Down); break; + + case Qt::Key_Return: releaseKey(HID::Keys::Start); break; + case Qt::Key_Backspace: releaseKey(HID::Keys::Select); break; + } } \ No newline at end of file From 9bbaab78712feceb161ae2319aad448b89aad72b Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Mon, 30 Oct 2023 20:46:11 +0200 Subject: [PATCH 016/162] [Qt] Add Pause/Resume/Reset --- src/panda_qt/main_window.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/panda_qt/main_window.cpp b/src/panda_qt/main_window.cpp index 8b70c04c..ab717087 100644 --- a/src/panda_qt/main_window.cpp +++ b/src/panda_qt/main_window.cpp @@ -27,6 +27,13 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent) auto pandaAction = fileMenu->addAction(tr("panda...")); connect(pandaAction, &QAction::triggered, this, &MainWindow::selectROM); + auto pauseAction = emulationMenu->addAction(tr("Pause")); + auto resumeAction = emulationMenu->addAction(tr("Resume")); + auto resetAction = emulationMenu->addAction(tr("Reset")); + connect(pauseAction, &QAction::triggered, this, [this]() { sendMessage(EmulatorMessage{.type = MessageType::Pause}); }); + connect(resumeAction, &QAction::triggered, this, [this]() { sendMessage(EmulatorMessage{.type = MessageType::Resume}); }); + connect(resetAction, &QAction::triggered, this, [this]() { sendMessage(EmulatorMessage{.type = MessageType::Reset}); }); + auto dumpRomFSAction = toolsMenu->addAction(tr("Dump RomFS")); connect(dumpRomFSAction, &QAction::triggered, this, &MainWindow::dumpRomFS); From b0261234a6a541fed677b2a8a995ce39bd2d5567 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Mon, 30 Oct 2023 20:53:41 +0200 Subject: [PATCH 017/162] [Qt] Properly update HID service --- include/emulator.hpp | 2 ++ src/panda_qt/main_window.cpp | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/include/emulator.hpp b/include/emulator.hpp index df930373..2da76847 100644 --- a/include/emulator.hpp +++ b/include/emulator.hpp @@ -52,12 +52,14 @@ class Emulator { // We bind gyro to right click + mouse movement bool holdingRightClick = false; + public: static constexpr u32 width = 400; static constexpr u32 height = 240 * 2; // * 2 because 2 screens ROMType romType = ROMType::None; bool running = false; // Is the emulator running a game? bool programRunning = false; // Is the emulator program itself running? + private: #ifdef PANDA3DS_ENABLE_HTTP_SERVER HttpServer httpServer; friend struct HttpServer; diff --git a/src/panda_qt/main_window.cpp b/src/panda_qt/main_window.cpp index ab717087..adfd3470 100644 --- a/src/panda_qt/main_window.cpp +++ b/src/panda_qt/main_window.cpp @@ -91,6 +91,10 @@ void MainWindow::emuThreadMainLoop() { } emu->runFrame(); + if (emu->romType != ROMType::None) { + emu->getServiceManager().getHID().updateInputs(emu->getTicks()); + } + swapEmuBuffer(); } From 7571e58ce5aa7043de0e1a5ee4a2baa30f935301 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Mon, 30 Oct 2023 21:14:03 +0200 Subject: [PATCH 018/162] [Qt] Add pink theme --- include/panda_qt/main_window.hpp | 1 + src/panda_qt/main_window.cpp | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/include/panda_qt/main_window.hpp b/include/panda_qt/main_window.hpp index f9eb4f63..31fb1e0d 100644 --- a/include/panda_qt/main_window.hpp +++ b/include/panda_qt/main_window.hpp @@ -23,6 +23,7 @@ class MainWindow : public QMainWindow { System = 0, Light = 1, Dark = 2, + GreetingsCat = 3, }; // Types of messages we might send from the GUI thread to the emulator thread diff --git a/src/panda_qt/main_window.cpp b/src/panda_qt/main_window.cpp index adfd3470..33e27a0d 100644 --- a/src/panda_qt/main_window.cpp +++ b/src/panda_qt/main_window.cpp @@ -43,6 +43,7 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent) themeSelect->addItem(tr("System")); themeSelect->addItem(tr("Light")); themeSelect->addItem(tr("Dark")); + themeSelect->addItem(tr("Greetings Cat")); themeSelect->setCurrentIndex(static_cast(currentTheme)); themeSelect->setGeometry(40, 40, 100, 50); @@ -192,6 +193,29 @@ void MainWindow::setTheme(Theme theme) { break; } + case Theme::GreetingsCat: { + QApplication::setStyle(QStyleFactory::create("Fusion")); + + QPalette p; + p.setColor(QPalette::Window, QColor(250, 207, 228)); + p.setColor(QPalette::WindowText, QColor(225, 22, 137)); + p.setColor(QPalette::Base, QColor(250, 207, 228)); + p.setColor(QPalette::AlternateBase, QColor(250, 207, 228)); + p.setColor(QPalette::ToolTipBase, QColor(225, 22, 137)); + p.setColor(QPalette::ToolTipText, QColor(225, 22, 137)); + p.setColor(QPalette::Text, QColor(225, 22, 137)); + p.setColor(QPalette::Button, QColor(250, 207, 228)); + p.setColor(QPalette::ButtonText, QColor(225, 22, 137)); + p.setColor(QPalette::BrightText, Qt::black); + p.setColor(QPalette::Link, QColor(42, 130, 218)); + + p.setColor(QPalette::Highlight, QColor(42, 130, 218)); + p.setColor(QPalette::HighlightedText, Qt::black); + qApp->setPalette(p); + break; + } + + case Theme::System: { qApp->setPalette(this->style()->standardPalette()); qApp->setStyle(QStyleFactory::create("WindowsVista")); From 1769783dc7e10358c5c34650f34375c9295a674f Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Mon, 30 Oct 2023 22:49:32 +0200 Subject: [PATCH 019/162] Add about menu --- CMakeLists.txt | 6 +-- docs/img/rstarstruck_icon.png | Bin 0 -> 9132 bytes include/panda_qt/about_window.hpp | 12 ++++++ include/panda_qt/main_window.hpp | 3 ++ src/panda_qt/about_window.cpp | 62 ++++++++++++++++++++++++++++++ src/panda_qt/main_window.cpp | 14 ++++++- 6 files changed, 92 insertions(+), 5 deletions(-) create mode 100644 docs/img/rstarstruck_icon.png create mode 100644 include/panda_qt/about_window.hpp create mode 100644 src/panda_qt/about_window.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 8a718fbb..c202f80c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -185,8 +185,8 @@ 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) - set(FRONTEND_HEADER_FILES include/panda_qt/screen.hpp include/panda_qt/main_window.hpp) + 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}) @@ -429,7 +429,7 @@ if(ENABLE_QT_GUI) qt_add_resources(Alber "app_images" PREFIX "/" FILES - docs/img/rsob_icon.png + docs/img/rsob_icon.png docs/img/rstarstruck_icon.png ) else() target_compile_definitions(Alber PUBLIC "PANDA3DS_FRONTEND_SDL=1") diff --git a/docs/img/rstarstruck_icon.png b/docs/img/rstarstruck_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..2b4aab062193120af369718fe3810c690e14ad83 GIT binary patch literal 9132 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 +#include +#include + +class AboutWindow : public QDialog { + Q_OBJECT + + public: + AboutWindow(QWidget* parent = nullptr); +}; \ No newline at end of file diff --git a/include/panda_qt/main_window.hpp b/include/panda_qt/main_window.hpp index 31fb1e0d..a50ee9a1 100644 --- a/include/panda_qt/main_window.hpp +++ b/include/panda_qt/main_window.hpp @@ -12,6 +12,7 @@ #include #include "emulator.hpp" +#include "panda_qt/about_window.hpp" #include "panda_qt/screen.hpp" #include "services/hid.hpp" @@ -56,6 +57,7 @@ class MainWindow : public QMainWindow { std::vector messageQueue; ScreenWidget screen; + AboutWindow* aboutWindow; QComboBox* themeSelect = nullptr; QMenuBar* menuBar = nullptr; @@ -65,6 +67,7 @@ class MainWindow : public QMainWindow { void emuThreadMainLoop(); void selectROM(); void dumpRomFS(); + void showAboutMenu(); void sendMessage(const EmulatorMessage& message); void dispatchMessage(const EmulatorMessage& message); diff --git a/src/panda_qt/about_window.cpp b/src/panda_qt/about_window.cpp new file mode 100644 index 00000000..6f799c23 --- /dev/null +++ b/src/panda_qt/about_window.cpp @@ -0,0 +1,62 @@ +#include "panda_qt/about_window.hpp" + +#include +#include +#include +#include + +// Based on https://github.com/dolphin-emu/dolphin/blob/master/Source/Core/DolphinQt/AboutDialog.cpp + +AboutWindow::AboutWindow(QWidget* parent) : QDialog(parent) { + resize(200, 200); + + setWindowTitle(tr("About Panda3DS")); + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + + const QString text = + QStringLiteral(R"( +

Panda3DS

+ +

+%ABOUT_PANDA3DS%
+
%SUPPORT%
+

+ +

+%AUTHORS% +

+)") + .replace(QStringLiteral("%ABOUT_PANDA3DS%"), tr("Panda3DS is a free and open source Nintendo 3DS emulator, for Windows, MacOS and Linux")) + .replace(QStringLiteral("%SUPPORT%"), tr("Visit panda3ds.com for help with Panda3DS and links to our official support sites.")) + .replace( + QStringLiteral("%AUTHORS%"), tr("Panda3DS is developed by volunteers in their spare time. Below is a list of some of these" + " volunteers who've agreed to be listed here, in no particular order.
If you think you should be " + "listed here too, please inform us

" + "- Peach (wheremyfoodat)
" + "- noumidev
" + "- liuk707
" + "- Wunk
" + "- marysaka
" + "- Sky
" + "- merryhime
" + "- TGP17
") + ); + + QLabel* textLabel = new QLabel(text); + textLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); + textLabel->setOpenExternalLinks(true); + + QLabel* logo = new QLabel(); + logo->setPixmap(QPixmap(":/docs/img/rstarstruck_icon.png")); + logo->setContentsMargins(30, 0, 30, 0); + + QVBoxLayout* mainLayout = new QVBoxLayout; + QHBoxLayout* hLayout = new QHBoxLayout; + + setLayout(mainLayout); + mainLayout->addLayout(hLayout); + + hLayout->setAlignment(Qt::AlignLeft); + hLayout->addWidget(logo); + hLayout->addWidget(textLabel); +} \ No newline at end of file diff --git a/src/panda_qt/main_window.cpp b/src/panda_qt/main_window.cpp index 33e27a0d..804dc63a 100644 --- a/src/panda_qt/main_window.cpp +++ b/src/panda_qt/main_window.cpp @@ -20,7 +20,6 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent) auto fileMenu = menuBar->addMenu(tr("File")); auto emulationMenu = menuBar->addMenu(tr("Emulation")); auto toolsMenu = menuBar->addMenu(tr("Tools")); - auto helpMenu = menuBar->addMenu(tr("Help")); auto aboutMenu = menuBar->addMenu(tr("About")); // Create and bind actions for them @@ -37,6 +36,9 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent) auto dumpRomFSAction = toolsMenu->addAction(tr("Dump RomFS")); connect(dumpRomFSAction, &QAction::triggered, this, &MainWindow::dumpRomFS); + auto aboutAction = aboutMenu->addAction(tr("About Panda3DS")); + connect(aboutAction, &QAction::triggered, this, &MainWindow::showAboutMenu); + // Set up theme selection setTheme(Theme::Dark); themeSelect = new QComboBox(this); @@ -50,6 +52,9 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent) themeSelect->show(); connect(themeSelect, &QComboBox::currentIndexChanged, this, [&](int index) { setTheme(static_cast(index)); }); + // Set up misc objects + aboutWindow = new AboutWindow(nullptr); + emu = new Emulator(); emu->setOutputSize(screen.surfaceWidth, screen.surfaceHeight); @@ -136,6 +141,7 @@ MainWindow::~MainWindow() { delete emu; delete menuBar; + delete aboutWindow; delete themeSelect; } @@ -215,7 +221,6 @@ void MainWindow::setTheme(Theme theme) { break; } - case Theme::System: { qApp->setPalette(this->style()->standardPalette()); qApp->setStyle(QStyleFactory::create("WindowsVista")); @@ -260,6 +265,11 @@ void MainWindow::dumpRomFS() { } } +void MainWindow::showAboutMenu() { + AboutWindow about(this); + about.exec(); +} + void MainWindow::dispatchMessage(const EmulatorMessage& message) { switch (message.type) { case MessageType::LoadROM: From f2b7f14dcd604ec6dbe1092e02c70cca4c9ad4ac Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Tue, 31 Oct 2023 19:33:14 +0200 Subject: [PATCH 020/162] [Qt] Fix up about window --- src/panda_qt/about_window.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/panda_qt/about_window.cpp b/src/panda_qt/about_window.cpp index 6f799c23..67767198 100644 --- a/src/panda_qt/about_window.cpp +++ b/src/panda_qt/about_window.cpp @@ -39,7 +39,8 @@ AboutWindow::AboutWindow(QWidget* parent) : QDialog(parent) { "- marysaka
" "- Sky
" "- merryhime
" - "- TGP17
") + "- TGP17
" + "- Shadow
") ); QLabel* textLabel = new QLabel(text); From 5a2e554a6ca5c1de95ff57150509813fb2e8c237 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Tue, 31 Oct 2023 23:51:34 +0200 Subject: [PATCH 021/162] Make incomplete framebuffers warn instead of panic --- include/renderer_gl/surfaces.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/renderer_gl/surfaces.hpp b/include/renderer_gl/surfaces.hpp index 1c2976d6..15304a7a 100644 --- a/include/renderer_gl/surfaces.hpp +++ b/include/renderer_gl/surfaces.hpp @@ -48,7 +48,7 @@ struct ColourBuffer { fbo.bind(OpenGL::DrawAndReadFramebuffer); if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { - Helpers::panic("Incomplete framebuffer"); + Helpers::warn("ColourBuffer: Incomplete framebuffer"); } // TODO: This should not clear the framebuffer contents. It should load them from VRAM. From 171733870d6884d279ba7070b5b303a89d10bfcd Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Wed, 1 Nov 2023 22:49:50 +0200 Subject: [PATCH 022/162] Add Y2R::GetStandardCoefficientParams --- include/services/y2r.hpp | 11 +++++++++++ src/core/services/y2r.cpp | 23 ++++++++++++++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/include/services/y2r.hpp b/include/services/y2r.hpp index 17ceaafa..3fe56f36 100644 --- a/include/services/y2r.hpp +++ b/include/services/y2r.hpp @@ -1,4 +1,5 @@ #pragma once +#include #include #include "helpers.hpp" #include "kernel_types.hpp" @@ -50,6 +51,15 @@ class Y2RService { Block8x8 = 1, // Output buffer's pixels are morton swizzled. Used when outputting to a GPU texture. }; + // https://github.com/citra-emu/citra/blob/ac9d72a95ca9a60de8d39484a14aecf489d6d016/src/core/hle/service/cam/y2r_u.cpp#L33 + using CoefficientSet = std::array; + static constexpr std::array standardCoefficients{{ + {{0x100, 0x166, 0xB6, 0x58, 0x1C5, -0x166F, 0x10EE, -0x1C5B}}, // ITU_Rec601 + {{0x100, 0x193, 0x77, 0x2F, 0x1DB, -0x1933, 0xA7C, -0x1D51}}, // ITU_Rec709 + {{0x12A, 0x198, 0xD0, 0x64, 0x204, -0x1BDE, 0x10F2, -0x229B}}, // ITU_Rec601_Scaling + {{0x12A, 0x1CA, 0x88, 0x36, 0x21C, -0x1F04, 0x99C, -0x2421}}, // ITU_Rec709_Scaling + }}; + InputFormat inputFmt; OutputFormat outputFmt; Rotation rotation; @@ -88,6 +98,7 @@ class Y2RService { void setSpacialDithering(u32 messagePointer); void setStandardCoeff(u32 messagePointer); void setTemporalDithering(u32 messagePointer); + void getStandardCoefficientParams(u32 messagePointer); void startConversion(u32 messagePointer); void stopConversion(u32 messagePointer); diff --git a/src/core/services/y2r.cpp b/src/core/services/y2r.cpp index 40a6a04d..9539f02a 100644 --- a/src/core/services/y2r.cpp +++ b/src/core/services/y2r.cpp @@ -24,6 +24,7 @@ namespace Y2RCommands { SetInputLines = 0x001C0040, GetInputLines = 0x001D0000, SetStandardCoeff = 0x00200040, + GetStandardCoefficientParams = 0x00210040, SetAlpha = 0x00220040, StartConversion = 0x00260000, StopConversion = 0x00270000, @@ -62,6 +63,7 @@ void Y2RService::handleSyncRequest(u32 messagePointer) { case Y2RCommands::GetInputLineWidth: getInputLineWidth(messagePointer); break; case Y2RCommands::GetOutputFormat: getOutputFormat(messagePointer); break; case Y2RCommands::GetTransferEndEvent: getTransferEndEvent(messagePointer); break; + case Y2RCommands::GetStandardCoefficientParams: getStandardCoefficientParams(messagePointer); break; case Y2RCommands::IsBusyConversion: isBusyConversion(messagePointer); break; case Y2RCommands::PingProcess: pingProcess(messagePointer); break; case Y2RCommands::SetAlpha: setAlpha(messagePointer); break; @@ -306,7 +308,7 @@ void Y2RService::setStandardCoeff(u32 messagePointer) { log("Y2R::SetStandardCoeff (coefficient = %d)\n", coeff); mem.write32(messagePointer, IPC::responseHeader(0x20, 1, 0)); - if (coeff > 3) { + if (coeff > 3) { // Invalid coefficient, should have an error code Helpers::panic("Y2R: Invalid standard coefficient (coefficient = %d)\n", coeff); } @@ -316,6 +318,25 @@ void Y2RService::setStandardCoeff(u32 messagePointer) { } } +void Y2RService::getStandardCoefficientParams(u32 messagePointer) { + const u32 coefficientIndex = mem.read32(messagePointer + 4); + log("Y2R::GetStandardCoefficientParams (coefficient = %d)\n", coefficientIndex); + + if (coefficientIndex > 3) { // Invalid coefficient, should have an error code + Helpers::panic("Y2R: Invalid standard coefficient (coefficient = %d)\n", coefficientIndex); + } else { + mem.write32(messagePointer, IPC::responseHeader(0x21, 5, 0)); + mem.write32(messagePointer + 4, Result::Success); + const auto& coeff = standardCoefficients[coefficientIndex]; + + // Write standard coefficient parameters to output buffer + for (int i = 0; i < 8; i++) { + const u32 pointer = messagePointer + 8 + i * sizeof(u16); // Pointer to write parameter to + mem.write16(pointer, coeff[i]); + } + } +} + void Y2RService::setSendingY(u32 messagePointer) { log("Y2R::SetSendingY\n"); Helpers::warn("Unimplemented Y2R::SetSendingY"); From cc130d52e92bba095fafd726f99d3b46e2c2123b Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Wed, 1 Nov 2023 23:01:38 +0200 Subject: [PATCH 023/162] Add Y2R::GetCoefficientParams --- include/services/y2r.hpp | 3 +++ src/core/services/y2r.cpp | 22 ++++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/include/services/y2r.hpp b/include/services/y2r.hpp index 3fe56f36..8b30525c 100644 --- a/include/services/y2r.hpp +++ b/include/services/y2r.hpp @@ -60,6 +60,8 @@ class Y2RService { {{0x12A, 0x1CA, 0x88, 0x36, 0x21C, -0x1F04, 0x99C, -0x2421}}, // ITU_Rec709_Scaling }}; + CoefficientSet conversionCoefficients; // Current conversion coefficients + InputFormat inputFmt; OutputFormat outputFmt; Rotation rotation; @@ -98,6 +100,7 @@ class Y2RService { void setSpacialDithering(u32 messagePointer); void setStandardCoeff(u32 messagePointer); void setTemporalDithering(u32 messagePointer); + void getCoefficientParams(u32 messagePointer); void getStandardCoefficientParams(u32 messagePointer); void startConversion(u32 messagePointer); diff --git a/src/core/services/y2r.cpp b/src/core/services/y2r.cpp index 9539f02a..44a04958 100644 --- a/src/core/services/y2r.cpp +++ b/src/core/services/y2r.cpp @@ -23,6 +23,7 @@ namespace Y2RCommands { GetInputLineWidth = 0x001B0000, SetInputLines = 0x001C0040, GetInputLines = 0x001D0000, + GetCoefficientParams = 0x001F0000, SetStandardCoeff = 0x00200040, GetStandardCoefficientParams = 0x00210040, SetAlpha = 0x00220040, @@ -51,6 +52,8 @@ void Y2RService::reset() { alpha = 0xFFFF; inputLines = 69; inputLineWidth = 420; + + conversionCoefficients.fill(0); } void Y2RService::handleSyncRequest(u32 messagePointer) { @@ -84,6 +87,9 @@ void Y2RService::handleSyncRequest(u32 messagePointer) { case Y2RCommands::SetTransferEndInterrupt: setTransferEndInterrupt(messagePointer); break; case Y2RCommands::StartConversion: [[likely]] startConversion(messagePointer); break; case Y2RCommands::StopConversion: stopConversion(messagePointer); break; + + // Intentionally break ordering a bit for less-used Y2R functions + case Y2RCommands::GetCoefficientParams: getCoefficientParams(messagePointer); break; default: Helpers::panic("Y2R service requested. Command: %08X\n", command); } } @@ -99,6 +105,8 @@ void Y2RService::driverInitialize(u32 messagePointer) { log("Y2R::DriverInitialize\n"); mem.write32(messagePointer, IPC::responseHeader(0x2B, 1, 0)); mem.write32(messagePointer + 4, Result::Success); + + conversionCoefficients.fill(0); } void Y2RService::driverFinalize(u32 messagePointer) { @@ -337,6 +345,20 @@ void Y2RService::getStandardCoefficientParams(u32 messagePointer) { } } +void Y2RService::getCoefficientParams(u32 messagePointer) { + log("Y2R::GetCoefficientParams\n"); + mem.write32(messagePointer, IPC::responseHeader(0x1F, 5, 0)); + mem.write32(messagePointer + 4, Result::Success); + const auto& coeff = conversionCoefficients; + + // Write coefficient parameters to output buffer + for (int i = 0; i < 8; i++) { + const u32 pointer = messagePointer + 8 + i * sizeof(u16); // Pointer to write parameter to + mem.write16(pointer, coeff[i]); + } +} + + void Y2RService::setSendingY(u32 messagePointer) { log("Y2R::SetSendingY\n"); Helpers::warn("Unimplemented Y2R::SetSendingY"); From 0b56427019e82ca7705680f4e7b1da46cccdc28d Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Wed, 1 Nov 2023 23:09:07 +0200 Subject: [PATCH 024/162] Add Y2R::SetCoefficientParams --- include/services/y2r.hpp | 1 + src/core/services/y2r.cpp | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/include/services/y2r.hpp b/include/services/y2r.hpp index 8b30525c..0a1cae2f 100644 --- a/include/services/y2r.hpp +++ b/include/services/y2r.hpp @@ -87,6 +87,7 @@ class Y2RService { void setAlpha(u32 messagePointer); void setBlockAlignment(u32 messagePointer); + void setCoefficientParams(u32 messagePointer); void setInputFormat(u32 messagePointer); void setInputLineWidth(u32 messagePointer); void setInputLines(u32 messagePointer); diff --git a/src/core/services/y2r.cpp b/src/core/services/y2r.cpp index 44a04958..62f933f6 100644 --- a/src/core/services/y2r.cpp +++ b/src/core/services/y2r.cpp @@ -23,6 +23,7 @@ namespace Y2RCommands { GetInputLineWidth = 0x001B0000, SetInputLines = 0x001C0040, GetInputLines = 0x001D0000, + SetCoefficientParams = 0x001E0100, GetCoefficientParams = 0x001F0000, SetStandardCoeff = 0x00200040, GetStandardCoefficientParams = 0x00210040, @@ -89,6 +90,7 @@ void Y2RService::handleSyncRequest(u32 messagePointer) { case Y2RCommands::StopConversion: stopConversion(messagePointer); break; // Intentionally break ordering a bit for less-used Y2R functions + case Y2RCommands::SetCoefficientParams: setCoefficientParams(messagePointer); break; case Y2RCommands::GetCoefficientParams: getCoefficientParams(messagePointer); break; default: Helpers::panic("Y2R service requested. Command: %08X\n", command); } @@ -345,6 +347,19 @@ void Y2RService::getStandardCoefficientParams(u32 messagePointer) { } } +void Y2RService::setCoefficientParams(u32 messagePointer) { + log("Y2R::SetCoefficientParams\n"); + mem.write32(messagePointer, IPC::responseHeader(0x1E, 1, 0)); + mem.write32(messagePointer + 4, Result::Success); + auto& coeff = conversionCoefficients; + + // Write coefficient parameters to output buffer + for (int i = 0; i < 8; i++) { + const u32 pointer = messagePointer + 8 + i * sizeof(u16); // Pointer to write parameter to + coeff[i] = mem.read16(pointer); + } +} + void Y2RService::getCoefficientParams(u32 messagePointer) { log("Y2R::GetCoefficientParams\n"); mem.write32(messagePointer, IPC::responseHeader(0x1F, 5, 0)); @@ -358,7 +373,6 @@ void Y2RService::getCoefficientParams(u32 messagePointer) { } } - void Y2RService::setSendingY(u32 messagePointer) { log("Y2R::SetSendingY\n"); Helpers::warn("Unimplemented Y2R::SetSendingY"); From 42252e6442a791b8ebf4a50b9681308e4a30fa1b Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Wed, 1 Nov 2023 23:29:09 +0200 Subject: [PATCH 025/162] Fix Y2R::SetCoefficientParams --- src/core/services/y2r.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/core/services/y2r.cpp b/src/core/services/y2r.cpp index 62f933f6..b5daf6bb 100644 --- a/src/core/services/y2r.cpp +++ b/src/core/services/y2r.cpp @@ -288,6 +288,7 @@ void Y2RService::getInputLineWidth(u32 messagePointer) { mem.write32(messagePointer + 4, Result::Success); mem.write32(messagePointer + 8, inputLineWidth); } + void Y2RService::setInputLines(u32 messagePointer) { const u16 lines = mem.read16(messagePointer + 4); log("Y2R::SetInputLines (lines = %d)\n", lines); @@ -349,15 +350,16 @@ void Y2RService::getStandardCoefficientParams(u32 messagePointer) { void Y2RService::setCoefficientParams(u32 messagePointer) { log("Y2R::SetCoefficientParams\n"); - mem.write32(messagePointer, IPC::responseHeader(0x1E, 1, 0)); - mem.write32(messagePointer + 4, Result::Success); auto& coeff = conversionCoefficients; // Write coefficient parameters to output buffer for (int i = 0; i < 8; i++) { - const u32 pointer = messagePointer + 8 + i * sizeof(u16); // Pointer to write parameter to + const u32 pointer = messagePointer + 4 + i * sizeof(u16); // Pointer to write parameter to coeff[i] = mem.read16(pointer); } + + mem.write32(messagePointer, IPC::responseHeader(0x1E, 1, 0)); + mem.write32(messagePointer + 4, Result::Success); } void Y2RService::getCoefficientParams(u32 messagePointer) { From 798b10ec690306e876224746ca52d678d2be078a Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Sat, 4 Nov 2023 18:07:13 +0200 Subject: [PATCH 026/162] Fix KTimer not properly triggering --- src/core/kernel/timers.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/core/kernel/timers.cpp b/src/core/kernel/timers.cpp index 1c18d9de..4c2bf358 100644 --- a/src/core/kernel/timers.cpp +++ b/src/core/kernel/timers.cpp @@ -51,6 +51,10 @@ void Kernel::signalTimer(Handle timerHandle, Timer* timer) { case ResetType::Pulse: Helpers::panic("Signalled pulsing timer"); break; } } + + if (timer->interval == 0) { + cancelTimer(timer); + } } void Kernel::svcCreateTimer() { @@ -70,8 +74,8 @@ void Kernel::svcCreateTimer() { void Kernel::svcSetTimer() { Handle handle = regs[0]; // TODO: Is this actually s64 or u64? 3DBrew says s64, but u64 makes more sense - const s64 initial = s64(u64(regs[1]) | (u64(regs[2]) << 32)); - const s64 interval = s64(u64(regs[3]) | (u64(regs[4]) << 32)); + const s64 initial = s64(u64(regs[1]) | (u64(regs[3]) << 32)); + const s64 interval = s64(u64(regs[2]) | (u64(regs[4]) << 32)); logSVC("SetTimer (handle = %X, initial delay = %llX, interval delay = %llX)\n", handle, initial, interval); KernelObject* object = getObject(handle, KernelObjectType::Timer); From 708b10c194e1056e7c342d61974faddf567f833a Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Sat, 4 Nov 2023 18:26:20 +0200 Subject: [PATCH 027/162] Fix svcSetTimer ABI maybe...? --- src/core/kernel/timers.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/kernel/timers.cpp b/src/core/kernel/timers.cpp index 4c2bf358..c6097ed7 100644 --- a/src/core/kernel/timers.cpp +++ b/src/core/kernel/timers.cpp @@ -74,8 +74,8 @@ void Kernel::svcCreateTimer() { void Kernel::svcSetTimer() { Handle handle = regs[0]; // TODO: Is this actually s64 or u64? 3DBrew says s64, but u64 makes more sense - const s64 initial = s64(u64(regs[1]) | (u64(regs[3]) << 32)); - const s64 interval = s64(u64(regs[2]) | (u64(regs[4]) << 32)); + const s64 initial = s64(u64(regs[2]) | (u64(regs[3]) << 32)); + const s64 interval = s64(u64(regs[1]) | (u64(regs[4]) << 32)); logSVC("SetTimer (handle = %X, initial delay = %llX, interval delay = %llX)\n", handle, initial, interval); KernelObject* object = getObject(handle, KernelObjectType::Timer); From 1f7fc2274ac5b3a985f1bf7f59ca21f27d67a53a Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Sat, 4 Nov 2023 18:28:28 +0200 Subject: [PATCH 028/162] Add vector of timer handles --- include/kernel/kernel.hpp | 1 + src/core/kernel/kernel.cpp | 1 + src/core/kernel/timers.cpp | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/include/kernel/kernel.hpp b/include/kernel/kernel.hpp index dc821a68..30f1310e 100644 --- a/include/kernel/kernel.hpp +++ b/include/kernel/kernel.hpp @@ -36,6 +36,7 @@ class Kernel { std::vector objects; std::vector portHandles; std::vector mutexHandles; + std::vector timerHandles; // Thread indices, sorted by priority std::vector threadIndices; diff --git a/src/core/kernel/kernel.cpp b/src/core/kernel/kernel.cpp index b21c4c58..cd691b48 100644 --- a/src/core/kernel/kernel.cpp +++ b/src/core/kernel/kernel.cpp @@ -148,6 +148,7 @@ void Kernel::reset() { } objects.clear(); mutexHandles.clear(); + timerHandles.clear(); portHandles.clear(); threadIndices.clear(); serviceManager.reset(); diff --git a/src/core/kernel/timers.cpp b/src/core/kernel/timers.cpp index c6097ed7..a9c95292 100644 --- a/src/core/kernel/timers.cpp +++ b/src/core/kernel/timers.cpp @@ -9,7 +9,7 @@ Handle Kernel::makeTimer(ResetType type) { Helpers::panic("Created pulse timer"); } - // timerHandles.push_back(ret); + timerHandles.push_back(ret); return ret; } From 135c8cb508fda0ec454833c39616d12d58b3ce5d Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Sat, 11 Nov 2023 20:11:40 +0200 Subject: [PATCH 029/162] [Applets] Proper glance/receiveParameter sorta --- include/applets/applet_manager.hpp | 7 +++++ include/services/apt.hpp | 25 ++++++++++++++++ src/core/applets/applet_manager.cpp | 29 ++++++++++++++++++ src/core/services/apt.cpp | 46 +++++++++-------------------- 4 files changed, 75 insertions(+), 32 deletions(-) diff --git a/include/applets/applet_manager.hpp b/include/applets/applet_manager.hpp index 95b54009..e75e1268 100644 --- a/include/applets/applet_manager.hpp +++ b/include/applets/applet_manager.hpp @@ -1,3 +1,6 @@ +#pragma once +#include + #include "applets/mii_selector.hpp" #include "applets/software_keyboard.hpp" #include "helpers.hpp" @@ -8,10 +11,14 @@ namespace Applets { class AppletManager { MiiSelectorApplet miiSelector; SoftwareKeyboardApplet swkbd; + std::optional nextParameter = std::nullopt; public: AppletManager(Memory& mem); void reset(); AppletBase* getApplet(u32 id); + + Applets::Parameter glanceParameter(); + Applets::Parameter receiveParameter(); }; } // namespace Applets \ No newline at end of file diff --git a/include/services/apt.hpp b/include/services/apt.hpp index a7df056f..48a59c2d 100644 --- a/include/services/apt.hpp +++ b/include/services/apt.hpp @@ -15,6 +15,31 @@ enum class ConsoleModel : u32 { Old3DS, New3DS }; +// https://www.3dbrew.org/wiki/NS_and_APT_Services#Command +namespace APT::Transitions { + enum : u32 { + None = 0, + Wakeup = 1, + Request = 2, + Response = 3, + Exit = 4, + Message = 5, + HomeButtonSingle = 6, + HomeButtonDouble = 7, + DSPSleep = 8, + DSPWakeup = 9, + WakeupByExit = 10, + WakuepByPause = 11, + WakeupByCancel = 12, + WakeupByCancelAll = 13, + WakeupByPowerButton = 14, + WakeupToJumpHome = 15, + RequestForApplet = 16, + WakeupToLaunchApp = 17, + ProcessDed = 0x41 + }; +} + class APTService { Handle handle = KernelHandles::APT; Memory& mem; diff --git a/src/core/applets/applet_manager.cpp b/src/core/applets/applet_manager.cpp index c94eee28..192a76c0 100644 --- a/src/core/applets/applet_manager.cpp +++ b/src/core/applets/applet_manager.cpp @@ -1,4 +1,7 @@ #include "applets/applet_manager.hpp" + +#include "services/apt.hpp" + using namespace Applets; AppletManager::AppletManager(Memory& mem) : miiSelector(mem), swkbd(mem) {} @@ -18,4 +21,30 @@ AppletBase* AppletManager::getApplet(u32 id) { default: return nullptr; } +} + +Applets::Parameter AppletManager::glanceParameter() { + if (nextParameter) { + // Copy parameter + Applets::Parameter param = nextParameter.value(); + // APT module clears next parameter even for GlanceParameter for these 2 signals + if (param.signal == APTSignal::DspWakeup || param.signal == APTSignal::DspSleep) { + nextParameter = std::nullopt; + } + + return param; + } + + // Default return value. This is legacy code from before applets were implemented. TODO: Update it + else { + return Applets::Parameter{.senderID = 0, .destID = Applets::AppletIDs::Application, .signal = APTSignal::Wakeup, .data = {}}; + } +} + +Applets::Parameter AppletManager::receiveParameter() { + Applets::Parameter param = glanceParameter(); + // ReceiveParameter always clears nextParameter whereas glanceParameter does not + nextParameter = std::nullopt; + + return param; } \ No newline at end of file diff --git a/src/core/services/apt.cpp b/src/core/services/apt.cpp index d969f2a4..64ff5cef 100644 --- a/src/core/services/apt.cpp +++ b/src/core/services/apt.cpp @@ -32,31 +32,6 @@ namespace APTCommands { }; } -// https://www.3dbrew.org/wiki/NS_and_APT_Services#Command -namespace APTTransitions { - enum : u32 { - None = 0, - Wakeup = 1, - Request = 2, - Response = 3, - Exit = 4, - Message = 5, - HomeButtonSingle = 6, - HomeButtonDouble = 7, - DSPSleep = 8, - DSPWakeup = 9, - WakeupByExit = 10, - WakuepByPause = 11, - WakeupByCancel = 12, - WakeupByCancelAll = 13, - WakeupByPowerButton = 14, - WakeupToJumpHome = 15, - RequestForApplet = 16, - WakeupToLaunchApp = 17, - ProcessDed = 0x41 - }; -} - void APTService::reset() { // Set the default CPU time limit to 30%. Seems safe, as this is what Metroid 2 uses by default cpuTimeLimit = 30; @@ -273,13 +248,16 @@ void APTService::receiveParameter(u32 messagePointer) { log("APT::ReceiveParameter(app ID = %X, size = %04X) (STUBBED)\n", app, size); if (size > 0x1000) Helpers::panic("APT::ReceiveParameter with size > 0x1000"); + auto parameter = appletManager.receiveParameter(); - // TODO: Properly implement this. We currently stub somewhat like 3dmoo mem.write32(messagePointer, IPC::responseHeader(0xD, 4, 4)); mem.write32(messagePointer + 4, Result::Success); - mem.write32(messagePointer + 8, 0); // Sender App ID - mem.write32(messagePointer + 12, APTTransitions::Wakeup); // Command - mem.write32(messagePointer + 16, 0); + // Sender App ID + mem.write32(messagePointer + 8, parameter.senderID); + // Command + mem.write32(messagePointer + 12, static_cast(parameter.signal)); + // Size of parameter data + mem.write32(messagePointer + 16, parameter.data.size()); mem.write32(messagePointer + 20, 0x10); mem.write32(messagePointer + 24, 0); mem.write32(messagePointer + 28, 0); @@ -291,13 +269,17 @@ void APTService::glanceParameter(u32 messagePointer) { log("APT::GlanceParameter(app ID = %X, size = %04X) (STUBBED)\n", app, size); if (size > 0x1000) Helpers::panic("APT::GlanceParameter with size > 0x1000"); + auto parameter = appletManager.glanceParameter(); // TODO: Properly implement this. We currently stub it similar mem.write32(messagePointer, IPC::responseHeader(0xE, 4, 4)); mem.write32(messagePointer + 4, Result::Success); - mem.write32(messagePointer + 8, 0); // Sender App ID - mem.write32(messagePointer + 12, APTTransitions::Wakeup); // Command - mem.write32(messagePointer + 16, 0); + // Sender App ID + mem.write32(messagePointer + 8, parameter.senderID); + // Command + mem.write32(messagePointer + 12, static_cast(parameter.signal)); + // Size of parameter data + mem.write32(messagePointer + 16, parameter.data.size()); mem.write32(messagePointer + 20, 0); mem.write32(messagePointer + 24, 0); mem.write32(messagePointer + 28, 0); From 60b565273e881d8bf579c00cecbed8ef907cf43c Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Sat, 11 Nov 2023 20:35:10 +0200 Subject: [PATCH 030/162] [Applets] Properly expose nextParameter --- include/applets/applet.hpp | 4 +++- include/applets/mii_selector.hpp | 2 +- include/applets/software_keyboard.hpp | 2 +- src/core/applets/applet_manager.cpp | 2 +- src/core/applets/software_keyboard.cpp | 9 +++++++++ 5 files changed, 15 insertions(+), 4 deletions(-) diff --git a/include/applets/applet.hpp b/include/applets/applet.hpp index 0c3ab519..f1a91afd 100644 --- a/include/applets/applet.hpp +++ b/include/applets/applet.hpp @@ -72,7 +72,9 @@ namespace Applets { }; class AppletBase { + protected: Memory& mem; + std::optional& nextParameter; public: virtual const char* name() = 0; @@ -83,6 +85,6 @@ namespace Applets { virtual Result::HorizonResult receiveParameter() = 0; virtual void reset() = 0; - AppletBase(Memory& mem) : mem(mem) {} + AppletBase(Memory& mem, std::optional& nextParam) : mem(mem), nextParameter(nextParam) {} }; } // namespace Applets \ No newline at end of file diff --git a/include/applets/mii_selector.hpp b/include/applets/mii_selector.hpp index e40547fb..7a882615 100644 --- a/include/applets/mii_selector.hpp +++ b/include/applets/mii_selector.hpp @@ -8,6 +8,6 @@ namespace Applets { virtual Result::HorizonResult receiveParameter() override; virtual void reset() override; - MiiSelectorApplet(Memory& memory) : AppletBase(memory) {} + MiiSelectorApplet(Memory& memory, std::optional& nextParam) : AppletBase(memory, nextParam) {} }; } // namespace Applets \ No newline at end of file diff --git a/include/applets/software_keyboard.hpp b/include/applets/software_keyboard.hpp index 1fb721a1..89b87953 100644 --- a/include/applets/software_keyboard.hpp +++ b/include/applets/software_keyboard.hpp @@ -8,6 +8,6 @@ namespace Applets { virtual Result::HorizonResult receiveParameter() override; virtual void reset() override; - SoftwareKeyboardApplet(Memory& memory) : AppletBase(memory) {} + SoftwareKeyboardApplet(Memory& memory, std::optional& nextParam) : AppletBase(memory, nextParam) {} }; } // namespace Applets \ No newline at end of file diff --git a/src/core/applets/applet_manager.cpp b/src/core/applets/applet_manager.cpp index 192a76c0..196b1f60 100644 --- a/src/core/applets/applet_manager.cpp +++ b/src/core/applets/applet_manager.cpp @@ -4,7 +4,7 @@ using namespace Applets; -AppletManager::AppletManager(Memory& mem) : miiSelector(mem), swkbd(mem) {} +AppletManager::AppletManager(Memory& mem) : miiSelector(mem, nextParameter), swkbd(mem, nextParameter) {} void AppletManager::reset() { miiSelector.reset(); diff --git a/src/core/applets/software_keyboard.cpp b/src/core/applets/software_keyboard.cpp index 2ff22792..3c203133 100644 --- a/src/core/applets/software_keyboard.cpp +++ b/src/core/applets/software_keyboard.cpp @@ -7,5 +7,14 @@ Result::HorizonResult SoftwareKeyboardApplet::start() { return Result::Success; Result::HorizonResult SoftwareKeyboardApplet::receiveParameter() { Helpers::warn("Software keyboard: Unimplemented ReceiveParameter"); + + Applets::Parameter param = Applets::Parameter{ + .senderID = AppletIDs::SoftwareKeyboard, + .destID = AppletIDs::Application, + .signal = APTSignal::Response, + .data = {}, + }; + + nextParameter = param; return Result::Success; } \ No newline at end of file From 5df44e0c4f91d5d6ee3a2c6fe5f85c52e3ca1675 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Sat, 11 Nov 2023 20:51:53 +0200 Subject: [PATCH 031/162] [GSP] Add GSP::GPU::ReleaseRight --- include/services/gsp_gpu.hpp | 1 + src/core/services/gsp_gpu.cpp | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/include/services/gsp_gpu.hpp b/include/services/gsp_gpu.hpp index 92ca36e2..68e24580 100644 --- a/include/services/gsp_gpu.hpp +++ b/include/services/gsp_gpu.hpp @@ -65,6 +65,7 @@ class GPUService { void flushDataCache(u32 messagePointer); void importDisplayCaptureInfo(u32 messagePointer); void registerInterruptRelayQueue(u32 messagePointer); + void releaseRight(u32 messagePointer); void saveVramSysArea(u32 messagePointer); void setAxiConfigQoSMode(u32 messagePointer); void setBufferSwap(u32 messagePointer); diff --git a/src/core/services/gsp_gpu.cpp b/src/core/services/gsp_gpu.cpp index 0e27c0ec..861dfb0a 100644 --- a/src/core/services/gsp_gpu.cpp +++ b/src/core/services/gsp_gpu.cpp @@ -15,6 +15,7 @@ namespace ServiceCommands { FlushDataCache = 0x00080082, SetLCDForceBlack = 0x000B0040, TriggerCmdReqQueue = 0x000C0000, + ReleaseRight = 0x00170000, ImportDisplayCaptureInfo = 0x00180000, SaveVramSysArea = 0x00190000, SetInternalPriorities = 0x001E0080, @@ -49,6 +50,7 @@ void GPUService::handleSyncRequest(u32 messagePointer) { case ServiceCommands::FlushDataCache: flushDataCache(messagePointer); break; case ServiceCommands::ImportDisplayCaptureInfo: importDisplayCaptureInfo(messagePointer); break; case ServiceCommands::RegisterInterruptRelayQueue: registerInterruptRelayQueue(messagePointer); break; + case ServiceCommands::ReleaseRight: releaseRight(messagePointer); break; case ServiceCommands::SaveVramSysArea: saveVramSysArea(messagePointer); break; case ServiceCommands::SetAxiConfigQoSMode: setAxiConfigQoSMode(messagePointer); break; case ServiceCommands::SetBufferSwap: setBufferSwap(messagePointer); break; @@ -80,6 +82,16 @@ void GPUService::acquireRight(u32 messagePointer) { mem.write32(messagePointer + 4, Result::Success); } +void GPUService::releaseRight(u32 messagePointer) { + log("GSP::GPU::ReleaseRight\n"); + if (privilegedProcess == currentPID) { + privilegedProcess = 0xFFFFFFFF; + } + + mem.write32(messagePointer, IPC::responseHeader(0x17, 1, 0)); + mem.write32(messagePointer + 4, Result::Success); +} + // TODO: What is the flags field meant to be? // What is the "GSP module thread index" meant to be? // How does the shared memory handle thing work? From bd1d7b7a810422c81257ac5b573f7f3b115f8049 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Sun, 12 Nov 2023 21:07:44 +0200 Subject: [PATCH 032/162] [APT] Properly forward parameters in SendParameter --- include/applets/applet.hpp | 4 ++-- include/applets/mii_selector.hpp | 2 +- include/applets/software_keyboard.hpp | 2 +- src/core/applets/applet_manager.cpp | 9 +++++++-- src/core/applets/mii_selector.cpp | 2 +- src/core/applets/software_keyboard.cpp | 6 +++--- src/core/services/apt.cpp | 16 +++++++++++++++- 7 files changed, 30 insertions(+), 11 deletions(-) diff --git a/include/applets/applet.hpp b/include/applets/applet.hpp index f1a91afd..79fba1cb 100644 --- a/include/applets/applet.hpp +++ b/include/applets/applet.hpp @@ -67,7 +67,7 @@ namespace Applets { struct Parameter { u32 senderID; u32 destID; - APTSignal signal; + u32 signal; std::vector data; }; @@ -82,7 +82,7 @@ namespace Applets { // Called by APT::StartLibraryApplet and similar virtual Result::HorizonResult start() = 0; // Transfer parameters from application -> applet - virtual Result::HorizonResult receiveParameter() = 0; + virtual Result::HorizonResult receiveParameter(const Parameter& parameter) = 0; virtual void reset() = 0; AppletBase(Memory& mem, std::optional& nextParam) : mem(mem), nextParameter(nextParam) {} diff --git a/include/applets/mii_selector.hpp b/include/applets/mii_selector.hpp index 7a882615..c90d4b59 100644 --- a/include/applets/mii_selector.hpp +++ b/include/applets/mii_selector.hpp @@ -5,7 +5,7 @@ namespace Applets { public: virtual const char* name() override { return "Mii Selector"; } virtual Result::HorizonResult start() override; - virtual Result::HorizonResult receiveParameter() override; + virtual Result::HorizonResult receiveParameter(const Applets::Parameter& parameter) override; virtual void reset() override; MiiSelectorApplet(Memory& memory, std::optional& nextParam) : AppletBase(memory, nextParam) {} diff --git a/include/applets/software_keyboard.hpp b/include/applets/software_keyboard.hpp index 89b87953..f4179012 100644 --- a/include/applets/software_keyboard.hpp +++ b/include/applets/software_keyboard.hpp @@ -5,7 +5,7 @@ namespace Applets { public: virtual const char* name() override { return "Software Keyboard"; } virtual Result::HorizonResult start() override; - virtual Result::HorizonResult receiveParameter() override; + virtual Result::HorizonResult receiveParameter(const Applets::Parameter& parameter) override; virtual void reset() override; SoftwareKeyboardApplet(Memory& memory, std::optional& nextParam) : AppletBase(memory, nextParam) {} diff --git a/src/core/applets/applet_manager.cpp b/src/core/applets/applet_manager.cpp index 196b1f60..4c241027 100644 --- a/src/core/applets/applet_manager.cpp +++ b/src/core/applets/applet_manager.cpp @@ -28,7 +28,7 @@ Applets::Parameter AppletManager::glanceParameter() { // Copy parameter Applets::Parameter param = nextParameter.value(); // APT module clears next parameter even for GlanceParameter for these 2 signals - if (param.signal == APTSignal::DspWakeup || param.signal == APTSignal::DspSleep) { + if (param.signal == static_cast(APTSignal::DspWakeup) || param.signal == static_cast(APTSignal::DspSleep)) { nextParameter = std::nullopt; } @@ -37,7 +37,12 @@ Applets::Parameter AppletManager::glanceParameter() { // Default return value. This is legacy code from before applets were implemented. TODO: Update it else { - return Applets::Parameter{.senderID = 0, .destID = Applets::AppletIDs::Application, .signal = APTSignal::Wakeup, .data = {}}; + return Applets::Parameter{ + .senderID = 0, + .destID = Applets::AppletIDs::Application, + .signal = static_cast(APTSignal::Wakeup), + .data = {}, + }; } } diff --git a/src/core/applets/mii_selector.cpp b/src/core/applets/mii_selector.cpp index 211e6f07..f392d846 100644 --- a/src/core/applets/mii_selector.cpp +++ b/src/core/applets/mii_selector.cpp @@ -5,7 +5,7 @@ using namespace Applets; void MiiSelectorApplet::reset() {} Result::HorizonResult MiiSelectorApplet::start() { return Result::Success; } -Result::HorizonResult MiiSelectorApplet::receiveParameter() { +Result::HorizonResult MiiSelectorApplet::receiveParameter(const Applets::Parameter& parameter) { Helpers::warn("Mii Selector: Unimplemented ReceiveParameter"); return Result::Success; } \ No newline at end of file diff --git a/src/core/applets/software_keyboard.cpp b/src/core/applets/software_keyboard.cpp index 3c203133..be5a9e21 100644 --- a/src/core/applets/software_keyboard.cpp +++ b/src/core/applets/software_keyboard.cpp @@ -5,13 +5,13 @@ using namespace Applets; void SoftwareKeyboardApplet::reset() {} Result::HorizonResult SoftwareKeyboardApplet::start() { return Result::Success; } -Result::HorizonResult SoftwareKeyboardApplet::receiveParameter() { +Result::HorizonResult SoftwareKeyboardApplet::receiveParameter(const Applets::Parameter& parameter) { Helpers::warn("Software keyboard: Unimplemented ReceiveParameter"); Applets::Parameter param = Applets::Parameter{ - .senderID = AppletIDs::SoftwareKeyboard, + .senderID = parameter.destID, .destID = AppletIDs::Application, - .signal = APTSignal::Response, + .signal = static_cast(APTSignal::Response), .data = {}, }; diff --git a/src/core/services/apt.cpp b/src/core/services/apt.cpp index 64ff5cef..f1c43b26 100644 --- a/src/core/services/apt.cpp +++ b/src/core/services/apt.cpp @@ -234,7 +234,21 @@ void APTService::sendParameter(u32 messagePointer) { if (destApplet == nullptr) { Helpers::warn("APT::SendParameter: Unimplemented dest applet ID"); } else { - auto result = destApplet->receiveParameter(); + // Construct parameter, send it to applet + Applets::Parameter param; + param.senderID = sourceAppID; + param.destID = destAppID; + param.signal = cmd; + + // Fetch parameter data buffer + param.data.reserve(paramSize); + u32 pointer = parameterPointer; + + for (u32 i = 0; i < paramSize; i++) { + param.data.push_back(mem.read8(pointer++)); + } + + auto result = destApplet->receiveParameter(param); } if (resumeEvent.has_value()) { From aa40eaf4cc9d1094fa5d9f83e8090a4de6a0d07a Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Sun, 12 Nov 2023 21:16:38 +0200 Subject: [PATCH 033/162] [APT] Remove redundant cast --- src/core/services/apt.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/services/apt.cpp b/src/core/services/apt.cpp index f1c43b26..0fc2bb8a 100644 --- a/src/core/services/apt.cpp +++ b/src/core/services/apt.cpp @@ -269,7 +269,7 @@ void APTService::receiveParameter(u32 messagePointer) { // Sender App ID mem.write32(messagePointer + 8, parameter.senderID); // Command - mem.write32(messagePointer + 12, static_cast(parameter.signal)); + mem.write32(messagePointer + 12, parameter.signal); // Size of parameter data mem.write32(messagePointer + 16, parameter.data.size()); mem.write32(messagePointer + 20, 0x10); From 62af35c42b8a13e00b356d1a30904f295e24b768 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Sun, 12 Nov 2023 21:24:12 +0200 Subject: [PATCH 034/162] [Applet Manager] Fix resetting --- src/core/applets/applet_manager.cpp | 2 ++ src/core/services/apt.cpp | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/core/applets/applet_manager.cpp b/src/core/applets/applet_manager.cpp index 4c241027..c2791777 100644 --- a/src/core/applets/applet_manager.cpp +++ b/src/core/applets/applet_manager.cpp @@ -7,6 +7,8 @@ using namespace Applets; AppletManager::AppletManager(Memory& mem) : miiSelector(mem, nextParameter), swkbd(mem, nextParameter) {} void AppletManager::reset() { + nextParameter = std::nullopt; + miiSelector.reset(); swkbd.reset(); } diff --git a/src/core/services/apt.cpp b/src/core/services/apt.cpp index 0fc2bb8a..754baa13 100644 --- a/src/core/services/apt.cpp +++ b/src/core/services/apt.cpp @@ -291,7 +291,7 @@ void APTService::glanceParameter(u32 messagePointer) { // Sender App ID mem.write32(messagePointer + 8, parameter.senderID); // Command - mem.write32(messagePointer + 12, static_cast(parameter.signal)); + mem.write32(messagePointer + 12, parameter.signal); // Size of parameter data mem.write32(messagePointer + 16, parameter.data.size()); mem.write32(messagePointer + 20, 0); From 469ae2805cc452ce72673eca87d5f042b130ecc8 Mon Sep 17 00:00:00 2001 From: offtkp Date: Tue, 14 Nov 2023 15:30:53 +0200 Subject: [PATCH 035/162] Add initial pandroid files --- CMakeLists.txt | 34 +++- src/emulator.cpp | 2 + src/jni_driver.cpp | 11 ++ src/pandroid/.gitignore | 15 ++ src/pandroid/app/.gitignore | 1 + src/pandroid/app/build.gradle.kts | 46 +++++ src/pandroid/app/proguard-rules.pro | 21 ++ src/pandroid/app/src/main/AndroidManifest.xml | 26 +++ .../com/panda3ds/pandroid/AlberDriver.java | 10 + .../com/panda3ds/pandroid/MainActivity.java | 16 ++ src/pandroid/app/src/main/jniLibs/.gitignore | 2 + .../app/src/main/jniLibs/arm64-v8a/.gitkeep | 0 .../app/src/main/jniLibs/x86_64/.gitkeep | 0 .../res/drawable/ic_launcher_background.xml | 170 ++++++++++++++++ .../res/drawable/ic_launcher_foreground.xml | 30 +++ .../app/src/main/res/layout/activity_main.xml | 18 ++ .../res/mipmap-anydpi-v26/ic_launcher.xml | 6 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 6 + .../src/main/res/mipmap-hdpi/ic_launcher.webp | Bin 0 -> 1404 bytes .../res/mipmap-hdpi/ic_launcher_round.webp | Bin 0 -> 2898 bytes .../src/main/res/mipmap-mdpi/ic_launcher.webp | Bin 0 -> 982 bytes .../res/mipmap-mdpi/ic_launcher_round.webp | Bin 0 -> 1772 bytes .../main/res/mipmap-xhdpi/ic_launcher.webp | Bin 0 -> 1900 bytes .../res/mipmap-xhdpi/ic_launcher_round.webp | Bin 0 -> 3918 bytes .../main/res/mipmap-xxhdpi/ic_launcher.webp | Bin 0 -> 2884 bytes .../res/mipmap-xxhdpi/ic_launcher_round.webp | Bin 0 -> 5914 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.webp | Bin 0 -> 3844 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.webp | Bin 0 -> 7778 bytes .../app/src/main/res/values-night/themes.xml | 7 + .../app/src/main/res/values/colors.xml | 5 + .../app/src/main/res/values/strings.xml | 3 + .../app/src/main/res/values/themes.xml | 9 + .../app/src/main/res/xml/backup_rules.xml | 13 ++ .../main/res/xml/data_extraction_rules.xml | 19 ++ src/pandroid/build.gradle.kts | 4 + src/pandroid/gradle.properties | 21 ++ .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 59203 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + src/pandroid/gradlew | 185 ++++++++++++++++++ src/pandroid/gradlew.bat | 89 +++++++++ src/pandroid/settings.gradle.kts | 17 ++ 41 files changed, 782 insertions(+), 10 deletions(-) create mode 100644 src/jni_driver.cpp create mode 100644 src/pandroid/.gitignore create mode 100644 src/pandroid/app/.gitignore create mode 100644 src/pandroid/app/build.gradle.kts create mode 100644 src/pandroid/app/proguard-rules.pro create mode 100644 src/pandroid/app/src/main/AndroidManifest.xml create mode 100644 src/pandroid/app/src/main/java/com/panda3ds/pandroid/AlberDriver.java create mode 100644 src/pandroid/app/src/main/java/com/panda3ds/pandroid/MainActivity.java create mode 100644 src/pandroid/app/src/main/jniLibs/.gitignore create mode 100644 src/pandroid/app/src/main/jniLibs/arm64-v8a/.gitkeep create mode 100644 src/pandroid/app/src/main/jniLibs/x86_64/.gitkeep create mode 100644 src/pandroid/app/src/main/res/drawable/ic_launcher_background.xml create mode 100644 src/pandroid/app/src/main/res/drawable/ic_launcher_foreground.xml create mode 100644 src/pandroid/app/src/main/res/layout/activity_main.xml create mode 100644 src/pandroid/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 src/pandroid/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 src/pandroid/app/src/main/res/mipmap-hdpi/ic_launcher.webp create mode 100644 src/pandroid/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp create mode 100644 src/pandroid/app/src/main/res/mipmap-mdpi/ic_launcher.webp create mode 100644 src/pandroid/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp create mode 100644 src/pandroid/app/src/main/res/mipmap-xhdpi/ic_launcher.webp create mode 100644 src/pandroid/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp create mode 100644 src/pandroid/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp create mode 100644 src/pandroid/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp create mode 100644 src/pandroid/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp create mode 100644 src/pandroid/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp create mode 100644 src/pandroid/app/src/main/res/values-night/themes.xml create mode 100644 src/pandroid/app/src/main/res/values/colors.xml create mode 100644 src/pandroid/app/src/main/res/values/strings.xml create mode 100644 src/pandroid/app/src/main/res/values/themes.xml create mode 100644 src/pandroid/app/src/main/res/xml/backup_rules.xml create mode 100644 src/pandroid/app/src/main/res/xml/data_extraction_rules.xml create mode 100644 src/pandroid/build.gradle.kts create mode 100644 src/pandroid/gradle.properties create mode 100644 src/pandroid/gradle/wrapper/gradle-wrapper.jar create mode 100644 src/pandroid/gradle/wrapper/gradle-wrapper.properties create mode 100755 src/pandroid/gradlew create mode 100644 src/pandroid/gradlew.bat create mode 100644 src/pandroid/settings.gradle.kts diff --git a/CMakeLists.txt b/CMakeLists.txt index c202f80c..2a80b06a 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) +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 "") + 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 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 81d3ac5c..cd6bbdcb 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 00000000..c2a00d7b --- /dev/null +++ b/src/jni_driver.cpp @@ -0,0 +1,11 @@ +#include +#include +#include "emulator.hpp" + +std::unique_ptr emulator; + +extern "C" JNIEXPORT void JNICALL Java_com_panda3ds_pandroid_AlberDriver_Initialize(JNIEnv* env, jobject obj) { + __android_log_print(ANDROID_LOG_INFO, "Panda3DS", "Initializing Alber Driver"); + emulator = std::make_unique(); + __android_log_print(ANDROID_LOG_INFO, "Panda3DS", "Done"); +} \ No newline at end of file diff --git a/src/pandroid/.gitignore b/src/pandroid/.gitignore new file mode 100644 index 00000000..aa724b77 --- /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 00000000..42afabfd --- /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 00000000..ff8debcb --- /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 00000000..481bb434 --- /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 00000000..8047484a --- /dev/null +++ b/src/pandroid/app/src/main/AndroidManifest.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + \ No newline at end of file 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 00000000..55bcd47e --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/AlberDriver.java @@ -0,0 +1,10 @@ +package com.panda3ds.pandroid; + +public class AlberDriver { + + public static native void Initialize(); + + static { + System.loadLibrary("Alber"); + } +} \ No newline at end of file diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/MainActivity.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/MainActivity.java new file mode 100644 index 00000000..e2ab412b --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/MainActivity.java @@ -0,0 +1,16 @@ +package com.panda3ds.pandroid; + +import androidx.appcompat.app.AppCompatActivity; + +import android.os.Bundle; + +public class MainActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + AlberDriver.Initialize(); + } +} \ No newline at end of file diff --git a/src/pandroid/app/src/main/jniLibs/.gitignore b/src/pandroid/app/src/main/jniLibs/.gitignore new file mode 100644 index 00000000..0b469178 --- /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 00000000..e69de29b 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 00000000..e69de29b 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 00000000..07d5da9c --- /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 00000000..2b068d11 --- /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/layout/activity_main.xml b/src/pandroid/app/src/main/res/layout/activity_main.xml new file mode 100644 index 00000000..17eab17b --- /dev/null +++ b/src/pandroid/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/src/pandroid/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/src/pandroid/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 00000000..6f3b755b --- /dev/null +++ b/src/pandroid/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/pandroid/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/src/pandroid/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 00000000..6f3b755b --- /dev/null +++ b/src/pandroid/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/pandroid/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/src/pandroid/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..c209e78ecd372343283f4157dcfd918ec5165bb3 GIT binary patch literal 1404 zcmV-?1%vuhNk&F=1pok7MM6+kP&il$0000G0000-002h-06|PpNX!5L00Dqw+t%{r zzW2vH!KF=w&cMnnN@{whkTw+#mAh0SV?YL=)3MimFYCWp#fpdtz~8$hD5VPuQgtcN zXl<@<#Cme5f5yr2h%@8TWh?)bSK`O z^Z@d={gn7J{iyxL_y_%J|L>ep{dUxUP8a{byupH&!UNR*OutO~0{*T4q5R6@ApLF! z5{w?Z150gC7#>(VHFJZ-^6O@PYp{t!jH(_Z*nzTK4 zkc{fLE4Q3|mA2`CWQ3{8;gxGizgM!zccbdQoOLZc8hThi-IhN90RFT|zlxh3Ty&VG z?Fe{#9RrRnxzsu|Lg2ddugg7k%>0JeD+{XZ7>Z~{=|M+sh1MF7~ zz>To~`~LVQe1nNoR-gEzkpe{Ak^7{{ZBk2i_<+`Bq<^GB!RYG+z)h;Y3+<{zlMUYd zrd*W4w&jZ0%kBuDZ1EW&KLpyR7r2=}fF2%0VwHM4pUs}ZI2egi#DRMYZPek*^H9YK zay4Iy3WXFG(F14xYsoDA|KXgGc5%2DhmQ1gFCkrgHBm!lXG8I5h*uf{rn48Z!_@ z4Bk6TJAB2CKYqPjiX&mWoW>OPFGd$wqroa($ne7EUK;#3VYkXaew%Kh^3OrMhtjYN?XEoY`tRPQsAkH-DSL^QqyN0>^ zmC>{#F14jz4GeW{pJoRpLFa_*GI{?T93^rX7SPQgT@LbLqpNA}<@2wH;q493)G=1Y z#-sCiRNX~qf3KgiFzB3I>4Z%AfS(3$`-aMIBU+6?gbgDb!)L~A)je+;fR0jWLL-Fu z4)P{c7{B4Hp91&%??2$v9iRSFnuckHUm}or9seH6 z>%NbT+5*@L5(I9j@06@(!{ZI?U0=pKn8uwIg&L{JV14+8s2hnvbRrU|hZCd}IJu7*;;ECgO%8_*W Kmw_-CKmY()leWbG literal 0 HcmV?d00001 diff --git a/src/pandroid/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/src/pandroid/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..b2dfe3d1ba5cf3ee31b3ecc1ced89044a1f3b7a9 GIT binary patch literal 2898 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 literal 0 HcmV?d00001 diff --git a/src/pandroid/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/src/pandroid/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..4f0f1d64e58ba64d180ce43ee13bf9a17835fbca GIT binary patch literal 982 zcmV;{11bDcNk&G_0{{S5MM6+kP&il$0000G0000l001ul06|PpNU8t;00Dqo+t#w^ z^1csucXz7-Qrhzl9HuHB%l>&>1tG2^vb*E&k^T3$FG1eQZ51g$uv4V+kI`0<^1Z@N zk?Jjh$olyC%l>)Xq;7!>{iBj&BjJ`P&$fsCfpve_epJOBkTF?nu-B7D!hO=2ZR}

C%4 zc_9eOXvPbC4kzU8YowIA8cW~Uv|eB&yYwAObSwL2vY~UYI7NXPvf3b+c^?wcs~_t{ ze_m66-0)^{JdOMKPwjpQ@Sna!*?$wTZ~su*tNv7o!gXT!GRgivP}ec?5>l1!7<(rT zds|8x(qGc673zrvYIz;J23FG{9nHMnAuP}NpAED^laz3mAN1sy+NXK)!6v1FxQ;lh zOBLA>$~P3r4b*NcqR;y6pwyhZ3_PiDb|%n1gGjl3ZU}ujInlP{eks-#oA6>rh&g+!f`hv#_%JrgYPu z(U^&XLW^QX7F9Z*SRPpQl{B%x)_AMp^}_v~?j7 zapvHMKxSf*Mtyx8I}-<*UGn3)oHd(nn=)BZ`d$lDBwq_GL($_TPaS{UeevT(AJ`p0 z9%+hQb6z)U9qjbuXjg|dExCLjpS8$VKQ55VsIC%@{N5t{NsW)=hNGI`J=x97_kbz@ E0Of=7!TQj4N+cqN`nQhxvX7dAV-`K|Ub$-q+H-5I?Tx0g9jWxd@A|?POE8`3b8fO$T))xP* z(X?&brZw({`)WU&rdAs1iTa0x6F@PIxJ&&L|dpySV!ID|iUhjCcKz(@mE z!x@~W#3H<)4Ae(4eQJRk`Iz3<1)6^m)0b_4_TRZ+cz#eD3f8V;2r-1fE!F}W zEi0MEkTTx}8i1{`l_6vo0(Vuh0HD$I4SjZ=?^?k82R51bC)2D_{y8mi_?X^=U?2|F{Vr7s!k(AZC$O#ZMyavHhlQ7 zUR~QXuH~#o#>(b$u4?s~HLF*3IcF7023AlwAYudn0FV~|odGH^05AYPEfR)8p`i{n zwg3zPVp{+wOsxKc>)(pMupKF!Y2HoUqQ3|Yu|8lwR=?5zZuhG6J?H`bSNk_wPoM{u zSL{c@pY7+c2kck>`^q1^^gR0QB7Y?KUD{vz-uVX~;V-rW)PDcI)$_UjgVV?S?=oLR zf4}zz{#*R_{LkiJ#0RdQLNC^2Vp%JPEUvG9ra2BVZ92(p9h7Ka@!yf9(lj#}>+|u* z;^_?KWdzkM`6gqPo9;;r6&JEa)}R3X{(CWv?NvgLeOTq$cZXqf7|sPImi-7cS8DCN zGf;DVt3Am`>hH3{4-WzH43Ftx)SofNe^-#|0HdCo<+8Qs!}TZP{HH8~z5n`ExcHuT zDL1m&|DVpIy=xsLO>8k92HcmfSKhflQ0H~9=^-{#!I1g(;+44xw~=* zxvNz35vfsQE)@)Zsp*6_GjYD};Squ83<_?^SbALb{a`j<0Gn%6JY!zhp=Fg}Ga2|8 z52e1WU%^L1}15Ex0fF$e@eCT(()_P zvV?CA%#Sy08_U6VPt4EtmVQraWJX` zh=N|WQ>LgrvF~R&qOfB$!%D3cGv?;Xh_z$z7k&s4N)$WYf*k=|*jCEkO19{h_(%W4 zPuOqbCw`SeAX*R}UUsbVsgtuG?xs(#Ikx9`JZoQFz0n*7ZG@Fv@kZk`gzO$HoA9kN z8U5{-yY zvV{`&WKU2$mZeoBmiJrEdzUZAv1sRxpePdg1)F*X^Y)zp^Y*R;;z~vOv-z&)&G)JQ{m!C9cmziu1^nHA z`#`0c>@PnQ9CJKgC5NjJD8HM3|KC(g5nnCq$n0Gsu_DXk36@ql%npEye|?%RmG)

FJ$wK}0tWNB{uH;AM~i literal 0 HcmV?d00001 diff --git a/src/pandroid/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/src/pandroid/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..948a3070fe34c611c42c0d3ad3013a0dce358be0 GIT binary patch literal 1900 zcmV-y2b1_xNk&Fw2LJ$9MM6+kP&il$0000G0001A003VA06|PpNH75a00DqwTbm-~ zullQTcXxO9ki!OCRx^i?oR|n!<8G0=kI^!JSjFi-LL*`V;ET0H2IXfU0*i>o6o6Gy zRq6Ap5(_{XLdXcL-MzlN`ugSdZY_`jXhcENAu)N_0?GhF))9R;E`!bo9p?g?SRgw_ zEXHhFG$0{qYOqhdX<(wE4N@es3VIo$%il%6xP9gjiBri+2pI6aY4 zJbgh-Ud|V%3O!IcHKQx1FQH(_*TK;1>FQWbt^$K1zNn^cczkBs=QHCYZ8b&l!UV{K z{L0$KCf_&KR^}&2Fe|L&?1I7~pBENnCtCuH3sjcx6$c zwqkNkru);ie``q+_QI;IYLD9OV0ZxkuyBz|5<$1BH|vtey$> z5oto4=l-R-Aaq`Dk0}o9N0VrkqW_#;!u{!bJLDq%0092{Ghe=F;(kn} z+sQ@1=UlX30+2nWjkL$B^b!H2^QYO@iFc0{(-~yXj2TWz?VG{v`Jg zg}WyYnwGgn>{HFaG7E~pt=)sOO}*yd(UU-D(E&x{xKEl6OcU?pl)K%#U$dn1mDF19 zSw@l8G!GNFB3c3VVK0?uyqN&utT-D5%NM4g-3@Sii9tSXKtwce~uF zS&Jn746EW^wV~8zdQ1XC28~kXu8+Yo9p!<8h&(Q({J*4DBglPdpe4M_mD8AguZFn~ ztiuO~{6Bx?SfO~_ZV(GIboeR9~hAym{{fV|VM=77MxDrbW6`ujX z<3HF(>Zr;#*uCvC*bpoSr~C$h?_%nXps@A)=l_;({Fo#6Y1+Zv`!T5HB+)#^-Ud_; zBwftPN=d8Vx)*O1Mj+0oO=mZ+NVH*ptNDC-&zZ7Hwho6UQ#l-yNvc0Cm+2$$6YUk2D2t#vdZX-u3>-Be1u9gtTBiMB^xwWQ_rgvGpZ6(C@e23c!^K=>ai-Rqu zhqT`ZQof;9Bu!AD(i^PCbYV%yha9zuoKMp`U^z;3!+&d@Hud&_iy!O-$b9ZLcSRh? z)R|826w}TU!J#X6P%@Zh=La$I6zXa#h!B;{qfug}O%z@K{EZECu6zl)7CiNi%xti0 zB{OKfAj83~iJvmpTU|&q1^?^cIMn2RQ?jeSB95l}{DrEPTW{_gmU_pqTc)h@4T>~& zluq3)GM=xa(#^VU5}@FNqpc$?#SbVsX!~RH*5p0p@w z;~v{QMX0^bFT1!cXGM8K9FP+=9~-d~#TK#ZE{4umGT=;dfvWi?rYj;^l_Zxywze`W z^Cr{55U@*BalS}K%Czii_80e0#0#Zkhlij4-~I@}`-JFJ7$5{>LnoJSs??J8kWVl6|8A}RCGAu9^rAsfCE=2}tHwl93t0C?#+jMpvr7O3`2=tr{Hg$=HlnjVG^ewm|Js0J*kfPa6*GhtB>`fN!m#9J(sU!?(OSfzY*zS(FJ<-Vb zfAIg+`U)YaXv#sY(c--|X zEB+TVyZ%Ie4L$gi#Fc++`h6%vzsS$pjz9aLt+ZL(g;n$Dzy5=m=_TV(3H8^C{r0xd zp#a%}ht55dOq?yhwYPrtp-m1xXp;4X;)NhxxUpgP%XTLmO zcjaFva^}dP3$&sfFTIR_jC=2pHh9kpI@2(6V*GQo7Ws)`j)hd+tr@P~gR*2gO@+1? zG<`_tB+LJuF|SZ9tIec;h%}}6WClT`L>HSW?E{Hp1h^+mlbf_$9zA>!ug>NALJsO{ mU%z=YwVD?}XMya)Bp;vlyE5&E_6!fzx9pwrdz474!~g(M6R?N? literal 0 HcmV?d00001 diff --git a/src/pandroid/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/src/pandroid/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..1b9a6956b3acdc11f40ce2bb3f6efbd845cc243f GIT binary patch literal 3918 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�(p-{x$&=IACIfoAR!za)F-^da-t3#0Dycnp zwO~NVXwXCl;jE<}>%@xz|=8fIJAB?>+E{7)|4l${4ngA3G|=r z2Dyv;VVWSgZx9Wj>qUjleGl3Ei9K4>h!(lPS%8VOG>Xu0%6VDz^O=bjJmuP7>DeUv zrbI}MlHB^^d?{zv6d=@_ZD2lg1&G7UjnVN{1}9WkaM3H~btX0GtSzB+tZ^qRgWo4m z!GmimlG$=wgXCnr6j@m<1gAL46#T~5Bnm=2{^@>|t&`9mkEPddj zAvG~@Tv~TAm2i%VW}R-g(Z0)z-Y|szHr@rk>4MAyG*Ma*7Yh#H7(!-5>DZ@8r;_dx z{prSe<>~099F8vsYd2xff7uAS%7{S)f(|@me3t2$iy&NEc7OUEchp@9A|X;;IA>8!oX+y(BKJ$EzV* znR$z;!L$s7uy@{OT~nG#B!NRraT8(X##Ho!0r_o@gg0CA-9H^;-uE&?$2$nHv_00o z%cbuUc-tCx$Uh&EZ4Nf4Zgqv)Y6>usG3>GeQnxx_Z6+PcbX-+ysbt1hQ`K1LDpOE? zrAhIZhSN9yVIAOa22gn577tbc&i3|3V8NWy&!tw##`}9*x}gtI^h1DzZRA>UuaJG) zaZ7j)dq!O}{?#8Y7~7i6fHh4{`pL?>-18|p!S75Y#^DM>-S3)vuZG+Q7l@ek zQP~#cBpWgg#mApc_sPYjpw8odQuRokmTkzcNl`^CcKB7e&;zViV;{Y{o^Y$%7i0m# z62%#1Lq!RC?}lK>%mp}T!3Xv;L*0v*>USLm``N%>w>@fwC+#T&Tx2bN4w(20JB}oU zuSa6v^kXi0xPs?pbaOHnyiqq6By1EZY9OZ^^QA>{q-Hsd&m`pbQ%8121aWG-F5xf zlZ%;B{;C>X19|`^_?dVyCq>n+41w7|!tUS!{9rHlbhX=SZO5CQ^;!Du_E7*`GiR^Q w)2!4MKjfSAeNo!9>IaV6aUZ*?W>} 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

UmY2RIYtNPVDpE$%vda+HD#3m z&VuXJ{BK&Qe+rBa7eq}Q(bq|tn(RrJAk|ztj2(i{d>nmQnM?;HF2k&9sA6up5tmjl z7lySlzMbifH17-m-Lwa_F&e7nOH?ESi3#ckR3tsM+jsck3`oG!uMS}|eAwVXv>}qxwq?QY%QJ0}r@^;fhuUA9W z*BVl>TGo&N004@xSiwDUXUvp51sVmqO3m)=B55aPwf@0=e}cN+$-BdKxY`YrT_4)0 z_d10#i44Q*rFr8MC>*)v$EJvz``(pb{e&*6k+b zsMz%($|1+8hn8c2?P(l@;Rb&CsZeYoCI3?2!LqjbwPXW3z4G$Qfj=cT5Yb%vY0(AX oeb?AaKtwrnc|$|zzw9vfvn^aJJ!zd)XFXqqy0000001=f@-~a#s literal 0 HcmV?d00001 diff --git a/src/pandroid/app/src/main/res/values-night/themes.xml b/src/pandroid/app/src/main/res/values-night/themes.xml new file mode 100644 index 00000000..98dd320a --- /dev/null +++ b/src/pandroid/app/src/main/res/values-night/themes.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/src/pandroid/app/src/main/res/values/colors.xml b/src/pandroid/app/src/main/res/values/colors.xml new file mode 100644 index 00000000..c8524cd9 --- /dev/null +++ b/src/pandroid/app/src/main/res/values/colors.xml @@ -0,0 +1,5 @@ + + + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/src/pandroid/app/src/main/res/values/strings.xml b/src/pandroid/app/src/main/res/values/strings.xml new file mode 100644 index 00000000..40c0990f --- /dev/null +++ b/src/pandroid/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + pandroid + \ No newline at end of file diff --git a/src/pandroid/app/src/main/res/values/themes.xml b/src/pandroid/app/src/main/res/values/themes.xml new file mode 100644 index 00000000..812da8ea --- /dev/null +++ b/src/pandroid/app/src/main/res/values/themes.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file From c1162678bb5783d4b10b919de86726141019a918 Mon Sep 17 00:00:00 2001 From: offtkp Date: Tue, 28 Nov 2023 13:26:07 +0200 Subject: [PATCH 041/162] Cleanup jni_driver --- patch.patch | 118 ------------------ src/jni_driver.cpp | 78 ++++++------ .../panda3ds/pandroid/app/GameActivity.java | 2 +- 3 files changed, 40 insertions(+), 158 deletions(-) delete mode 100644 patch.patch diff --git a/patch.patch b/patch.patch deleted file mode 100644 index 98cad32b..00000000 --- a/patch.patch +++ /dev/null @@ -1,118 +0,0 @@ -diff --git a/src/host_shaders/opengl_display.frag b/src/host_shaders/opengl_display.frag -index 612671c8..1937f711 100644 ---- a/src/host_shaders/opengl_display.frag -+++ b/src/host_shaders/opengl_display.frag -@@ -1,4 +1,5 @@ --#version 410 core -+#version 300 es -+precision mediump float; - in vec2 UV; - out vec4 FragColor; - -diff --git a/src/host_shaders/opengl_display.vert b/src/host_shaders/opengl_display.vert -index 990e2f80..6917c23c 100644 ---- a/src/host_shaders/opengl_display.vert -+++ b/src/host_shaders/opengl_display.vert -@@ -1,4 +1,4 @@ --#version 410 core -+#version 300 es - out vec2 UV; - - void main() { -diff --git a/src/host_shaders/opengl_fragment_shader.frag b/src/host_shaders/opengl_fragment_shader.frag -index f6fa6c55..b0850438 100644 ---- a/src/host_shaders/opengl_fragment_shader.frag -+++ b/src/host_shaders/opengl_fragment_shader.frag -@@ -1,4 +1,5 @@ --#version 410 core -+#version 300 es -+precision mediump float; - - in vec3 v_tangent; - in vec3 v_normal; -@@ -27,7 +28,7 @@ uniform bool u_depthmapEnable; - uniform sampler2D u_tex0; - uniform sampler2D u_tex1; - uniform sampler2D u_tex2; --uniform sampler1DArray u_tex_lighting_lut; -+// uniform sampler1DArray u_tex_lighting_lut; - - uniform uint u_picaRegs[0x200 - 0x48]; - -@@ -145,9 +146,15 @@ vec4 tevCalculateCombiner(int tev_id) { - #define RR_LUT 6u - - float lutLookup(uint lut, uint light, float value) { -- if (lut >= FR_LUT && lut <= RR_LUT) lut -= 1; -- if (lut == SP_LUT) lut = light + 8; -- return texture(u_tex_lighting_lut, vec2(value, lut)).r; -+ // if (lut >= FR_LUT && lut <= RR_LUT) lut -= 1; -+ // if (lut == SP_LUT) lut = light + 8; -+ // return texture(u_tex_lighting_lut, vec2(value, lut)).r; -+ return 0.0; -+} -+ -+uint bitfieldExtract(uint val, int off, int size) { -+ uint mask = uint((1 << size) - 1); -+ return uint(val >> off) & mask; - } - - vec3 regToColor(uint reg) { -diff --git a/src/host_shaders/opengl_vertex_shader.vert b/src/host_shaders/opengl_vertex_shader.vert -index a25d7a6d..5967ccd6 100644 ---- a/src/host_shaders/opengl_vertex_shader.vert -+++ b/src/host_shaders/opengl_vertex_shader.vert -@@ -1,4 +1,4 @@ --#version 410 core -+#version 300 es - - layout(location = 0) in vec4 a_coords; - layout(location = 1) in vec4 a_quaternion; -@@ -20,7 +20,7 @@ out vec2 v_texcoord2; - flat out vec4 v_textureEnvColor[6]; - flat out vec4 v_textureEnvBufferColor; - --out float gl_ClipDistance[2]; -+// out float gl_ClipDistance[2]; - - // TEV uniforms - uniform uint u_textureEnvColor[6]; -@@ -93,6 +93,6 @@ void main() { - ); - - // There's also another, always-on clipping plane based on vertex z -- gl_ClipDistance[0] = -a_coords.z; -- gl_ClipDistance[1] = dot(clipData, a_coords); -+ // gl_ClipDistance[0] = -a_coords.z; -+ // gl_ClipDistance[1] = dot(clipData, a_coords); - } -diff --git a/third_party/opengl/opengl.hpp b/third_party/opengl/opengl.hpp -index f368f573..5ead7f63 100644 ---- a/third_party/opengl/opengl.hpp -+++ b/third_party/opengl/opengl.hpp -@@ -520,21 +520,21 @@ namespace OpenGL { - static void enableBlend() { glEnable(GL_BLEND); } - static void disableBlend() { glDisable(GL_BLEND); } - static void enableLogicOp() { glEnable(GL_COLOR_LOGIC_OP); } -- static void disableLogicOp() { glDisable(GL_COLOR_LOGIC_OP); } -+ static void disableLogicOp() { /* glDisable(GL_COLOR_LOGIC_OP); */ } - static void enableDepth() { glEnable(GL_DEPTH_TEST); } - static void disableDepth() { glDisable(GL_DEPTH_TEST); } - static void enableStencil() { glEnable(GL_STENCIL_TEST); } - static void disableStencil() { glDisable(GL_STENCIL_TEST); } - -- static void enableClipPlane(GLuint index) { glEnable(GL_CLIP_DISTANCE0 + index); } -- static void disableClipPlane(GLuint index) { glDisable(GL_CLIP_DISTANCE0 + index); } -+ static void enableClipPlane(GLuint index) { /* glEnable(GL_CLIP_DISTANCE0 + index); */ } -+ static void disableClipPlane(GLuint index) { /* glDisable(GL_CLIP_DISTANCE0 + index); */ } - - static void setDepthFunc(DepthFunc func) { glDepthFunc(static_cast(func)); } - static void setColourMask(GLboolean r, GLboolean g, GLboolean b, GLboolean a) { glColorMask(r, g, b, a); } - static void setDepthMask(GLboolean mask) { glDepthMask(mask); } - - // TODO: Add a proper enum for this -- static void setLogicOp(GLenum op) { glLogicOp(op); } -+ static void setLogicOp(GLenum op) { /* glLogicOp(op); */ } - - enum Primitives { - Triangle = GL_TRIANGLES, diff --git a/src/jni_driver.cpp b/src/jni_driver.cpp index 5cd519e6..422096d8 100644 --- a/src/jni_driver.cpp +++ b/src/jni_driver.cpp @@ -1,74 +1,74 @@ -#include -#include -#include #include -#include "renderer_gl/renderer_gl.hpp" +#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()); - __android_log_print(ANDROID_LOG_INFO, "AlberDriver", "OpenGL ES Before %d.%d", GLVersion.major, GLVersion.minor); - if (!gladLoadGLES2Loader(reinterpret_cast(eglGetProcAddress))) { + 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); + + __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(); - - emulator->getServiceManager().getHID().updateInputs(emulator->getTicks()); + 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; - renderer = nullptr; + 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 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); + 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) { - emulator->getServiceManager().getHID().setTouchScreenPress((u16)x, (u16)y); -} - -extern "C" JNIEXPORT void JNICALL Java_com_panda3ds_pandroid_AlberDriver_TouchScreenUp(JNIEnv* env, jobject obj) { - emulator->getServiceManager().getHID().releaseTouchScreen(); + 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) { - emulator->getServiceManager().getHID().releaseKey((u32)keyCode); + hidService->releaseKey((u32)keyCode); } extern "C" JNIEXPORT void JNICALL Java_com_panda3ds_pandroid_AlberDriver_KeyDown(JNIEnv* env, jobject obj, jint keyCode) { - emulator->getServiceManager().getHID().pressKey((u32)keyCode); + hidService->pressKey((u32)keyCode); } extern "C" JNIEXPORT void JNICALL Java_com_panda3ds_pandroid_AlberDriver_SetCirclepadAxis(JNIEnv* env, jobject obj, jint x, jint y) { - emulator->getServiceManager().getHID().setCirclepadX((s16)x); - emulator->getServiceManager().getHID().setCirclepadY((s16)y); + hidService->setCirclepadX((s16)x); + hidService->setCirclepadY((s16)y); } - - 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 index 275797fb..60fe8724 100644 --- 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 @@ -38,7 +38,7 @@ public class GameActivity extends BaseActivity { return; } - pandaSurface = new PandaGlSurfaceView(this, intent.getStringExtra(Constants.EXTRA_PATH));; + pandaSurface = new PandaGlSurfaceView(this, intent.getStringExtra(Constants.EXTRA_PATH)); setContentView(R.layout.game_activity); From 200a884589238d240c63bb3bdea0632a65300cf2 Mon Sep 17 00:00:00 2001 From: offtkp Date: Tue, 28 Nov 2023 14:05:51 +0200 Subject: [PATCH 042/162] Various cleanups --- .../panda3ds/pandroid/app/GameActivity.java | 2 +- .../com/panda3ds/pandroid/math/Vector2.java | 3 ++ .../panda3ds/pandroid/utils/Constants.java | 3 ++ .../pandroid/view/PandaGlRenderer.java | 29 +++++++++---------- .../pandroid/view/PandaGlSurfaceView.java | 4 --- 5 files changed, 21 insertions(+), 20 deletions(-) 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 index 60fe8724..5c234423 100644 --- 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 @@ -33,7 +33,7 @@ public class GameActivity extends BaseActivity { if(!intent.hasExtra(Constants.EXTRA_PATH)){ setContentView(new FrameLayout(this)); - Toast.makeText(this, "INVALID ROM PATH", Toast.LENGTH_LONG).show(); + Toast.makeText(this, "Invalid rom path!", Toast.LENGTH_LONG).show(); finish(); return; } 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 index 8d413203..9c5b45b7 100644 --- 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 @@ -2,9 +2,11 @@ package com.panda3ds.pandroid.math; public class Vector2 { public float x,y; + public Vector2(){ this(0.0f); } + public Vector2(float value){ this(value,value); } @@ -17,6 +19,7 @@ public class Vector2 { 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 index c8ba68cd..2e706530 100644 --- 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 @@ -18,6 +18,9 @@ public class Constants { public static final int INPUT_KEY_START = 1 << 3; public static final int INPUT_KEY_SELECT = 1 << 2; + public static final int N3DS_WIDTH = 400; + public static final int N3DS_HEIGHT = 240; + 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/view/PandaGlRenderer.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/PandaGlRenderer.java index 4adce575..b6be4df2 100644 --- 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 @@ -10,6 +10,7 @@ import android.opengl.GLSurfaceView; import android.util.Log; import com.panda3ds.pandroid.AlberDriver; +import com.panda3ds.pandroid.utils.Constants; import java.util.ArrayList; @@ -69,43 +70,41 @@ public class PandaGlRenderer implements GLSurfaceView.Renderer { AlberDriver.RunFrame(screenFbo); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); glBindFramebuffer(GL_READ_FRAMEBUFFER, screenFbo); + if (screenWidth > screenHeight) { - int topDisplayWidth = (int) ((screenHeight / 240.0) * 400); + int topDisplayWidth = (int)((screenHeight / (float)Constants.N3DS_HEIGHT) * Constants.N3DS_WIDTH); int topDisplayHeight = screenHeight; - if (topDisplayWidth > (screenWidth*0.7)){ - topDisplayWidth = (int) (screenWidth * 0.7); - topDisplayHeight = (int) ((topDisplayWidth/400.0)*240); + if (topDisplayWidth > screenWidth * 0.7){ + topDisplayWidth = (int)(screenWidth * 0.7); + topDisplayHeight = (int)((topDisplayWidth / (float)Constants.N3DS_WIDTH) * Constants.N3DS_HEIGHT); } - int bottomDisplayHeight = (int) (((screenWidth-topDisplayWidth)/320)*240); + int bottomDisplayHeight = (int)(((screenWidth - topDisplayWidth) / 320) * Constants.N3DS_HEIGHT); + int topDisplayY = screenHeight - topDisplayHeight; + int bottomDisplayY = screenHeight - bottomDisplayHeight; - int topDisplayY = screenHeight-topDisplayHeight; - int bottomDisplayY = screenHeight-bottomDisplayHeight; - - glBlitFramebuffer(0, 240, - 400, 480, + glBlitFramebuffer(0, Constants.N3DS_HEIGHT, + Constants.N3DS_WIDTH, Constants.N3DS_HEIGHT * 2, 0, topDisplayY, topDisplayWidth,topDisplayY+topDisplayHeight, GL_COLOR_BUFFER_BIT, GL_LINEAR); glBlitFramebuffer( 40, 0, - 360, 240, + 360, Constants.N3DS_HEIGHT, 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); + int h = (int)((screenWidth / (float)Constants.N3DS_WIDTH) * Constants.N3DS_HEIGHT * 2); + glBlitFramebuffer(0, 0, Constants.N3DS_WIDTH, Constants.N3DS_HEIGHT * 2, 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 index 35805657..3b5694ed 100644 --- 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 @@ -13,8 +13,4 @@ public class PandaGlSurfaceView extends GLSurfaceView { renderer = new PandaGlRenderer(romPath); setRenderer(renderer); } - - public PandaGlRenderer getRenderer() { - return renderer; - } } \ No newline at end of file From 9aded6e55611cc1d950156c7c4262726465a28ec Mon Sep 17 00:00:00 2001 From: offtkp Date: Tue, 28 Nov 2023 18:45:00 +0200 Subject: [PATCH 043/162] Permissions for older phones --- src/jni_driver.cpp | 2 +- src/pandroid/app/src/main/AndroidManifest.xml | 3 +++ .../main/java/com/panda3ds/pandroid/app/MainActivity.java | 8 ++++++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/jni_driver.cpp b/src/jni_driver.cpp index 422096d8..6d3bd104 100644 --- a/src/jni_driver.cpp +++ b/src/jni_driver.cpp @@ -49,8 +49,8 @@ extern "C" JNIEXPORT jboolean JNICALL Java_com_panda3ds_pandroid_AlberDriver_Has 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); + __android_log_print(ANDROID_LOG_INFO, "AlberDriver", "Loading ROM %s, result: %d", pathStr, (int)romLoaded); env->ReleaseStringUTFChars(path, pathStr); } diff --git a/src/pandroid/app/src/main/AndroidManifest.xml b/src/pandroid/app/src/main/AndroidManifest.xml index 01931edc..978edcc5 100644 --- a/src/pandroid/app/src/main/AndroidManifest.xml +++ b/src/pandroid/app/src/main/AndroidManifest.xml @@ -2,6 +2,8 @@ + + Date: Tue, 28 Nov 2023 14:54:10 -0400 Subject: [PATCH 044/162] TouchScreen input (#6) --- .../panda3ds/pandroid/app/GameActivity.java | 20 ++- .../com/panda3ds/pandroid/math/Vector2.java | 14 +- .../pandroid/view/PandaGlRenderer.java | 78 +++++---- .../pandroid/view/PandaGlSurfaceView.java | 36 +++- .../pandroid/view/PandaLayoutController.java | 2 + .../view/controller/ControllerLayout.java | 14 +- .../view/controller/ControllerNode.java | 4 + .../controller/nodes/TouchScreenNodeImpl.java | 39 +++++ .../view/renderer/ConsoleRenderer.java | 9 + .../view/renderer/layout/ConsoleLayout.java | 15 ++ .../renderer/layout/DefaultScreenLayout.java | 81 +++++++++ .../app/src/main/res/layout/game_activity.xml | 161 +++++++++--------- .../app/src/main/res/values/values.xml | 4 + 13 files changed, 349 insertions(+), 128 deletions(-) create mode 100644 src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/controller/nodes/TouchScreenNodeImpl.java create mode 100644 src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/renderer/ConsoleRenderer.java create mode 100644 src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/renderer/layout/ConsoleLayout.java create mode 100644 src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/renderer/layout/DefaultScreenLayout.java create mode 100644 src/pandroid/app/src/main/res/values/values.xml 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 index 5c234423..53fb1805 100644 --- 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 @@ -2,24 +2,20 @@ 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.os.Handler; import android.view.View; import android.view.ViewGroup; +import android.view.WindowManager; 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; @@ -46,11 +42,21 @@ public class GameActivity extends BaseActivity { .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); + findViewById(R.id.overlay_controller) + .setVisibility(isChecked ? View.VISIBLE : View.INVISIBLE); }); + + } + + @Override + protected void onResume() { + super.onResume(); + getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); + getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); } } 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 index 9c5b45b7..541d45e1 100644 --- 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 @@ -2,13 +2,8 @@ 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(Vector2 value){ + this(value.x,value.y); } public Vector2(float x, float y){ @@ -23,4 +18,9 @@ public class Vector2 { public static float distance(float x, float y, float x2, float y2){ return (float) Math.sqrt((x - x2) * (x - x2) + (y - y2) * (y - y2)); } + + public void set(float x, float y) { + this.x = x; + this.y = y; + } } 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 index b6be4df2..e6adbd9f 100644 --- 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 @@ -6,17 +6,19 @@ import javax.microedition.khronos.opengles.GL10; import static android.opengl.GLES32.*; import android.content.res.Resources; +import android.graphics.Rect; import android.opengl.GLSurfaceView; import android.util.Log; import com.panda3ds.pandroid.AlberDriver; -import com.panda3ds.pandroid.utils.Constants; +import com.panda3ds.pandroid.view.renderer.layout.ConsoleLayout; +import com.panda3ds.pandroid.view.renderer.ConsoleRenderer; +import com.panda3ds.pandroid.view.renderer.layout.DefaultScreenLayout; -import java.util.ArrayList; - -public class PandaGlRenderer implements GLSurfaceView.Renderer { +public class PandaGlRenderer implements GLSurfaceView.Renderer, ConsoleRenderer { private final String romPath; + private ConsoleLayout displayLayout; private int screenWidth, screenHeight; private int screenTexture; public int screenFbo; @@ -24,6 +26,10 @@ public class PandaGlRenderer implements GLSurfaceView.Renderer { PandaGlRenderer(String romPath) { super(); this.romPath = romPath; + + screenWidth = Resources.getSystem().getDisplayMetrics().widthPixels; + screenHeight = Resources.getSystem().getDisplayMetrics().heightPixels; + setLayout(new DefaultScreenLayout()); } @Override @@ -40,8 +46,6 @@ public class PandaGlRenderer implements GLSurfaceView.Renderer { 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); @@ -71,40 +75,48 @@ public class PandaGlRenderer implements GLSurfaceView.Renderer { glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); glBindFramebuffer(GL_READ_FRAMEBUFFER, screenFbo); - if (screenWidth > screenHeight) { - int topDisplayWidth = (int)((screenHeight / (float)Constants.N3DS_HEIGHT) * Constants.N3DS_WIDTH); - int topDisplayHeight = screenHeight; + Rect topScreen = displayLayout.getTopDisplayBounds(); + Rect bottomScreen = displayLayout.getBottomDisplayBounds(); - if (topDisplayWidth > screenWidth * 0.7){ - topDisplayWidth = (int)(screenWidth * 0.7); - topDisplayHeight = (int)((topDisplayWidth / (float)Constants.N3DS_WIDTH) * Constants.N3DS_HEIGHT); - } + glBlitFramebuffer( + 0, 480, + 400, 240, + topScreen.left, screenHeight - topScreen.top, + topScreen.right, screenHeight - topScreen.bottom, + GL_COLOR_BUFFER_BIT, GL_LINEAR); - int bottomDisplayHeight = (int)(((screenWidth - topDisplayWidth) / 320) * Constants.N3DS_HEIGHT); - int topDisplayY = screenHeight - topDisplayHeight; - int bottomDisplayY = screenHeight - bottomDisplayHeight; - - glBlitFramebuffer(0, Constants.N3DS_HEIGHT, - Constants.N3DS_WIDTH, Constants.N3DS_HEIGHT * 2, - 0, topDisplayY, - topDisplayWidth,topDisplayY+topDisplayHeight, - GL_COLOR_BUFFER_BIT, GL_LINEAR); - - glBlitFramebuffer( - 40, 0, - 360, Constants.N3DS_HEIGHT, - topDisplayWidth, bottomDisplayY, - screenWidth,bottomDisplayY+bottomDisplayHeight, - GL_COLOR_BUFFER_BIT, GL_LINEAR); - } else { - int h = (int)((screenWidth / (float)Constants.N3DS_WIDTH) * Constants.N3DS_HEIGHT * 2); - glBlitFramebuffer(0, 0, Constants.N3DS_WIDTH, Constants.N3DS_HEIGHT * 2, 0, screenHeight - h, screenWidth, screenHeight, GL_COLOR_BUFFER_BIT, GL_LINEAR); - } + glBlitFramebuffer( + 40, 240, + 360, 0, + bottomScreen.left, screenHeight - bottomScreen.top, + bottomScreen.right, screenHeight - bottomScreen.bottom, + GL_COLOR_BUFFER_BIT, GL_LINEAR); } } public void onSurfaceChanged(GL10 unused, int width, int height) { screenWidth = width; screenHeight = height; + glDisable(GL_SCISSOR_TEST); + + displayLayout.update(screenWidth, screenHeight); + } + + @Override + public void setLayout(ConsoleLayout layout) { + displayLayout = layout; + displayLayout.setTopDisplaySourceSize(400, 240); + displayLayout.setBottomDisplaySourceSize(320, 240); + displayLayout.update(screenWidth, screenHeight); + } + + @Override + public ConsoleLayout getLayout() { + return displayLayout; + } + + @Override + public String getBackendName() { + return "OpenGL"; } } \ 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 index 3b5694ed..9f280393 100644 --- 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 @@ -1,10 +1,22 @@ package com.panda3ds.pandroid.view; import android.content.Context; +import android.graphics.Canvas; import android.opengl.GLSurfaceView; +import android.util.Log; -public class PandaGlSurfaceView extends GLSurfaceView { +import androidx.annotation.NonNull; + +import com.panda3ds.pandroid.math.Vector2; +import com.panda3ds.pandroid.utils.Constants; +import com.panda3ds.pandroid.view.controller.TouchEvent; +import com.panda3ds.pandroid.view.controller.nodes.TouchScreenNodeImpl; +import com.panda3ds.pandroid.view.renderer.ConsoleRenderer; + +public class PandaGlSurfaceView extends GLSurfaceView implements TouchScreenNodeImpl { final PandaGlRenderer renderer; + private int size_width; + private int size_height; public PandaGlSurfaceView(Context context, String romPath) { super(context); @@ -13,4 +25,26 @@ public class PandaGlSurfaceView extends GLSurfaceView { renderer = new PandaGlRenderer(romPath); setRenderer(renderer); } + + public ConsoleRenderer getRenderer() { + return renderer; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + size_width = getMeasuredWidth(); + size_height = getMeasuredHeight(); + } + + @NonNull + @Override + public Vector2 getSize() { + return new Vector2(size_width, size_height); + } + + @Override + public void onTouch(TouchEvent event) { + onTouchScreenPress(renderer, event); + } } \ 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 index 63f328da..e7d9438f 100644 --- 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 @@ -58,5 +58,7 @@ public class PandaLayoutController extends ControllerLayout { .setJoystickListener((joystick, axisX, axisY) -> { AlberDriver.SetCirclepadAxis((int)(axisX*0x9C), (int)(axisY*0x9C)*-1); }); + + refreshChildren(); } } 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 index 97c8eb12..1ef93c89 100644 --- 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 @@ -2,14 +2,18 @@ package com.panda3ds.pandroid.view.controller; import android.content.Context; import android.util.AttributeSet; +import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.RelativeLayout; import com.panda3ds.pandroid.math.Vector2; +import com.panda3ds.pandroid.utils.Constants; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; public class ControllerLayout extends RelativeLayout { @@ -34,8 +38,14 @@ public class ControllerLayout extends RelativeLayout { } public void refreshChildren(){ + ArrayList nodes = new ArrayList<>(); + populateNodesArray(this, nodes); + + //Need Reverse: First view is in back and last view is in front for respect android View hierarchy + Collections.reverse(nodes); + controllerNodes.clear(); - populateNodesArray(this, controllerNodes); + controllerNodes.addAll(nodes); } private void populateNodesArray(ViewGroup group, ArrayList list){ @@ -89,7 +99,7 @@ public class ControllerLayout extends RelativeLayout { 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){ + if(item.isVisible() && x > cx && x < cx+size.x && y > cy && y < cy+size.y){ node = item; break; } 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 index 8b01fa97..a29d08a4 100644 --- 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 @@ -18,6 +18,10 @@ public interface ControllerNode { return new Vector2(position[0], position[1]); } + default boolean isVisible(){ + return ((View)this).isShown(); + } + @NonNull Vector2 getSize(); void onTouch(TouchEvent event); diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/controller/nodes/TouchScreenNodeImpl.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/controller/nodes/TouchScreenNodeImpl.java new file mode 100644 index 00000000..85d1ddd6 --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/controller/nodes/TouchScreenNodeImpl.java @@ -0,0 +1,39 @@ +package com.panda3ds.pandroid.view.controller.nodes; + +import android.graphics.Rect; +import android.util.Log; +import android.view.View; + +import com.panda3ds.pandroid.AlberDriver; +import com.panda3ds.pandroid.R; +import com.panda3ds.pandroid.utils.Constants; +import com.panda3ds.pandroid.view.controller.ControllerNode; +import com.panda3ds.pandroid.view.controller.TouchEvent; +import com.panda3ds.pandroid.view.renderer.ConsoleRenderer; + +public interface TouchScreenNodeImpl extends ControllerNode { + default void onTouchScreenPress(ConsoleRenderer renderer, TouchEvent event){ + + View me = (View) this; + boolean hasDownEvent = me.getTag(R.id.TagEventHasDown) != null && (boolean) me.getTag(R.id.TagEventHasDown); + + Rect bounds = renderer.getLayout().getBottomDisplayBounds();; + + if (event.getX() >= bounds.left && event.getY() >= bounds.top && event.getX() <= bounds.right && event.getY() <= bounds.bottom){ + int x = (int) (event.getX() - bounds.left); + int y = (int) (event.getY() - bounds.top); + + x = Math.round((x/(float)bounds.width())*320); + y = Math.round((y/(float)bounds.height())*240); + + AlberDriver.TouchScreenDown(x,y); + + me.setTag(R.id.TagEventHasDown, true); + } + + if (hasDownEvent && event.getAction() == TouchEvent.ACTION_UP){ + AlberDriver.TouchScreenUp(); + me.setTag(R.id.TagEventHasDown, false); + } + } +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/renderer/ConsoleRenderer.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/renderer/ConsoleRenderer.java new file mode 100644 index 00000000..13ad4d39 --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/renderer/ConsoleRenderer.java @@ -0,0 +1,9 @@ +package com.panda3ds.pandroid.view.renderer; + +import com.panda3ds.pandroid.view.renderer.layout.ConsoleLayout; + +public interface ConsoleRenderer { + void setLayout(ConsoleLayout layout); + ConsoleLayout getLayout(); + String getBackendName(); +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/renderer/layout/ConsoleLayout.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/renderer/layout/ConsoleLayout.java new file mode 100644 index 00000000..781f0abd --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/renderer/layout/ConsoleLayout.java @@ -0,0 +1,15 @@ +package com.panda3ds.pandroid.view.renderer.layout; + +import android.graphics.Rect; + +import com.panda3ds.pandroid.math.Vector2; + +public interface ConsoleLayout { + void update(int screenWidth, int screenHeight); + + void setBottomDisplaySourceSize(int width, int height); + void setTopDisplaySourceSize(int width, int height); + + Rect getBottomDisplayBounds(); + Rect getTopDisplayBounds(); +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/renderer/layout/DefaultScreenLayout.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/renderer/layout/DefaultScreenLayout.java new file mode 100644 index 00000000..a54db8f7 --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/renderer/layout/DefaultScreenLayout.java @@ -0,0 +1,81 @@ +package com.panda3ds.pandroid.view.renderer.layout; + +import android.graphics.Rect; +import android.util.Size; + +import com.panda3ds.pandroid.math.Vector2; + +public class DefaultScreenLayout implements ConsoleLayout { + private final Rect topDisplay = new Rect(); + private final Rect bottomDisplay = new Rect(); + + private final Vector2 screenSize = new Vector2(1.0F, 1.0F); + private final Vector2 topSourceSize = new Vector2(1.0F, 1.0F); + private final Vector2 bottomSourceSize = new Vector2(1.0F, 1.0F); + + @Override + public void update(int screenWidth, int screenHeight) { + screenSize.set(screenWidth, screenHeight); + updateBounds(); + } + + @Override + public void setBottomDisplaySourceSize(int width, int height) { + bottomSourceSize.set(width,height); + updateBounds(); + } + @Override + public void setTopDisplaySourceSize(int width, int height) { + topSourceSize.set(width,height); + updateBounds(); + } + + private void updateBounds(){ + int screenWidth = (int) screenSize.x; + int screenHeight = (int) screenSize.y; + + if (screenWidth > screenHeight){ + + int topDisplayWidth = (int) ((screenHeight / topSourceSize.y) * topSourceSize.x); + int topDisplayHeight = screenHeight; + + if (topDisplayWidth > (screenWidth*0.7)){ + topDisplayWidth = (int) (screenWidth * 0.7); + topDisplayHeight = (int) ((topDisplayWidth/topSourceSize.x)* topSourceSize.y); + } + + int bottomDisplayHeight = (int) (((screenWidth-topDisplayWidth)/ bottomSourceSize.x)* bottomSourceSize.y); + + + topDisplay.set(0, 0, topDisplayWidth, topDisplayHeight); + bottomDisplay.set(topDisplayWidth, 0, topDisplayWidth+(screenWidth-topDisplayWidth), bottomDisplayHeight); + } else { + int topScreenHeight = (int) ((screenWidth/ topSourceSize.x) * topSourceSize.y); + topDisplay.set(0,0,screenWidth,topScreenHeight); + + int bottomDisplayHeight = (int)((screenWidth/ bottomSourceSize.x) * bottomSourceSize.y); + int bottomDisplayWidth = screenWidth; + int bottomDisplayX = 0; + + if (topScreenHeight + bottomDisplayHeight > screenHeight){ + bottomDisplayHeight = (screenHeight-topScreenHeight); + bottomDisplayWidth = (int) ((bottomDisplayHeight/ bottomSourceSize.y)*bottomSourceSize.x); + bottomDisplayX = (screenWidth-bottomDisplayX)/2; + } + + topDisplay.set(0,0, screenWidth,topScreenHeight); + bottomDisplay.set(bottomDisplayX, topScreenHeight, bottomDisplayX+bottomDisplayWidth,topScreenHeight+bottomDisplayHeight); + } + } + + + @Override + public Rect getBottomDisplayBounds() { + return bottomDisplay; + } + + @Override + public Rect getTopDisplayBounds() { + return topDisplay; + } +} diff --git a/src/pandroid/app/src/main/res/layout/game_activity.xml b/src/pandroid/app/src/main/res/layout/game_activity.xml index 64f067b2..6f6f65d7 100644 --- a/src/pandroid/app/src/main/res/layout/game_activity.xml +++ b/src/pandroid/app/src/main/res/layout/game_activity.xml @@ -4,105 +4,110 @@ android:layout_height="match_parent" android:background="#000"> - - - + android:layout_height="match_parent"/> - + + + android:layout_height="match_parent" + android:gravity="bottom" + android:padding="20dp"> - - - - - - + android:layout_height="wrap_content"> - - - - - - - - - - - - - + android:layout_height="25dp" + android:layout_gravity="left" + style="@style/ControllerSimpleButton" + android:background="@drawable/simple_card_button_left" + android:text="L"/> + + + + + + + + + + + + + + + + + android:layout_gravity="bottom|center"> - + - + - + - + + + + diff --git a/src/pandroid/app/src/main/res/values/values.xml b/src/pandroid/app/src/main/res/values/values.xml new file mode 100644 index 00000000..9a4071c7 --- /dev/null +++ b/src/pandroid/app/src/main/res/values/values.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file From a40e74a15d08798ae045bae2b64324949b8e1ac3 Mon Sep 17 00:00:00 2001 From: offtkp Date: Tue, 28 Nov 2023 21:11:58 +0200 Subject: [PATCH 045/162] Remove magic numbers --- .../com/panda3ds/pandroid/utils/Constants.java | 3 ++- .../panda3ds/pandroid/view/PandaGlRenderer.java | 14 +++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) 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 index 2e706530..14d5c398 100644 --- 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 @@ -19,7 +19,8 @@ public class Constants { public static final int INPUT_KEY_SELECT = 1 << 2; public static final int N3DS_WIDTH = 400; - public static final int N3DS_HEIGHT = 240; + public static final int N3DS_HALF_HEIGHT = 240; + public static final int N3DS_FULL_HEIGHT = 480; 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/view/PandaGlRenderer.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/PandaGlRenderer.java index e6adbd9f..0e1826d1 100644 --- 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 @@ -79,15 +79,16 @@ public class PandaGlRenderer implements GLSurfaceView.Renderer, ConsoleRenderer Rect bottomScreen = displayLayout.getBottomDisplayBounds(); glBlitFramebuffer( - 0, 480, - 400, 240, + 0, Constants.N3DS_FULL_HEIGHT, + Constants.N3DS_WIDTH, Constants.N3DS_HALF_HEIGHT, topScreen.left, screenHeight - topScreen.top, topScreen.right, screenHeight - topScreen.bottom, GL_COLOR_BUFFER_BIT, GL_LINEAR); + // Remove the black bars on the bottom screen glBlitFramebuffer( - 40, 240, - 360, 0, + 40, Constants.N3DS_HALF_HEIGHT, + Constants.N3DS_WIDTH - 40, 0, bottomScreen.left, screenHeight - bottomScreen.top, bottomScreen.right, screenHeight - bottomScreen.bottom, GL_COLOR_BUFFER_BIT, GL_LINEAR); @@ -97,7 +98,6 @@ public class PandaGlRenderer implements GLSurfaceView.Renderer, ConsoleRenderer public void onSurfaceChanged(GL10 unused, int width, int height) { screenWidth = width; screenHeight = height; - glDisable(GL_SCISSOR_TEST); displayLayout.update(screenWidth, screenHeight); } @@ -105,8 +105,8 @@ public class PandaGlRenderer implements GLSurfaceView.Renderer, ConsoleRenderer @Override public void setLayout(ConsoleLayout layout) { displayLayout = layout; - displayLayout.setTopDisplaySourceSize(400, 240); - displayLayout.setBottomDisplaySourceSize(320, 240); + displayLayout.setTopDisplaySourceSize(Constants.N3DS_WIDTH, Constants.N3DS_HALF_HEIGHT); + displayLayout.setBottomDisplaySourceSize(Constants.N3DS_WIDTH - 40 - 40, Constants.N3DS_HALF_HEIGHT); displayLayout.update(screenWidth, screenHeight); } From ed0864d24f2f4675275f72beb30a768a1d58c3fb Mon Sep 17 00:00:00 2001 From: offtkp Date: Tue, 28 Nov 2023 21:41:57 +0200 Subject: [PATCH 046/162] Run clang-format on .java files --- src/pandroid/app/.gitignore | 1 - src/pandroid/app/build.gradle.kts | 4 - .../com/panda3ds/pandroid/AlberDriver.java | 29 +-- .../panda3ds/pandroid/app/BaseActivity.java | 3 +- .../panda3ds/pandroid/app/GameActivity.java | 62 +++-- .../panda3ds/pandroid/app/MainActivity.java | 80 +++---- .../com/panda3ds/pandroid/math/Vector2.java | 30 +-- .../panda3ds/pandroid/utils/Constants.java | 35 ++- .../panda3ds/pandroid/utils/PathUtils.java | 156 ++++++------- .../pandroid/view/PandaGlRenderer.java | 176 +++++++------- .../pandroid/view/PandaGlSurfaceView.java | 56 +++-- .../pandroid/view/PandaLayoutController.java | 68 +++--- .../view/controller/ControllerLayout.java | 218 +++++++++--------- .../view/controller/ControllerNode.java | 26 +-- .../pandroid/view/controller/TouchEvent.java | 32 ++- .../listeners/ButtonStateListener.java | 2 +- .../listeners/JoystickListener.java | 2 +- .../controller/nodes/BasicControllerNode.java | 14 +- .../view/controller/nodes/Button.java | 86 ++++--- .../view/controller/nodes/Joystick.java | 155 ++++++------- .../controller/nodes/TouchScreenNodeImpl.java | 37 ++- .../view/renderer/ConsoleRenderer.java | 6 +- .../view/renderer/layout/ConsoleLayout.java | 11 +- .../renderer/layout/DefaultScreenLayout.java | 116 +++++----- 24 files changed, 634 insertions(+), 771 deletions(-) delete mode 100644 src/pandroid/app/.gitignore diff --git a/src/pandroid/app/.gitignore b/src/pandroid/app/.gitignore deleted file mode 100644 index 42afabfd..00000000 --- a/src/pandroid/app/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build \ No newline at end of file diff --git a/src/pandroid/app/build.gradle.kts b/src/pandroid/app/build.gradle.kts index ff8debcb..276eb552 100644 --- a/src/pandroid/app/build.gradle.kts +++ b/src/pandroid/app/build.gradle.kts @@ -36,11 +36,7 @@ android { } 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/src/main/java/com/panda3ds/pandroid/AlberDriver.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/AlberDriver.java index 92d276a2..81bf29a5 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/AlberDriver.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/AlberDriver.java @@ -1,24 +1,19 @@ package com.panda3ds.pandroid; public class AlberDriver { + AlberDriver() { super(); } - 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 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); - 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"); - } + 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 index 597d664b..72926b07 100644 --- 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 @@ -2,5 +2,4 @@ package com.panda3ds.pandroid.app; import androidx.appcompat.app.AppCompatActivity; -public class BaseActivity extends 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 index 53fb1805..e196d0d1 100644 --- 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 @@ -9,54 +9,48 @@ import android.view.WindowManager; import android.widget.CheckBox; import android.widget.FrameLayout; import android.widget.Toast; - import androidx.annotation.Nullable; - import com.panda3ds.pandroid.R; import com.panda3ds.pandroid.utils.Constants; import com.panda3ds.pandroid.view.PandaGlSurfaceView; import com.panda3ds.pandroid.view.PandaLayoutController; public class GameActivity extends BaseActivity { - private PandaGlSurfaceView pandaSurface; - private PandaLayoutController controllerLayout; + private PandaGlSurfaceView pandaSurface; + private PandaLayoutController controllerLayout; - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); - Intent intent = getIntent(); - if(!intent.hasExtra(Constants.EXTRA_PATH)){ + 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; + } - 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)); - pandaSurface = new PandaGlSurfaceView(this, intent.getStringExtra(Constants.EXTRA_PATH)); + setContentView(R.layout.game_activity); - 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)); - ((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 = findViewById(R.id.controller_layout); + controllerLayout.initialize(); - controllerLayout.initialize(); + ((CheckBox) findViewById(R.id.hide_screen_controller)).setOnCheckedChangeListener((buttonView, isChecked) -> { + findViewById(R.id.overlay_controller).setVisibility(isChecked ? View.VISIBLE : View.INVISIBLE); + }); + } - ((CheckBox)findViewById(R.id.hide_screen_controller)) - .setOnCheckedChangeListener((buttonView, isChecked) -> { - findViewById(R.id.overlay_controller) - .setVisibility(isChecked ? View.VISIBLE : View.INVISIBLE); - }); - - } - - @Override - protected void onResume() { - super.onResume(); - getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); - getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); - } + @Override + protected void onResume() { + super.onResume(); + getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); + getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + } } 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 index ef0ac63f..181cfb4e 100644 --- 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 @@ -1,5 +1,7 @@ package com.panda3ds.pandroid.app; +import static android.Manifest.permission.READ_EXTERNAL_STORAGE; +import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE; import static android.provider.Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION; import android.content.Intent; @@ -7,58 +9,50 @@ import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.widget.Toast; - import androidx.appcompat.app.AppCompatActivity; - -import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE; -import static android.Manifest.permission.READ_EXTERNAL_STORAGE; import androidx.core.app.ActivityCompat; - -import com.panda3ds.pandroid.utils.Constants; import com.panda3ds.pandroid.R; +import com.panda3ds.pandroid.utils.Constants; import com.panda3ds.pandroid.utils.PathUtils; public class MainActivity extends BaseActivity { - private static final int PICK_3DS_ROM = 2; - private static final int PERMISSION_REQUEST_CODE = 3; + private static final int PICK_3DS_ROM = 2; + private static final int PERMISSION_REQUEST_CODE = 3; - private void openFile() { - Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); - intent.addCategory(Intent.CATEGORY_OPENABLE); - intent.setType("*/*"); - startActivityForResult(intent, PICK_3DS_ROM); - } + 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); - } - } else { - ActivityCompat.requestPermissions(this, new String[]{READ_EXTERNAL_STORAGE}, PERMISSION_REQUEST_CODE); - ActivityCompat.requestPermissions(this, new String[]{WRITE_EXTERNAL_STORAGE}, PERMISSION_REQUEST_CODE); - } + @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); + } + } else { + ActivityCompat.requestPermissions(this, new String[] {READ_EXTERNAL_STORAGE}, PERMISSION_REQUEST_CODE); + ActivityCompat.requestPermissions(this, new String[] {WRITE_EXTERNAL_STORAGE}, PERMISSION_REQUEST_CODE); + } - setContentView(R.layout.activity_main); + setContentView(R.layout.activity_main); - findViewById(R.id.load_rom).setOnClickListener(v->{ - openFile(); - }); - } + 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); - } - } + @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 index 541d45e1..4208b610 100644 --- 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 @@ -1,26 +1,20 @@ package com.panda3ds.pandroid.math; public class Vector2 { - public float x,y; - public Vector2(Vector2 value){ - this(value.x,value.y); - } + public float x, y; + public Vector2(Vector2 value) { this(value.x, value.y); } - public Vector2(float x, float y){ - this.x = x; - this.y = y; - } + 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 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)); - } + public static float distance(float x, float y, float x2, float y2) { return (float) Math.sqrt((x - x2) * (x - x2) + (y - y2) * (y - y2)); } - public void set(float x, float y) { - this.x = x; - this.y = y; - } + public void set(float x, float y) { + this.x = x; + this.y = y; + } } 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 index 14d5c398..bc389987 100644 --- 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 @@ -1,27 +1,26 @@ 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_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_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_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 int INPUT_KEY_START = 1 << 3; - public static final int INPUT_KEY_SELECT = 1 << 2; + public static final int N3DS_WIDTH = 400; + public static final int N3DS_HALF_HEIGHT = 240; + public static final int N3DS_FULL_HEIGHT = 480; - public static final int N3DS_WIDTH = 400; - public static final int N3DS_HALF_HEIGHT = 240; - public static final int N3DS_FULL_HEIGHT = 480; - - public static final String EXTRA_PATH = "path"; - public static final String LOG_TAG = "Alber"; + 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 index 0a24603c..ccf655bf 100644 --- 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 @@ -10,116 +10,92 @@ 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; - public static String getPath(final Context context, final Uri uri) { + if ("primary".equalsIgnoreCase(type)) { + return Environment.getExternalStorageDirectory() + "/" + split[1]; - // DocumentProvider - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && DocumentsContract.isDocumentUri(context, uri)) { + } else { + if (Environment.isExternalStorageRemovable()) { + storageDefinition = "EXTERNAL_STORAGE"; - if (isExternalStorageDocument(uri)) {// ExternalStorageProvider - final String docId = DocumentsContract.getDocumentId(uri); - final String[] split = docId.split(":"); - final String type = split[0]; - String storageDefinition; + } else { + storageDefinition = "SECONDARY_STORAGE"; + } + return System.getenv(storageDefinition) + "/" + split[1]; + } - if("primary".equalsIgnoreCase(type)){ + } else if (isDownloadsDocument(uri)) { // DownloadsProvider - return Environment.getExternalStorageDirectory() + "/" + split[1]; + final String id = DocumentsContract.getDocumentId(uri); + final Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); - } else { + return getDataColumn(context, contentUri, null, null); - if(Environment.isExternalStorageRemovable()){ - storageDefinition = "EXTERNAL_STORAGE"; + } else if (isMediaDocument(uri)) { // MediaProvider + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; - } else{ - storageDefinition = "SECONDARY_STORAGE"; - } + 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; + } - return System.getenv(storageDefinition) + "/" + split[1]; - } - - } else if (isDownloadsDocument(uri)) {// DownloadsProvider + final String selection = "_id=?"; + final String[] selectionArgs = new String[] {split[1]}; - final String id = DocumentsContract.getDocumentId(uri); - final Uri contentUri = ContentUris.withAppendedId( - Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); + return getDataColumn(context, contentUri, selection, selectionArgs); + } - return getDataColumn(context, contentUri, null, null); + } else if ("content".equalsIgnoreCase(uri.getScheme())) { // MediaStore (and general) - } else if (isMediaDocument(uri)) {// MediaProvider - final String docId = DocumentsContract.getDocumentId(uri); - final String[] split = docId.split(":"); - final String type = split[0]; + // Return the remote address + if (isGooglePhotosUri(uri)) return uri.getLastPathSegment(); - 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; - } + return getDataColumn(context, uri, null, null); - final String selection = "_id=?"; - final String[] selectionArgs = new String[]{ - split[1] - }; + } else if ("file".equalsIgnoreCase(uri.getScheme())) { // File + return uri.getPath(); + } - return getDataColumn(context, contentUri, selection, selectionArgs); - } + return null; + } - } else if ("content".equalsIgnoreCase(uri.getScheme())) {// MediaStore (and general) + public static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) { + Cursor cursor = null; + final String column = "_data"; + final String[] projection = {column}; - // Return the remote address - if (isGooglePhotosUri(uri)) - return uri.getLastPathSegment(); + 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; + } - return getDataColumn(context, uri, null, null); + public static boolean isExternalStorageDocument(Uri uri) { return "com.android.externalstorage.documents".equals(uri.getAuthority()); } - } else if ("file".equalsIgnoreCase(uri.getScheme())) {// File - return uri.getPath(); - } + public static boolean isDownloadsDocument(Uri uri) { return "com.android.providers.downloads.documents".equals(uri.getAuthority()); } - return null; - } + public static boolean isMediaDocument(Uri uri) { return "com.android.providers.media.documents".equals(uri.getAuthority()); } - 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()); - } + 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 index 0e1826d1..b21e3353 100644 --- 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 @@ -1,122 +1,116 @@ 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.graphics.Rect; import android.opengl.GLSurfaceView; import android.util.Log; - import com.panda3ds.pandroid.AlberDriver; -import com.panda3ds.pandroid.view.renderer.layout.ConsoleLayout; +import com.panda3ds.pandroid.utils.Constants; import com.panda3ds.pandroid.view.renderer.ConsoleRenderer; +import com.panda3ds.pandroid.view.renderer.layout.ConsoleLayout; import com.panda3ds.pandroid.view.renderer.layout.DefaultScreenLayout; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.opengles.GL10; public class PandaGlRenderer implements GLSurfaceView.Renderer, ConsoleRenderer { + private final String romPath; + private ConsoleLayout displayLayout; + private int screenWidth, screenHeight; + private int screenTexture; + public int screenFbo; - private final String romPath; - private ConsoleLayout displayLayout; - private int screenWidth, screenHeight; - private int screenTexture; - public int screenFbo; + PandaGlRenderer(String romPath) { + super(); + this.romPath = romPath; - PandaGlRenderer(String romPath) { - super(); - this.romPath = romPath; + screenWidth = Resources.getSystem().getDisplayMetrics().widthPixels; + screenHeight = Resources.getSystem().getDisplayMetrics().heightPixels; + setLayout(new DefaultScreenLayout()); + } - screenWidth = Resources.getSystem().getDisplayMetrics().widthPixels; - screenHeight = Resources.getSystem().getDisplayMetrics().heightPixels; - setLayout(new DefaultScreenLayout()); - } + @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(); + } - @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)); - public void onSurfaceCreated(GL10 unused, EGLConfig config) { - Log.i("pandroid", glGetString(GL_EXTENSIONS)); - Log.w("pandroid", glGetString(GL_VERSION)); + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); - 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); - 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); - 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); + } - 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); - public void onDrawFrame(GL10 unused) { - if (AlberDriver.HasRomLoaded()) { - AlberDriver.RunFrame(screenFbo); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - glBindFramebuffer(GL_READ_FRAMEBUFFER, screenFbo); + Rect topScreen = displayLayout.getTopDisplayBounds(); + Rect bottomScreen = displayLayout.getBottomDisplayBounds(); - Rect topScreen = displayLayout.getTopDisplayBounds(); - Rect bottomScreen = displayLayout.getBottomDisplayBounds(); + glBlitFramebuffer( + 0, Constants.N3DS_FULL_HEIGHT, Constants.N3DS_WIDTH, Constants.N3DS_HALF_HEIGHT, topScreen.left, screenHeight - topScreen.top, + topScreen.right, screenHeight - topScreen.bottom, GL_COLOR_BUFFER_BIT, GL_LINEAR + ); - glBlitFramebuffer( - 0, Constants.N3DS_FULL_HEIGHT, - Constants.N3DS_WIDTH, Constants.N3DS_HALF_HEIGHT, - topScreen.left, screenHeight - topScreen.top, - topScreen.right, screenHeight - topScreen.bottom, - GL_COLOR_BUFFER_BIT, GL_LINEAR); + // Remove the black bars on the bottom screen + glBlitFramebuffer( + 40, Constants.N3DS_HALF_HEIGHT, Constants.N3DS_WIDTH - 40, 0, bottomScreen.left, screenHeight - bottomScreen.top, bottomScreen.right, + screenHeight - bottomScreen.bottom, GL_COLOR_BUFFER_BIT, GL_LINEAR + ); + } + } - // Remove the black bars on the bottom screen - glBlitFramebuffer( - 40, Constants.N3DS_HALF_HEIGHT, - Constants.N3DS_WIDTH - 40, 0, - bottomScreen.left, screenHeight - bottomScreen.top, - bottomScreen.right, screenHeight - bottomScreen.bottom, - GL_COLOR_BUFFER_BIT, GL_LINEAR); - } - } + public void onSurfaceChanged(GL10 unused, int width, int height) { + screenWidth = width; + screenHeight = height; - public void onSurfaceChanged(GL10 unused, int width, int height) { - screenWidth = width; - screenHeight = height; + displayLayout.update(screenWidth, screenHeight); + } - displayLayout.update(screenWidth, screenHeight); - } + @Override + public void setLayout(ConsoleLayout layout) { + displayLayout = layout; + displayLayout.setTopDisplaySourceSize(Constants.N3DS_WIDTH, Constants.N3DS_HALF_HEIGHT); + displayLayout.setBottomDisplaySourceSize(Constants.N3DS_WIDTH - 40 - 40, Constants.N3DS_HALF_HEIGHT); + displayLayout.update(screenWidth, screenHeight); + } - @Override - public void setLayout(ConsoleLayout layout) { - displayLayout = layout; - displayLayout.setTopDisplaySourceSize(Constants.N3DS_WIDTH, Constants.N3DS_HALF_HEIGHT); - displayLayout.setBottomDisplaySourceSize(Constants.N3DS_WIDTH - 40 - 40, Constants.N3DS_HALF_HEIGHT); - displayLayout.update(screenWidth, screenHeight); - } + @Override + public ConsoleLayout getLayout() { + return displayLayout; + } - @Override - public ConsoleLayout getLayout() { - return displayLayout; - } - - @Override - public String getBackendName() { - return "OpenGL"; - } + @Override + public String getBackendName() { + return "OpenGL"; + } } \ 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 index 9f280393..f5a19efe 100644 --- 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 @@ -4,9 +4,7 @@ import android.content.Context; import android.graphics.Canvas; import android.opengl.GLSurfaceView; import android.util.Log; - import androidx.annotation.NonNull; - import com.panda3ds.pandroid.math.Vector2; import com.panda3ds.pandroid.utils.Constants; import com.panda3ds.pandroid.view.controller.TouchEvent; @@ -14,37 +12,35 @@ import com.panda3ds.pandroid.view.controller.nodes.TouchScreenNodeImpl; import com.panda3ds.pandroid.view.renderer.ConsoleRenderer; public class PandaGlSurfaceView extends GLSurfaceView implements TouchScreenNodeImpl { - final PandaGlRenderer renderer; - private int size_width; - private int size_height; + final PandaGlRenderer renderer; + private int size_width; + private int size_height; - public PandaGlSurfaceView(Context context, String romPath) { - super(context); - setEGLContextClientVersion(3); - setDebugFlags(DEBUG_LOG_GL_CALLS); - renderer = new PandaGlRenderer(romPath); - setRenderer(renderer); - } + public PandaGlSurfaceView(Context context, String romPath) { + super(context); + setEGLContextClientVersion(3); + setDebugFlags(DEBUG_LOG_GL_CALLS); + renderer = new PandaGlRenderer(romPath); + setRenderer(renderer); + } - public ConsoleRenderer getRenderer() { - return renderer; - } + public ConsoleRenderer getRenderer() { return renderer; } - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - size_width = getMeasuredWidth(); - size_height = getMeasuredHeight(); - } + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + size_width = getMeasuredWidth(); + size_height = getMeasuredHeight(); + } - @NonNull - @Override - public Vector2 getSize() { - return new Vector2(size_width, size_height); - } + @NonNull + @Override + public Vector2 getSize() { + return new Vector2(size_width, size_height); + } - @Override - public void onTouch(TouchEvent event) { - onTouchScreenPress(renderer, event); - } + @Override + public void onTouch(TouchEvent event) { + onTouchScreenPress(renderer, event); + } } \ 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 index e7d9438f..543ab840 100644 --- 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 @@ -2,7 +2,6 @@ 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; @@ -11,54 +10,41 @@ 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) { super(context); } - public PandaLayoutController(Context context, AttributeSet attrs) { - super(context, attrs); - } + 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) { super(context, attrs, defStyleAttr); } - public PandaLayoutController(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - } + 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, + 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_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_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 - }; + 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); - }); - } + 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); - }); + ((Joystick) findViewById(R.id.left_analog)).setJoystickListener((joystick, axisX, axisY) -> { + AlberDriver.SetCirclepadAxis((int) (axisX * 0x9C), (int) (axisY * 0x9C) * -1); + }); - refreshChildren(); - } + refreshChildren(); + } } 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 index 1ef93c89..137bd312 100644 --- 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 @@ -7,150 +7,140 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.RelativeLayout; - import com.panda3ds.pandroid.math.Vector2; import com.panda3ds.pandroid.utils.Constants; - import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; public class ControllerLayout extends RelativeLayout { + private final HashMap activeTouchEvents = new HashMap<>(); + private final ArrayList controllerNodes = new ArrayList<>(); - private final HashMap activeTouchEvents = new HashMap<>(); - private final ArrayList controllerNodes = new ArrayList<>(); + public ControllerLayout(Context context) { this(context, null); } - public ControllerLayout(Context context) { - this(context,null); - } + public ControllerLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } - 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) { - this(context, attrs, defStyleAttr,0 ); - } + public ControllerLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } - public ControllerLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - } + public void refreshChildren() { + ArrayList nodes = new ArrayList<>(); + populateNodesArray(this, nodes); - public void refreshChildren(){ - ArrayList nodes = new ArrayList<>(); - populateNodesArray(this, nodes); + // Need Reverse: First view is in back and last view is in front for respect android View hierarchy + Collections.reverse(nodes); - //Need Reverse: First view is in back and last view is in front for respect android View hierarchy - Collections.reverse(nodes); + controllerNodes.clear(); + controllerNodes.addAll(nodes); + } - controllerNodes.clear(); - controllerNodes.addAll(nodes); - } + 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); + } + } + } - 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(); - @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; + } - 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); - private void processTouch(boolean up, float x, float y, int index){ - int[] globalPosition = new int[2]; - getLocationInWindow(globalPosition); + int action = TouchEvent.ACTION_MOVE; - 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(); - 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 (item.isVisible() && 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; + } + } - float cx = (pos.x - globalPosition[0]); - float cy = (pos.y - globalPosition[1]); - if(item.isVisible() && 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; - if (up) action = TouchEvent.ACTION_UP; + ControllerNode node = activeTouchEvents.get(index); + Vector2 pos = node.getPosition(); + pos.x -= globalPosition[0]; + pos.y -= globalPosition[1]; - ControllerNode node = activeTouchEvents.get(index); - Vector2 pos = node.getPosition(); - pos.x -= globalPosition[0]; - pos.y -= globalPosition[1]; + x -= pos.x; + y -= pos.y; - x -= pos.x; - y -= pos.y; + node.onTouch(new TouchEvent(x, y, action)); - node.onTouch(new TouchEvent(x,y,action)); + if (up) { + activeTouchEvents.remove(index); + } + } - if(up){ - activeTouchEvents.remove(index); - } - } + @Override + public void onViewAdded(View child) { + super.onViewAdded(child); + refreshChildren(); + } - @Override - public void onViewAdded(View child) { - super.onViewAdded(child); - refreshChildren(); - } + @Override + public void onViewRemoved(View child) { + super.onViewRemoved(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*/ - /*@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; - } + @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 index a29d08a4..e5b8795e 100644 --- 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 @@ -1,28 +1,22 @@ 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; - @NonNull - default Vector2 getPosition(){ - View me = (View) this; + int[] position = new int[2]; + me.getLocationInWindow(position); + return new Vector2(position[0], position[1]); + } - int[] position = new int[2]; - me.getLocationInWindow(position); - return new Vector2(position[0], position[1]); - } + default boolean isVisible() { return ((View) this).isShown(); } - default boolean isVisible(){ - return ((View)this).isShown(); - } + @NonNull Vector2 getSize(); - @NonNull Vector2 getSize(); - - void onTouch(TouchEvent event); + 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 index a804cda1..d3b40db9 100644 --- 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 @@ -1,28 +1,22 @@ 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; + 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; + private final int action; + private final float x, y; - public float getX() { - return x; - } + public float getX() { return x; } - public float getY() { - return y; - } + public float getY() { return y; } - public int getAction() { - return action; - } + public int getAction() { return action; } - public TouchEvent(float x, float y, int action){ - this.x = x; - this.y = y; - this.action = 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 index a658cdc0..eb5a693a 100644 --- 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 @@ -3,5 +3,5 @@ package com.panda3ds.pandroid.view.controller.listeners; import com.panda3ds.pandroid.view.controller.nodes.Button; public interface ButtonStateListener { - void onButtonPressedChange(Button button, boolean pressed); + 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 index 538e2cd8..77225223 100644 --- 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 @@ -3,5 +3,5 @@ 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); + 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 index 90d5f5f7..224be9a4 100644 --- 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 @@ -2,23 +2,15 @@ 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) { super(context); } - public BasicControllerNode(@NonNull Context context, @Nullable AttributeSet attrs) { - super(context, attrs); - } + 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); - } + 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 index 7e8d4e86..4dadc22b 100644 --- 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 @@ -5,69 +5,63 @@ 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 boolean pressed = false; + private int width, height; - private ButtonStateListener stateListener; + private ButtonStateListener stateListener; - public Button(@NonNull Context context) { - super(context); - init(); - } + 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) { + super(context, attrs); + init(); + } - public Button(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - 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(); - } + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + width = getWidth(); + height = getHeight(); + } - private void init(){ - setTextAlignment(TEXT_ALIGNMENT_CENTER); - setGravity(Gravity.CENTER); - } + private void init() { + setTextAlignment(TEXT_ALIGNMENT_CENTER); + setGravity(Gravity.CENTER); + } - public void setStateListener(ButtonStateListener stateListener) { - this.stateListener = stateListener; - } + public void setStateListener(ButtonStateListener stateListener) { this.stateListener = stateListener; } - public boolean isPressed() { - return pressed; - } + public boolean isPressed() { return pressed; } - @NonNull - @Override - public Vector2 getSize() { - return new Vector2(width,height); - } + @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); - } - } + @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 index 757ec3cb..df487bd8 100644 --- 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 @@ -6,127 +6,110 @@ 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 float stick_x = 0; + private float stick_y = 0; - private int size_width = 0; - private int size_height= 0; + private int size_width = 0; + private int size_height = 0; - private JoystickListener joystickListener; + private JoystickListener joystickListener; - public Joystick(Context context) { - this(context,null); - } + public Joystick(Context context) { this(context, null); } - public Joystick(Context context, AttributeSet attrs) { - this(context, attrs,0); - } + public Joystick(Context context, AttributeSet attrs) { this(context, attrs, 0); } - public Joystick(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); + public Joystick(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); - paint.setColor(Color.RED); - invalidate(); - } + paint.setColor(Color.RED); + invalidate(); + } - private final Paint paint = new Paint(); + private final Paint paint = new Paint(); - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - } + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + } - @Override - public void onDrawForeground(Canvas canvas) { - size_width = getWidth(); - size_height = getHeight(); + @Override + public void onDrawForeground(Canvas canvas) { + size_width = getWidth(); + size_height = getHeight(); - int analogIconSize = size_width-getPaddingLeft(); + int analogIconSize = size_width - getPaddingLeft(); - float middleIconSize = analogIconSize / 2.0F; - float middle = size_width / 2.0F; + float middleIconSize = analogIconSize / 2.0F; + float middle = size_width / 2.0F; - float maxDistance = (middle - middleIconSize) * 0.9F; + float maxDistance = (middle - middleIconSize) * 0.9F; - float tx = maxDistance * stick_x; - float ty = maxDistance * stick_y; + 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); + 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)); + 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)); + 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; - 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))); } - 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 void setJoystickListener(JoystickListener joystickListener) { this.joystickListener = joystickListener; } - 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)) - ); - } + @NonNull + @Override + public Vector2 getSize() { + return new Vector2(size_width, size_height); + } - public void setJoystickListener(JoystickListener joystickListener) { - this.joystickListener = joystickListener; - } + @Override + public void onTouch(TouchEvent event) { + float middle = size_width / 2.0F; - @NonNull - @Override - public Vector2 getSize() { - return new Vector2(size_width,size_height); - } + float x = event.getX(); + float y = event.getY(); - @Override - public void onTouch(TouchEvent event) { + x = Math.max(0, Math.min(middle * 2, x)); + y = Math.max(0, Math.min(middle * 2, y)); - float middle = size_width/2.0F; + stick_x = ((x - middle) / middle); - float x = event.getX(); - float y = event.getY(); + stick_y = ((y - middle) / middle); - x = Math.max(0, Math.min(middle*2, x)); - y = Math.max(0, Math.min(middle*2, y)); + if (event.getAction() == TouchEvent.ACTION_UP) { + stick_x = 0; + stick_y = 0; + } - stick_x = ((x-middle)/middle); + if (joystickListener != null) { + joystickListener.onJoystickAxisChange(this, stick_x, stick_y); + } - 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(); - } + invalidate(); + } } diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/controller/nodes/TouchScreenNodeImpl.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/controller/nodes/TouchScreenNodeImpl.java index 85d1ddd6..1009d29c 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/controller/nodes/TouchScreenNodeImpl.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/controller/nodes/TouchScreenNodeImpl.java @@ -3,7 +3,6 @@ package com.panda3ds.pandroid.view.controller.nodes; import android.graphics.Rect; import android.util.Log; import android.view.View; - import com.panda3ds.pandroid.AlberDriver; import com.panda3ds.pandroid.R; import com.panda3ds.pandroid.utils.Constants; @@ -12,28 +11,28 @@ import com.panda3ds.pandroid.view.controller.TouchEvent; import com.panda3ds.pandroid.view.renderer.ConsoleRenderer; public interface TouchScreenNodeImpl extends ControllerNode { - default void onTouchScreenPress(ConsoleRenderer renderer, TouchEvent event){ + default void onTouchScreenPress(ConsoleRenderer renderer, TouchEvent event) { + View me = (View) this; + boolean hasDownEvent = me.getTag(R.id.TagEventHasDown) != null && (boolean) me.getTag(R.id.TagEventHasDown); - View me = (View) this; - boolean hasDownEvent = me.getTag(R.id.TagEventHasDown) != null && (boolean) me.getTag(R.id.TagEventHasDown); + Rect bounds = renderer.getLayout().getBottomDisplayBounds(); + ; - Rect bounds = renderer.getLayout().getBottomDisplayBounds();; + if (event.getX() >= bounds.left && event.getY() >= bounds.top && event.getX() <= bounds.right && event.getY() <= bounds.bottom) { + int x = (int) (event.getX() - bounds.left); + int y = (int) (event.getY() - bounds.top); - if (event.getX() >= bounds.left && event.getY() >= bounds.top && event.getX() <= bounds.right && event.getY() <= bounds.bottom){ - int x = (int) (event.getX() - bounds.left); - int y = (int) (event.getY() - bounds.top); + x = Math.round((x / (float) bounds.width()) * 320); + y = Math.round((y / (float) bounds.height()) * 240); - x = Math.round((x/(float)bounds.width())*320); - y = Math.round((y/(float)bounds.height())*240); + AlberDriver.TouchScreenDown(x, y); - AlberDriver.TouchScreenDown(x,y); + me.setTag(R.id.TagEventHasDown, true); + } - me.setTag(R.id.TagEventHasDown, true); - } - - if (hasDownEvent && event.getAction() == TouchEvent.ACTION_UP){ - AlberDriver.TouchScreenUp(); - me.setTag(R.id.TagEventHasDown, false); - } - } + if (hasDownEvent && event.getAction() == TouchEvent.ACTION_UP) { + AlberDriver.TouchScreenUp(); + me.setTag(R.id.TagEventHasDown, false); + } + } } diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/renderer/ConsoleRenderer.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/renderer/ConsoleRenderer.java index 13ad4d39..d5c99e8d 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/renderer/ConsoleRenderer.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/renderer/ConsoleRenderer.java @@ -3,7 +3,7 @@ package com.panda3ds.pandroid.view.renderer; import com.panda3ds.pandroid.view.renderer.layout.ConsoleLayout; public interface ConsoleRenderer { - void setLayout(ConsoleLayout layout); - ConsoleLayout getLayout(); - String getBackendName(); + void setLayout(ConsoleLayout layout); + ConsoleLayout getLayout(); + String getBackendName(); } diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/renderer/layout/ConsoleLayout.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/renderer/layout/ConsoleLayout.java index 781f0abd..dc83c59f 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/renderer/layout/ConsoleLayout.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/renderer/layout/ConsoleLayout.java @@ -1,15 +1,14 @@ package com.panda3ds.pandroid.view.renderer.layout; import android.graphics.Rect; - import com.panda3ds.pandroid.math.Vector2; public interface ConsoleLayout { - void update(int screenWidth, int screenHeight); + void update(int screenWidth, int screenHeight); - void setBottomDisplaySourceSize(int width, int height); - void setTopDisplaySourceSize(int width, int height); + void setBottomDisplaySourceSize(int width, int height); + void setTopDisplaySourceSize(int width, int height); - Rect getBottomDisplayBounds(); - Rect getTopDisplayBounds(); + Rect getBottomDisplayBounds(); + Rect getTopDisplayBounds(); } diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/renderer/layout/DefaultScreenLayout.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/renderer/layout/DefaultScreenLayout.java index a54db8f7..d8616167 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/renderer/layout/DefaultScreenLayout.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/renderer/layout/DefaultScreenLayout.java @@ -2,80 +2,76 @@ package com.panda3ds.pandroid.view.renderer.layout; import android.graphics.Rect; import android.util.Size; - import com.panda3ds.pandroid.math.Vector2; public class DefaultScreenLayout implements ConsoleLayout { - private final Rect topDisplay = new Rect(); - private final Rect bottomDisplay = new Rect(); + private final Rect topDisplay = new Rect(); + private final Rect bottomDisplay = new Rect(); - private final Vector2 screenSize = new Vector2(1.0F, 1.0F); - private final Vector2 topSourceSize = new Vector2(1.0F, 1.0F); - private final Vector2 bottomSourceSize = new Vector2(1.0F, 1.0F); + private final Vector2 screenSize = new Vector2(1.0F, 1.0F); + private final Vector2 topSourceSize = new Vector2(1.0F, 1.0F); + private final Vector2 bottomSourceSize = new Vector2(1.0F, 1.0F); - @Override - public void update(int screenWidth, int screenHeight) { - screenSize.set(screenWidth, screenHeight); - updateBounds(); - } + @Override + public void update(int screenWidth, int screenHeight) { + screenSize.set(screenWidth, screenHeight); + updateBounds(); + } - @Override - public void setBottomDisplaySourceSize(int width, int height) { - bottomSourceSize.set(width,height); - updateBounds(); - } - @Override - public void setTopDisplaySourceSize(int width, int height) { - topSourceSize.set(width,height); - updateBounds(); - } + @Override + public void setBottomDisplaySourceSize(int width, int height) { + bottomSourceSize.set(width, height); + updateBounds(); + } + @Override + public void setTopDisplaySourceSize(int width, int height) { + topSourceSize.set(width, height); + updateBounds(); + } - private void updateBounds(){ - int screenWidth = (int) screenSize.x; - int screenHeight = (int) screenSize.y; + private void updateBounds() { + int screenWidth = (int) screenSize.x; + int screenHeight = (int) screenSize.y; - if (screenWidth > screenHeight){ + if (screenWidth > screenHeight) { + int topDisplayWidth = (int) ((screenHeight / topSourceSize.y) * topSourceSize.x); + int topDisplayHeight = screenHeight; - int topDisplayWidth = (int) ((screenHeight / topSourceSize.y) * topSourceSize.x); - int topDisplayHeight = screenHeight; + if (topDisplayWidth > (screenWidth * 0.7)) { + topDisplayWidth = (int) (screenWidth * 0.7); + topDisplayHeight = (int) ((topDisplayWidth / topSourceSize.x) * topSourceSize.y); + } - if (topDisplayWidth > (screenWidth*0.7)){ - topDisplayWidth = (int) (screenWidth * 0.7); - topDisplayHeight = (int) ((topDisplayWidth/topSourceSize.x)* topSourceSize.y); - } + int bottomDisplayHeight = (int) (((screenWidth - topDisplayWidth) / bottomSourceSize.x) * bottomSourceSize.y); - int bottomDisplayHeight = (int) (((screenWidth-topDisplayWidth)/ bottomSourceSize.x)* bottomSourceSize.y); + topDisplay.set(0, 0, topDisplayWidth, topDisplayHeight); + bottomDisplay.set(topDisplayWidth, 0, topDisplayWidth + (screenWidth - topDisplayWidth), bottomDisplayHeight); + } else { + int topScreenHeight = (int) ((screenWidth / topSourceSize.x) * topSourceSize.y); + topDisplay.set(0, 0, screenWidth, topScreenHeight); + int bottomDisplayHeight = (int) ((screenWidth / bottomSourceSize.x) * bottomSourceSize.y); + int bottomDisplayWidth = screenWidth; + int bottomDisplayX = 0; - topDisplay.set(0, 0, topDisplayWidth, topDisplayHeight); - bottomDisplay.set(topDisplayWidth, 0, topDisplayWidth+(screenWidth-topDisplayWidth), bottomDisplayHeight); - } else { - int topScreenHeight = (int) ((screenWidth/ topSourceSize.x) * topSourceSize.y); - topDisplay.set(0,0,screenWidth,topScreenHeight); + if (topScreenHeight + bottomDisplayHeight > screenHeight) { + bottomDisplayHeight = (screenHeight - topScreenHeight); + bottomDisplayWidth = (int) ((bottomDisplayHeight / bottomSourceSize.y) * bottomSourceSize.x); + bottomDisplayX = (screenWidth - bottomDisplayX) / 2; + } - int bottomDisplayHeight = (int)((screenWidth/ bottomSourceSize.x) * bottomSourceSize.y); - int bottomDisplayWidth = screenWidth; - int bottomDisplayX = 0; + topDisplay.set(0, 0, screenWidth, topScreenHeight); + bottomDisplay.set(bottomDisplayX, topScreenHeight, bottomDisplayX + bottomDisplayWidth, topScreenHeight + bottomDisplayHeight); + } + } - if (topScreenHeight + bottomDisplayHeight > screenHeight){ - bottomDisplayHeight = (screenHeight-topScreenHeight); - bottomDisplayWidth = (int) ((bottomDisplayHeight/ bottomSourceSize.y)*bottomSourceSize.x); - bottomDisplayX = (screenWidth-bottomDisplayX)/2; - } + @Override + public Rect getBottomDisplayBounds() { + return bottomDisplay; + } - topDisplay.set(0,0, screenWidth,topScreenHeight); - bottomDisplay.set(bottomDisplayX, topScreenHeight, bottomDisplayX+bottomDisplayWidth,topScreenHeight+bottomDisplayHeight); - } - } - - - @Override - public Rect getBottomDisplayBounds() { - return bottomDisplay; - } - - @Override - public Rect getTopDisplayBounds() { - return topDisplay; - } + @Override + public Rect getTopDisplayBounds() { + return topDisplay; + } } From ba3db24f3024349ba5d088703fb6da7be8a25609 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Wed, 6 Dec 2023 02:39:55 +0200 Subject: [PATCH 047/162] [Kernel] Fix WaitSynchronization1 ABI --- src/core/kernel/events.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/kernel/events.cpp b/src/core/kernel/events.cpp index 06841720..7da4788e 100644 --- a/src/core/kernel/events.cpp +++ b/src/core/kernel/events.cpp @@ -96,7 +96,7 @@ void Kernel::svcSignalEvent() { // Result WaitSynchronization1(Handle handle, s64 timeout_nanoseconds) void Kernel::waitSynchronization1() { const Handle handle = regs[0]; - const s64 ns = s64(u64(regs[1]) | (u64(regs[2]) << 32)); + const s64 ns = s64(u64(regs[2]) | (u64(regs[3]) << 32)); logSVC("WaitSynchronization1(handle = %X, ns = %lld)\n", handle, ns); const auto object = getObject(handle); From 13d680e8b6e818224d0913e9a68a793c368d5281 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Wed, 6 Dec 2023 02:40:09 +0200 Subject: [PATCH 048/162] [IR] Report CPP as not plugged --- src/core/services/ir_user.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/core/services/ir_user.cpp b/src/core/services/ir_user.cpp index 47a2299a..ce4f94c4 100644 --- a/src/core/services/ir_user.cpp +++ b/src/core/services/ir_user.cpp @@ -120,9 +120,14 @@ void IRUserService::requireConnection(u32 messagePointer) { u32 sharedMemAddress = sharedMemory.value().addr; if (deviceID == u8(DeviceID::CirclePadPro)) { - mem.write8(sharedMemAddress + offsetof(SharedMemoryStatus, connectionStatus), 2); // Citra uses 2 here but only 1 works?? - mem.write8(sharedMemAddress + offsetof(SharedMemoryStatus, connectionRole), 2); - mem.write8(sharedMemAddress + offsetof(SharedMemoryStatus, isConnected), 1); + // Note: We temporarily pretend we don't have a CirclePad Pro. This code must change when we emulate it or N3DS C-stick + constexpr u8 status = 1; // Not connected. Any value other than 2 is considered not connected. + constexpr u8 role = 0; + constexpr u8 connected = 0; + + mem.write8(sharedMemAddress + offsetof(SharedMemoryStatus, connectionStatus), status); + mem.write8(sharedMemAddress + offsetof(SharedMemoryStatus, connectionRole), role); + mem.write8(sharedMemAddress + offsetof(SharedMemoryStatus, isConnected), connected); connectedDevice = true; if (connectionStatusEvent.has_value()) { From 95c1923c3778d7bd029abc657a4867ee024675f3 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Wed, 6 Dec 2023 17:11:30 +0200 Subject: [PATCH 049/162] Update .gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index ee5f1331..7214ef50 100644 --- a/.gitignore +++ b/.gitignore @@ -58,9 +58,10 @@ fb.bat *.3ds *.3dsx *.app +*.cia *.cci *.cxi *.elf *.smdh -config.toml \ No newline at end of file +config.toml From 609d3fc196984c7f071ca6efa9b7ea1c16906907 Mon Sep 17 00:00:00 2001 From: gabriel Date: Thu, 7 Dec 2023 17:49:22 -0400 Subject: [PATCH 050/162] Fix words --- src/pandroid/app/src/main/AndroidManifest.xml | 2 +- .../panda3ds/pandroid/app/GameActivity.java | 13 +++--- .../com/panda3ds/pandroid/math/Vector2.java | 4 -- .../panda3ds/pandroid/utils/Constants.java | 4 +- .../pandroid/view/PandaGlRenderer.java | 4 +- .../pandroid/view/PandaGlSurfaceView.java | 14 +++---- .../view/controller/ControllerNode.java | 4 +- .../view/controller/nodes/Joystick.java | 42 +++++++++---------- .../controller/nodes/TouchScreenNodeImpl.java | 12 +++--- .../view/renderer/layout/ConsoleLayout.java | 1 - .../renderer/layout/DefaultScreenLayout.java | 2 +- 11 files changed, 45 insertions(+), 57 deletions(-) diff --git a/src/pandroid/app/src/main/AndroidManifest.xml b/src/pandroid/app/src/main/AndroidManifest.xml index 978edcc5..291496f4 100644 --- a/src/pandroid/app/src/main/AndroidManifest.xml +++ b/src/pandroid/app/src/main/AndroidManifest.xml @@ -8,7 +8,7 @@ + android:glEsVersion="0x0030001"/> { - findViewById(R.id.overlay_controller).setVisibility(isChecked ? View.VISIBLE : View.INVISIBLE); - }); + ((CheckBox) findViewById(R.id.hide_screen_controller)).setOnCheckedChangeListener((buttonView, isChecked) -> findViewById(R.id.overlay_controller).setVisibility(isChecked ? View.VISIBLE : View.INVISIBLE)); } @Override 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 index 4208b610..becec9e1 100644 --- 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 @@ -2,15 +2,11 @@ package com.panda3ds.pandroid.math; public class Vector2 { public float x, y; - public Vector2(Vector2 value) { this(value.x, value.y); } - 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)); } public void set(float x, float y) { 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 index bc389987..3485180e 100644 --- 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 @@ -6,7 +6,7 @@ public class Constants { 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_A = 1; 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; @@ -22,5 +22,5 @@ public class Constants { public static final int N3DS_FULL_HEIGHT = 480; public static final String EXTRA_PATH = "path"; - public static final String LOG_TAG = "Alber"; + public static final String LOG_TAG = "pandroid"; } 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 index b21e3353..0df05e9f 100644 --- 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 @@ -42,8 +42,8 @@ public class PandaGlRenderer implements GLSurfaceView.Renderer, ConsoleRenderer } public void onSurfaceCreated(GL10 unused, EGLConfig config) { - Log.i("pandroid", glGetString(GL_EXTENSIONS)); - Log.w("pandroid", glGetString(GL_VERSION)); + Log.i(Constants.LOG_TAG, glGetString(GL_EXTENSIONS)); + Log.w(Constants.LOG_TAG, glGetString(GL_VERSION)); glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); 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 index f5a19efe..eb65762d 100644 --- 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 @@ -1,20 +1,18 @@ package com.panda3ds.pandroid.view; import android.content.Context; -import android.graphics.Canvas; import android.opengl.GLSurfaceView; -import android.util.Log; + import androidx.annotation.NonNull; import com.panda3ds.pandroid.math.Vector2; -import com.panda3ds.pandroid.utils.Constants; import com.panda3ds.pandroid.view.controller.TouchEvent; import com.panda3ds.pandroid.view.controller.nodes.TouchScreenNodeImpl; import com.panda3ds.pandroid.view.renderer.ConsoleRenderer; public class PandaGlSurfaceView extends GLSurfaceView implements TouchScreenNodeImpl { final PandaGlRenderer renderer; - private int size_width; - private int size_height; + private int width; + private int height; public PandaGlSurfaceView(Context context, String romPath) { super(context); @@ -29,14 +27,14 @@ public class PandaGlSurfaceView extends GLSurfaceView implements TouchScreenNode @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); - size_width = getMeasuredWidth(); - size_height = getMeasuredHeight(); + width = getMeasuredWidth(); + height = getMeasuredHeight(); } @NonNull @Override public Vector2 getSize() { - return new Vector2(size_width, size_height); + return new Vector2(width, height); } @Override 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 index e5b8795e..e6c1dc92 100644 --- 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 @@ -7,10 +7,10 @@ import com.panda3ds.pandroid.math.Vector2; public interface ControllerNode { @NonNull default Vector2 getPosition() { - View me = (View) this; + View view = (View) this; int[] position = new int[2]; - me.getLocationInWindow(position); + view.getLocationInWindow(position); return new Vector2(position[0], position[1]); } 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 index df487bd8..ad3df6b1 100644 --- 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 @@ -7,18 +7,18 @@ 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 float axisX = 0; + private float axisY = 0; - private int size_width = 0; - private int size_height = 0; + private int width = 0; + private int height = 0; private JoystickListener joystickListener; @@ -42,18 +42,18 @@ public class Joystick extends BasicControllerNode implements ControllerNode { @Override public void onDrawForeground(Canvas canvas) { - size_width = getWidth(); - size_height = getHeight(); + width = getWidth(); + height = getHeight(); - int analogIconSize = size_width - getPaddingLeft(); + int analogIconSize = width - getPaddingLeft(); float middleIconSize = analogIconSize / 2.0F; - float middle = size_width / 2.0F; + float middle = width / 2.0F; float maxDistance = (middle - middleIconSize) * 0.9F; - float tx = maxDistance * stick_x; - float ty = maxDistance * stick_y; + float tx = maxDistance * axisX; + float ty = maxDistance * axisY; float radius = Vector2.distance(0.0F, 0.0F, Math.abs(tx), Math.abs(ty)); radius = Math.min(maxDistance, radius); @@ -62,8 +62,8 @@ public class Joystick extends BasicControllerNode implements ControllerNode { 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)); + axisX = Math.max(-1.0f, Math.min(1.0f, axisX)); + axisY = Math.max(-1.0f, Math.min(1.0f, axisY)); float x = middle - middleIconSize + rx; float y = middle - middleIconSize + ry; @@ -77,19 +77,19 @@ public class Joystick extends BasicControllerNode implements ControllerNode { } } - 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 Vector2 getAxis() { return new Vector2(Math.max(-1.0F, Math.min(1.0F, axisX)), Math.max(-1.0F, Math.min(1.0F, axisY))); } public void setJoystickListener(JoystickListener joystickListener) { this.joystickListener = joystickListener; } @NonNull @Override public Vector2 getSize() { - return new Vector2(size_width, size_height); + return new Vector2(width, height); } @Override public void onTouch(TouchEvent event) { - float middle = size_width / 2.0F; + float middle = width / 2.0F; float x = event.getX(); float y = event.getY(); @@ -97,17 +97,17 @@ public class Joystick extends BasicControllerNode implements ControllerNode { x = Math.max(0, Math.min(middle * 2, x)); y = Math.max(0, Math.min(middle * 2, y)); - stick_x = ((x - middle) / middle); + axisX = ((x - middle) / middle); - stick_y = ((y - middle) / middle); + axisY = ((y - middle) / middle); if (event.getAction() == TouchEvent.ACTION_UP) { - stick_x = 0; - stick_y = 0; + axisX = 0; + axisY = 0; } if (joystickListener != null) { - joystickListener.onJoystickAxisChange(this, stick_x, stick_y); + joystickListener.onJoystickAxisChange(this, axisX, axisY); } invalidate(); diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/controller/nodes/TouchScreenNodeImpl.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/controller/nodes/TouchScreenNodeImpl.java index 1009d29c..39e6aae9 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/controller/nodes/TouchScreenNodeImpl.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/controller/nodes/TouchScreenNodeImpl.java @@ -1,22 +1,20 @@ package com.panda3ds.pandroid.view.controller.nodes; import android.graphics.Rect; -import android.util.Log; import android.view.View; + import com.panda3ds.pandroid.AlberDriver; import com.panda3ds.pandroid.R; -import com.panda3ds.pandroid.utils.Constants; import com.panda3ds.pandroid.view.controller.ControllerNode; import com.panda3ds.pandroid.view.controller.TouchEvent; import com.panda3ds.pandroid.view.renderer.ConsoleRenderer; public interface TouchScreenNodeImpl extends ControllerNode { default void onTouchScreenPress(ConsoleRenderer renderer, TouchEvent event) { - View me = (View) this; - boolean hasDownEvent = me.getTag(R.id.TagEventHasDown) != null && (boolean) me.getTag(R.id.TagEventHasDown); + View view = (View) this; + boolean hasDownEvent = view.getTag(R.id.TagEventHasDown) != null && (boolean) view.getTag(R.id.TagEventHasDown); Rect bounds = renderer.getLayout().getBottomDisplayBounds(); - ; if (event.getX() >= bounds.left && event.getY() >= bounds.top && event.getX() <= bounds.right && event.getY() <= bounds.bottom) { int x = (int) (event.getX() - bounds.left); @@ -27,12 +25,12 @@ public interface TouchScreenNodeImpl extends ControllerNode { AlberDriver.TouchScreenDown(x, y); - me.setTag(R.id.TagEventHasDown, true); + view.setTag(R.id.TagEventHasDown, true); } if (hasDownEvent && event.getAction() == TouchEvent.ACTION_UP) { AlberDriver.TouchScreenUp(); - me.setTag(R.id.TagEventHasDown, false); + view.setTag(R.id.TagEventHasDown, false); } } } diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/renderer/layout/ConsoleLayout.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/renderer/layout/ConsoleLayout.java index dc83c59f..7ec00974 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/renderer/layout/ConsoleLayout.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/renderer/layout/ConsoleLayout.java @@ -1,7 +1,6 @@ package com.panda3ds.pandroid.view.renderer.layout; import android.graphics.Rect; -import com.panda3ds.pandroid.math.Vector2; public interface ConsoleLayout { void update(int screenWidth, int screenHeight); diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/renderer/layout/DefaultScreenLayout.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/renderer/layout/DefaultScreenLayout.java index d8616167..adfe5443 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/renderer/layout/DefaultScreenLayout.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/renderer/layout/DefaultScreenLayout.java @@ -1,7 +1,7 @@ package com.panda3ds.pandroid.view.renderer.layout; import android.graphics.Rect; -import android.util.Size; + import com.panda3ds.pandroid.math.Vector2; public class DefaultScreenLayout implements ConsoleLayout { From 00bf1bc6b870c46c4e8c66e47a4aebec613f3429 Mon Sep 17 00:00:00 2001 From: offtkp Date: Fri, 8 Dec 2023 02:24:02 +0200 Subject: [PATCH 051/162] Reduce jni boilerplate --- src/jni_driver.cpp | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/jni_driver.cpp b/src/jni_driver.cpp index 6d3bd104..82720e2a 100644 --- a/src/jni_driver.cpp +++ b/src/jni_driver.cpp @@ -13,7 +13,10 @@ HIDService* hidService = nullptr; RendererGL* renderer = nullptr; bool romLoaded = false; -extern "C" JNIEXPORT void JNICALL Java_com_panda3ds_pandroid_AlberDriver_Initialize(JNIEnv* env, jobject obj) { +#define AlberFunction(type, name) JNIEXPORT type JNICALL Java_com_panda3ds_pandroid_AlberDriver_##name + +extern "C" { +AlberFunction(void, Initialize)(JNIEnv* env, jobject obj) { emulator = std::make_unique(); if (emulator->getRendererType() != RendererType::OpenGL) { @@ -31,7 +34,7 @@ extern "C" JNIEXPORT void JNICALL Java_com_panda3ds_pandroid_AlberDriver_Initial emulator->initGraphicsContext(nullptr); } -extern "C" JNIEXPORT void JNICALL Java_com_panda3ds_pandroid_AlberDriver_RunFrame(JNIEnv* env, jobject obj, jint fbo) { +AlberFunction(void, RunFrame)(JNIEnv* env, jobject obj, jint fbo) { renderer->setFBO(fbo); renderer->resetStateManager(); emulator->runFrame(); @@ -39,36 +42,33 @@ extern "C" JNIEXPORT void JNICALL Java_com_panda3ds_pandroid_AlberDriver_RunFram hidService->updateInputs(emulator->getTicks()); } -extern "C" JNIEXPORT void JNICALL Java_com_panda3ds_pandroid_AlberDriver_Finalize(JNIEnv* env, jobject obj) { +AlberFunction(void, 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; } +AlberFunction(jboolean, HasRomLoaded)(JNIEnv* env, jobject obj) { return romLoaded; } -extern "C" JNIEXPORT void JNICALL Java_com_panda3ds_pandroid_AlberDriver_LoadRom(JNIEnv* env, jobject obj, jstring path) { +AlberFunction(void, LoadRom)(JNIEnv* env, jobject obj, jstring path) { const char* pathStr = env->GetStringUTFChars(path, nullptr); romLoaded = emulator->loadROM(pathStr); __android_log_print(ANDROID_LOG_INFO, "AlberDriver", "Loading ROM %s, result: %d", pathStr, (int)romLoaded); 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); -} +AlberFunction(void, 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(); } +AlberFunction(void, 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); -} +AlberFunction(void, 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); -} +AlberFunction(void, 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) { +AlberFunction(void, SetCirclepadAxis)(JNIEnv* env, jobject obj, jint x, jint y) { hidService->setCirclepadX((s16)x); hidService->setCirclepadY((s16)y); } +} + +#undef AlberFunction \ No newline at end of file From a78a1a099f562ad100d0d80f30c09a02ce1e49c0 Mon Sep 17 00:00:00 2001 From: offtkp Date: Fri, 8 Dec 2023 03:12:21 +0200 Subject: [PATCH 052/162] More peach fixups --- .../panda3ds/pandroid/app/GameActivity.java | 4 ++-- .../panda3ds/pandroid/app/MainActivity.java | 8 +++---- .../panda3ds/pandroid/utils/Constants.java | 23 ++++++++----------- .../panda3ds/pandroid/utils/PathUtils.java | 19 ++++----------- .../pandroid/view/PandaGlRenderer.java | 8 +++++++ .../view/controller/ControllerLayout.java | 7 +++--- .../pandroid/view/controller/TouchEvent.java | 10 +++----- .../pandroid/view/controller/TouchType.java | 7 ++++++ .../view/controller/nodes/Button.java | 5 ++-- .../view/controller/nodes/Joystick.java | 19 +++++++-------- .../controller/nodes/TouchScreenNodeImpl.java | 3 ++- .../renderer/layout/DefaultScreenLayout.java | 6 ++--- 12 files changed, 60 insertions(+), 59 deletions(-) create mode 100644 src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/controller/TouchType.java 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 index d086e85f..dfba96e5 100644 --- 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 @@ -23,14 +23,14 @@ public class GameActivity extends BaseActivity { super.onCreate(savedInstanceState); Intent intent = getIntent(); - if (!intent.hasExtra(Constants.EXTRA_PATH)) { + if (!intent.hasExtra(Constants.ACTIVITY_PARAMETER_PATH)) { setContentView(new FrameLayout(this)); Toast.makeText(this, "Invalid rom path!", Toast.LENGTH_LONG).show(); finish(); return; } - PandaGlSurfaceView pandaSurface = new PandaGlSurfaceView(this, intent.getStringExtra(Constants.EXTRA_PATH)); + PandaGlSurfaceView pandaSurface = new PandaGlSurfaceView(this, intent.getStringExtra(Constants.ACTIVITY_PARAMETER_PATH)); setContentView(R.layout.game_activity); 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 index 181cfb4e..368efe0e 100644 --- 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 @@ -16,14 +16,14 @@ import com.panda3ds.pandroid.utils.Constants; import com.panda3ds.pandroid.utils.PathUtils; public class MainActivity extends BaseActivity { - private static final int PICK_3DS_ROM = 2; + private static final int PICK_ROM = 2; private static final int PERMISSION_REQUEST_CODE = 3; private void openFile() { Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setType("*/*"); - startActivityForResult(intent, PICK_3DS_ROM); + startActivityForResult(intent, PICK_ROM); } @Override @@ -46,11 +46,11 @@ public class MainActivity extends BaseActivity { @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { - if (requestCode == PICK_3DS_ROM) { + if (requestCode == PICK_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)); + startActivity(new Intent(this, GameActivity.class).putExtra(Constants.ACTIVITY_PARAMETER_PATH, path)); } super.onActivityResult(requestCode, resultCode, data); } 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 index 3485180e..6b480c23 100644 --- 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 @@ -1,26 +1,23 @@ package com.panda3ds.pandroid.utils; public class Constants { + public static final int INPUT_KEY_A = 1 << 0; + public static final int INPUT_KEY_B = 1 << 1; + public static final int INPUT_KEY_SELECT = 1 << 2; + public static final int INPUT_KEY_START = 1 << 3; + public static final int INPUT_KEY_RIGHT = 1 << 4; + public static final int INPUT_KEY_LEFT = 1 << 5; 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; - public static final int INPUT_KEY_B = 1 << 1; + public static final int INPUT_KEY_R = 1 << 8; + public static final int INPUT_KEY_L = 1 << 9; 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 int N3DS_WIDTH = 400; public static final int N3DS_HALF_HEIGHT = 240; - public static final int N3DS_FULL_HEIGHT = 480; + public static final int N3DS_FULL_HEIGHT = N3DS_HALF_HEIGHT * 2; - public static final String EXTRA_PATH = "path"; + public static final String ACTIVITY_PARAMETER_PATH = "path"; public static final String LOG_TAG = "pandroid"; } 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 index ccf655bf..0434e53f 100644 --- 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 @@ -11,9 +11,8 @@ 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 + if (isExternalStorageDocument(uri)) { final String docId = DocumentsContract.getDocumentId(uri); final String[] split = docId.split(":"); final String type = split[0]; @@ -33,14 +32,12 @@ public class PathUtils { return System.getenv(storageDefinition) + "/" + split[1]; } - } else if (isDownloadsDocument(uri)) { // DownloadsProvider - + } else if (isDownloadsDocument(uri)) { 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 + } else if (isMediaDocument(uri)) { final String docId = DocumentsContract.getDocumentId(uri); final String[] split = docId.split(":"); final String type = split[0]; @@ -56,18 +53,13 @@ public class PathUtils { 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 + } else if ("content".equalsIgnoreCase(uri.getScheme())) { if (isGooglePhotosUri(uri)) return uri.getLastPathSegment(); - return getDataColumn(context, uri, null, null); - - } else if ("file".equalsIgnoreCase(uri.getScheme())) { // File + } else if ("file".equalsIgnoreCase(uri.getScheme())) { return uri.getPath(); } @@ -78,7 +70,6 @@ public class PathUtils { 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()) { 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 index 0df05e9f..128e9b4f 100644 --- 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 @@ -45,6 +45,14 @@ public class PandaGlRenderer implements GLSurfaceView.Renderer, ConsoleRenderer Log.i(Constants.LOG_TAG, glGetString(GL_EXTENSIONS)); Log.w(Constants.LOG_TAG, glGetString(GL_VERSION)); + int[] version = new int[2]; + glGetIntegerv(GL_MAJOR_VERSION, version, 0); + glGetIntegerv(GL_MINOR_VERSION, version, 1); + + if (version[0] < 3 || (version[0] == 3 && version[1] < 1)) { + Log.e(Constants.LOG_TAG, "OpenGL 3.1 or higher is required"); + } + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); 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 index 137bd312..4af28497 100644 --- 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 @@ -79,8 +79,7 @@ public class ControllerLayout extends RelativeLayout { int[] globalPosition = new int[2]; getLocationInWindow(globalPosition); - int action = TouchEvent.ACTION_MOVE; - + TouchType action = TouchType.ACTION_MOVE; if ((!activeTouchEvents.containsKey(index))) { if (up) return; ControllerNode node = null; @@ -97,13 +96,13 @@ public class ControllerLayout extends RelativeLayout { } if (node != null) { activeTouchEvents.put(index, node); - action = TouchEvent.ACTION_DOWN; + action = TouchType.ACTION_DOWN; } else { return; } } - if (up) action = TouchEvent.ACTION_UP; + if (up) action = TouchType.ACTION_UP; ControllerNode node = activeTouchEvents.get(index); Vector2 pos = node.getPosition(); 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 index d3b40db9..8c940414 100644 --- 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 @@ -1,20 +1,16 @@ 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 TouchType action; private final float x, y; public float getX() { return x; } public float getY() { return y; } - public int getAction() { return action; } + public TouchType getAction() { return action; } - public TouchEvent(float x, float y, int action) { + public TouchEvent(float x, float y, TouchType action) { this.x = x; this.y = y; this.action = action; diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/controller/TouchType.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/controller/TouchType.java new file mode 100644 index 00000000..69772915 --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/controller/TouchType.java @@ -0,0 +1,7 @@ +package com.panda3ds.pandroid.view.controller; + +public enum TouchType { + ACTION_DOWN, + ACTION_MOVE, + ACTION_UP +}; \ No newline at end of file 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 index 4dadc22b..83c38d48 100644 --- 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 @@ -11,6 +11,7 @@ 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.TouchType; import com.panda3ds.pandroid.view.controller.listeners.ButtonStateListener; public class Button extends BasicControllerNode { @@ -58,8 +59,8 @@ public class Button extends BasicControllerNode { @Override public void onTouch(TouchEvent event) { - pressed = event.getAction() != TouchEvent.ACTION_UP; - setAlpha(pressed ? 0.2F : 1.0F); + pressed = event.getAction() != TouchType.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 index ad3df6b1..b63e9d66 100644 --- 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 @@ -11,6 +11,7 @@ import androidx.annotation.NonNull; 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.TouchType; import com.panda3ds.pandroid.view.controller.listeners.JoystickListener; public class Joystick extends BasicControllerNode implements ControllerNode { @@ -47,20 +48,20 @@ public class Joystick extends BasicControllerNode implements ControllerNode { int analogIconSize = width - getPaddingLeft(); - float middleIconSize = analogIconSize / 2.0F; - float middle = width / 2.0F; + float middleIconSize = analogIconSize / 2.0f; + float middle = width / 2.0f; - float maxDistance = (middle - middleIconSize) * 0.9F; + float maxDistance = (middle - middleIconSize) * 0.9f; float tx = maxDistance * axisX; float ty = maxDistance * axisY; - float radius = Vector2.distance(0.0F, 0.0F, Math.abs(tx), Math.abs(ty)); + 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)); + float rx = (float) (radius * Math.cos(2 * Math.PI * deg / 360.0)); + float ry = (float) (radius * Math.sin(2 * Math.PI * deg / 360.0)); axisX = Math.max(-1.0f, Math.min(1.0f, axisX)); axisY = Math.max(-1.0f, Math.min(1.0f, axisY)); @@ -77,7 +78,7 @@ public class Joystick extends BasicControllerNode implements ControllerNode { } } - public Vector2 getAxis() { return new Vector2(Math.max(-1.0F, Math.min(1.0F, axisX)), Math.max(-1.0F, Math.min(1.0F, axisY))); } + public Vector2 getAxis() { return new Vector2(Math.max(-1.0f, Math.min(1.0f, axisX)), Math.max(-1.0f, Math.min(1.0f, axisY))); } public void setJoystickListener(JoystickListener joystickListener) { this.joystickListener = joystickListener; } @@ -89,7 +90,7 @@ public class Joystick extends BasicControllerNode implements ControllerNode { @Override public void onTouch(TouchEvent event) { - float middle = width / 2.0F; + float middle = width / 2.0f; float x = event.getX(); float y = event.getY(); @@ -101,7 +102,7 @@ public class Joystick extends BasicControllerNode implements ControllerNode { axisY = ((y - middle) / middle); - if (event.getAction() == TouchEvent.ACTION_UP) { + if (event.getAction() == TouchType.ACTION_UP) { axisX = 0; axisY = 0; } diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/controller/nodes/TouchScreenNodeImpl.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/controller/nodes/TouchScreenNodeImpl.java index 39e6aae9..bf51d4fe 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/controller/nodes/TouchScreenNodeImpl.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/controller/nodes/TouchScreenNodeImpl.java @@ -7,6 +7,7 @@ import com.panda3ds.pandroid.AlberDriver; import com.panda3ds.pandroid.R; import com.panda3ds.pandroid.view.controller.ControllerNode; import com.panda3ds.pandroid.view.controller.TouchEvent; +import com.panda3ds.pandroid.view.controller.TouchType; import com.panda3ds.pandroid.view.renderer.ConsoleRenderer; public interface TouchScreenNodeImpl extends ControllerNode { @@ -28,7 +29,7 @@ public interface TouchScreenNodeImpl extends ControllerNode { view.setTag(R.id.TagEventHasDown, true); } - if (hasDownEvent && event.getAction() == TouchEvent.ACTION_UP) { + if (hasDownEvent && event.getAction() == TouchType.ACTION_UP) { AlberDriver.TouchScreenUp(); view.setTag(R.id.TagEventHasDown, false); } diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/renderer/layout/DefaultScreenLayout.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/renderer/layout/DefaultScreenLayout.java index adfe5443..a726b2e6 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/renderer/layout/DefaultScreenLayout.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/renderer/layout/DefaultScreenLayout.java @@ -8,9 +8,9 @@ public class DefaultScreenLayout implements ConsoleLayout { private final Rect topDisplay = new Rect(); private final Rect bottomDisplay = new Rect(); - private final Vector2 screenSize = new Vector2(1.0F, 1.0F); - private final Vector2 topSourceSize = new Vector2(1.0F, 1.0F); - private final Vector2 bottomSourceSize = new Vector2(1.0F, 1.0F); + private final Vector2 screenSize = new Vector2(1.0f, 1.0f); + private final Vector2 topSourceSize = new Vector2(1.0f, 1.0f); + private final Vector2 bottomSourceSize = new Vector2(1.0f, 1.0f); @Override public void update(int screenWidth, int screenHeight) { From e6880b15641f4fae33880e407ac77289fb041f58 Mon Sep 17 00:00:00 2001 From: offtkp Date: Fri, 8 Dec 2023 03:28:43 +0200 Subject: [PATCH 053/162] Even more peach stuff --- src/jni_driver.cpp | 11 ++++++++--- .../java/com/panda3ds/pandroid/utils/Constants.java | 4 ++-- .../com/panda3ds/pandroid/view/PandaGlRenderer.java | 3 +++ .../panda3ds/pandroid/view/PandaGlSurfaceView.java | 5 ++++- 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/jni_driver.cpp b/src/jni_driver.cpp index 82720e2a..770e16da 100644 --- a/src/jni_driver.cpp +++ b/src/jni_driver.cpp @@ -15,19 +15,24 @@ bool romLoaded = false; #define AlberFunction(type, name) JNIEXPORT type JNICALL Java_com_panda3ds_pandroid_AlberDriver_##name +void throwException(JNIEnv* env, const char* message) { + jclass exceptionClass = env->FindClass("java/lang/RuntimeException"); + env->ThrowNew(exceptionClass, message); +} + extern "C" { AlberFunction(void, Initialize)(JNIEnv* env, jobject obj) { emulator = std::make_unique(); if (emulator->getRendererType() != RendererType::OpenGL) { - throw std::runtime_error("Renderer is not OpenGL"); + return throwException(env, "Renderer type 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"); + return throwException(env, "Failed to load OpenGL ES 2.0"); } __android_log_print(ANDROID_LOG_INFO, "AlberDriver", "OpenGL ES %d.%d", GLVersion.major, GLVersion.minor); @@ -36,6 +41,7 @@ AlberFunction(void, Initialize)(JNIEnv* env, jobject obj) { AlberFunction(void, RunFrame)(JNIEnv* env, jobject obj, jint fbo) { renderer->setFBO(fbo); + // TODO: don't reset entire state manager renderer->resetStateManager(); emulator->runFrame(); @@ -53,7 +59,6 @@ AlberFunction(jboolean, HasRomLoaded)(JNIEnv* env, jobject obj) { return romLoad AlberFunction(void, LoadRom)(JNIEnv* env, jobject obj, jstring path) { const char* pathStr = env->GetStringUTFChars(path, nullptr); romLoaded = emulator->loadROM(pathStr); - __android_log_print(ANDROID_LOG_INFO, "AlberDriver", "Loading ROM %s, result: %d", pathStr, (int)romLoaded); env->ReleaseStringUTFChars(path, pathStr); } 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 index 6b480c23..d94164c7 100644 --- 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 @@ -15,8 +15,8 @@ public class Constants { public static final int INPUT_KEY_Y = 1 << 11; public static final int N3DS_WIDTH = 400; - public static final int N3DS_HALF_HEIGHT = 240; - public static final int N3DS_FULL_HEIGHT = N3DS_HALF_HEIGHT * 2; + public static final int N3DS_FULL_HEIGHT = 480; + public static final int N3DS_HALF_HEIGHT = N3DS_FULL_HEIGHT / 2; public static final String ACTIVITY_PARAMETER_PATH = "path"; public static final String LOG_TAG = "pandroid"; 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 index 128e9b4f..8dd350ce 100644 --- 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 @@ -69,6 +69,9 @@ public class PandaGlRenderer implements GLSurfaceView.Renderer, ConsoleRenderer screenFbo = generateBuffer[0]; glBindFramebuffer(GL_FRAMEBUFFER, screenFbo); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, screenTexture, 0); + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + Log.e(Constants.LOG_TAG, "Framebuffer is not complete"); + } glBindFramebuffer(GL_FRAMEBUFFER, 0); AlberDriver.Initialize(); 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 index eb65762d..c813294c 100644 --- 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 @@ -2,6 +2,7 @@ package com.panda3ds.pandroid.view; import android.content.Context; import android.opengl.GLSurfaceView; +import android.os.Debug; import androidx.annotation.NonNull; import com.panda3ds.pandroid.math.Vector2; @@ -17,7 +18,9 @@ public class PandaGlSurfaceView extends GLSurfaceView implements TouchScreenNode public PandaGlSurfaceView(Context context, String romPath) { super(context); setEGLContextClientVersion(3); - setDebugFlags(DEBUG_LOG_GL_CALLS); + if (Debug.isDebuggerConnected()) { + setDebugFlags(DEBUG_LOG_GL_CALLS); + } renderer = new PandaGlRenderer(romPath); setRenderer(renderer); } From 63a09dd51fb88013ef71612e7e01e78b415495f6 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Wed, 13 Dec 2023 22:47:32 +0200 Subject: [PATCH 054/162] Android bonk --- src/jni_driver.cpp | 3 --- .../src/main/java/com/panda3ds/pandroid/app/GameActivity.java | 3 --- .../src/main/java/com/panda3ds/pandroid/app/MainActivity.java | 1 - .../src/main/java/com/panda3ds/pandroid/utils/PathUtils.java | 3 --- .../com/panda3ds/pandroid/view/PandaLayoutController.java | 2 -- .../panda3ds/pandroid/view/controller/ControllerLayout.java | 4 +--- .../com/panda3ds/pandroid/view/controller/ControllerNode.java | 1 - .../com/panda3ds/pandroid/view/controller/TouchEvent.java | 2 -- .../pandroid/view/controller/nodes/BasicControllerNode.java | 2 -- .../com/panda3ds/pandroid/view/controller/nodes/Joystick.java | 3 --- 10 files changed, 1 insertion(+), 23 deletions(-) diff --git a/src/jni_driver.cpp b/src/jni_driver.cpp index 770e16da..8f5c352e 100644 --- a/src/jni_driver.cpp +++ b/src/jni_driver.cpp @@ -63,11 +63,8 @@ AlberFunction(void, LoadRom)(JNIEnv* env, jobject obj, jstring path) { } AlberFunction(void, TouchScreenDown)(JNIEnv* env, jobject obj, jint x, jint y) { hidService->setTouchScreenPress((u16)x, (u16)y); } - AlberFunction(void, TouchScreenUp)(JNIEnv* env, jobject obj) { hidService->releaseTouchScreen(); } - AlberFunction(void, KeyUp)(JNIEnv* env, jobject obj, jint keyCode) { hidService->releaseKey((u32)keyCode); } - AlberFunction(void, KeyDown)(JNIEnv* env, jobject obj, jint keyCode) { hidService->pressKey((u32)keyCode); } AlberFunction(void, SetCirclepadAxis)(JNIEnv* env, jobject obj, jint x, jint y) { 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 index dfba96e5..2da73b97 100644 --- 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 @@ -17,7 +17,6 @@ import com.panda3ds.pandroid.view.PandaGlSurfaceView; import com.panda3ds.pandroid.view.PandaLayoutController; public class GameActivity extends BaseActivity { - @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -31,14 +30,12 @@ public class GameActivity extends BaseActivity { } PandaGlSurfaceView pandaSurface = new PandaGlSurfaceView(this, intent.getStringExtra(Constants.ACTIVITY_PARAMETER_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)); PandaLayoutController controllerLayout = findViewById(R.id.controller_layout); - controllerLayout.initialize(); ((CheckBox) findViewById(R.id.hide_screen_controller)).setOnCheckedChangeListener((buttonView, isChecked) -> findViewById(R.id.overlay_controller).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 index 368efe0e..f4fc27bf 100644 --- 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 @@ -40,7 +40,6 @@ public class MainActivity extends BaseActivity { } setContentView(R.layout.activity_main); - findViewById(R.id.load_rom).setOnClickListener(v -> { openFile(); }); } 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 index 0434e53f..9bfaa0e4 100644 --- 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 @@ -83,10 +83,7 @@ public class PathUtils { } 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/PandaLayoutController.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/PandaLayoutController.java index 543ab840..617b407e 100644 --- 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 @@ -11,9 +11,7 @@ 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) { 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 index 4af28497..32451bc7 100644 --- 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 @@ -19,9 +19,7 @@ public class ControllerLayout extends RelativeLayout { 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) { @@ -131,7 +129,7 @@ public class ControllerLayout extends RelativeLayout { refreshChildren(); } - /*@TODO: Need replace that methods for prevent Android send events directly to children*/ + // TODO: Need to replace these methods to prevent Android sending events directly to children @Override public ArrayList getTouchables() { 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 index e6c1dc92..7d45550a 100644 --- 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 @@ -17,6 +17,5 @@ public interface ControllerNode { default boolean isVisible() { return ((View) this).isShown(); } @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 index 8c940414..6b82d201 100644 --- 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 @@ -5,9 +5,7 @@ public class TouchEvent { private final float x, y; public float getX() { return x; } - public float getY() { return y; } - public TouchType getAction() { return action; } public TouchEvent(float x, float y, TouchType action) { 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 index 224be9a4..d196c7ec 100644 --- 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 @@ -9,8 +9,6 @@ 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/Joystick.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/controller/nodes/Joystick.java index b63e9d66..cf33afb6 100644 --- 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 @@ -20,11 +20,9 @@ public class Joystick extends BasicControllerNode implements ControllerNode { private int width = 0; private int 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) { @@ -99,7 +97,6 @@ public class Joystick extends BasicControllerNode implements ControllerNode { y = Math.max(0, Math.min(middle * 2, y)); axisX = ((x - middle) / middle); - axisY = ((y - middle) / middle); if (event.getAction() == TouchType.ACTION_UP) { From 79079f35e2d99986aa9585b42dc55bd72e444729 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Wed, 13 Dec 2023 23:24:43 +0200 Subject: [PATCH 055/162] [Qt] Preparations before making the Qt builds public --- src/panda_qt/main_window.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/panda_qt/main_window.cpp b/src/panda_qt/main_window.cpp index 804dc63a..2e4963d3 100644 --- a/src/panda_qt/main_window.cpp +++ b/src/panda_qt/main_window.cpp @@ -23,7 +23,7 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent) auto aboutMenu = menuBar->addMenu(tr("About")); // Create and bind actions for them - auto pandaAction = fileMenu->addAction(tr("panda...")); + auto pandaAction = fileMenu->addAction(tr("Load game")); connect(pandaAction, &QAction::triggered, this, &MainWindow::selectROM); auto pauseAction = emulationMenu->addAction(tr("Pause")); @@ -341,4 +341,4 @@ void MainWindow::keyReleaseEvent(QKeyEvent* event) { case Qt::Key_Return: releaseKey(HID::Keys::Start); break; case Qt::Key_Backspace: releaseKey(HID::Keys::Select); break; } -} \ No newline at end of file +} From 6527df72111234db91574ff547d1892f134791e1 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Wed, 13 Dec 2023 23:33:13 +0200 Subject: [PATCH 056/162] Add panda-qt links --- readme.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 8667862d..0d9e7f7b 100644 --- a/readme.md +++ b/readme.md @@ -13,14 +13,23 @@ Join our Discord server by pressing on the banner below, or find us on other pla ![screenshot1](docs/img/KirbyRobobot.png) ![screenshot2](docs/img/OoT_Title.png) ![screenshot3](docs/img/pokegang.png) # Download -You can download stable builds from the Releases tab, or you can download the latest build from the table below +You can download stable builds from the Releases tab, or you can download the latest build from the tables below. Panda3DS comes in two flavours: A minimal SDL frontend, which does not have a GUI, and an experimental Qt frontend +SDL builds (No GUI): |Platform|Status|Download| |--------|------------|--------| |Windows build|[![Windows Build](https://github.com/wheremyfoodat/Panda3DS/actions/workflows/Windows_Build.yml/badge.svg?branch=master)](https://github.com/wheremyfoodat/Panda3DS/actions/workflows/Windows_Build.yml)|[Windows Executable](https://nightly.link/wheremyfoodat/Panda3DS/workflows/Windows_Build/master/Windows%20executable.zip)| |MacOS build|[![MacOS Build](https://github.com/wheremyfoodat/Panda3DS/actions/workflows/MacOS_Build.yml/badge.svg?branch=master)](https://github.com/wheremyfoodat/Panda3DS/actions/workflows/MacOS_Build.yml)|[MacOS App Bundle](https://nightly.link/wheremyfoodat/Panda3DS/workflows/MacOS_Build/master/MacOS%20Alber%20App%20Bundle.zip)| |Linux build|[![Linux Build](https://github.com/wheremyfoodat/Panda3DS/actions/workflows/Linux_Build.yml/badge.svg?branch=master)](https://github.com/wheremyfoodat/Panda3DS/actions/workflows/Linux_Build.yml)|[Linux AppImage](https://nightly.link/wheremyfoodat/Panda3DS/workflows/Linux_AppImage_Build/master/Linux%20executable.zip)| +Qt builds: +|Platform|Status|Download| +|--------|------------|--------| +|Windows build|[![Windows Build](https://github.com/wheremyfoodat/Panda3DS/actions/workflows/Windows_Build.yml/badge.svg?branch=master)](https://github.com/wheremyfoodat/Panda3DS/actions/workflows/Windows_Build.yml)|[Windows Executable](https://nightly.link/wheremyfoodat/Panda3DS/workflows/Qt_Build/master/Windows%20executable.zip)| +|MacOS build|[![MacOS Build](https://github.com/wheremyfoodat/Panda3DS/actions/workflows/MacOS_Build.yml/badge.svg?branch=master)](https://github.com/wheremyfoodat/Panda3DS/actions/workflows/MacOS_Build.yml)|[MacOS App Bundle](https://nightly.link/wheremyfoodat/Panda3DS/workflows/Qt_Build/master/MacOS%20Alber%20App%20Bundle.zip)| +|Linux build|[![Linux Build](https://github.com/wheremyfoodat/Panda3DS/actions/workflows/Linux_Build.yml/badge.svg?branch=master)](https://github.com/wheremyfoodat/Panda3DS/actions/workflows/Linux_Build.yml)|[Linux AppImage](https://nightly.link/wheremyfoodat/Panda3DS/workflows/Qt_Build/master/Linux%20executable.zip)| + + # Compatibility Panda3DS is still in the early stages of development. Many games boot, many don't. Lots of games have at least some hilariously broken graphics, audio is not supported, and some QoL features (including a GUI) are missing. However, even more things are implemented, such as most of the 3DS core required to play games, and various neat features, such as Lua scripting, discord bot support, support for some system apps, cheats, controller support, WIP amiibo support and many more! The emulator is constantly evolving, so make sure to take a peek every now and then! From 09f6d46aaa186e43dfc19c1a672e0f02aaf7e8c6 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Wed, 13 Dec 2023 23:33:43 +0200 Subject: [PATCH 057/162] Update readme.md --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 0d9e7f7b..152e3550 100644 --- a/readme.md +++ b/readme.md @@ -13,7 +13,7 @@ Join our Discord server by pressing on the banner below, or find us on other pla ![screenshot1](docs/img/KirbyRobobot.png) ![screenshot2](docs/img/OoT_Title.png) ![screenshot3](docs/img/pokegang.png) # Download -You can download stable builds from the Releases tab, or you can download the latest build from the tables below. Panda3DS comes in two flavours: A minimal SDL frontend, which does not have a GUI, and an experimental Qt frontend +You can download stable builds from the Releases tab, or you can download the latest build from the tables below. Panda3DS comes in two flavours: A minimal SDL frontend, which does not have a GUI, and an experimental Qt frontend. SDL builds (No GUI): |Platform|Status|Download| From 29c946af99fb0a24be0840e240ded145f26486be Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Wed, 13 Dec 2023 23:34:20 +0200 Subject: [PATCH 058/162] Update readme.md --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 152e3550..63be3921 100644 --- a/readme.md +++ b/readme.md @@ -13,7 +13,7 @@ Join our Discord server by pressing on the banner below, or find us on other pla ![screenshot1](docs/img/KirbyRobobot.png) ![screenshot2](docs/img/OoT_Title.png) ![screenshot3](docs/img/pokegang.png) # Download -You can download stable builds from the Releases tab, or you can download the latest build from the tables below. Panda3DS comes in two flavours: A minimal SDL frontend, which does not have a GUI, and an experimental Qt frontend. +You can download stable builds from the Releases tab, or you can download the latest build from the tables below. Panda3DS comes in two flavours: A minimal SDL frontend, which does not have a GUI, and an experimental Qt 6 frontend with a proper user interface. SDL builds (No GUI): |Platform|Status|Download| From 8afda8da5b056e1658bcbb7862181f6d4dd7f73c Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Wed, 13 Dec 2023 23:37:17 +0200 Subject: [PATCH 059/162] Update readme.md --- readme.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index 63be3921..6e4d5ac0 100644 --- a/readme.md +++ b/readme.md @@ -25,9 +25,9 @@ SDL builds (No GUI): Qt builds: |Platform|Status|Download| |--------|------------|--------| -|Windows build|[![Windows Build](https://github.com/wheremyfoodat/Panda3DS/actions/workflows/Windows_Build.yml/badge.svg?branch=master)](https://github.com/wheremyfoodat/Panda3DS/actions/workflows/Windows_Build.yml)|[Windows Executable](https://nightly.link/wheremyfoodat/Panda3DS/workflows/Qt_Build/master/Windows%20executable.zip)| -|MacOS build|[![MacOS Build](https://github.com/wheremyfoodat/Panda3DS/actions/workflows/MacOS_Build.yml/badge.svg?branch=master)](https://github.com/wheremyfoodat/Panda3DS/actions/workflows/MacOS_Build.yml)|[MacOS App Bundle](https://nightly.link/wheremyfoodat/Panda3DS/workflows/Qt_Build/master/MacOS%20Alber%20App%20Bundle.zip)| -|Linux build|[![Linux Build](https://github.com/wheremyfoodat/Panda3DS/actions/workflows/Linux_Build.yml/badge.svg?branch=master)](https://github.com/wheremyfoodat/Panda3DS/actions/workflows/Linux_Build.yml)|[Linux AppImage](https://nightly.link/wheremyfoodat/Panda3DS/workflows/Qt_Build/master/Linux%20executable.zip)| +|Windows build|[![Qt Build](https://github.com/wheremyfoodat/Panda3DS/actions/workflows/Qt_Build.yml/badge.svg)](https://github.com/wheremyfoodat/Panda3DS/actions/workflows/Qt_Build.yml)|[Windows Executable](https://nightly.link/wheremyfoodat/Panda3DS/workflows/Qt_Build/master/Windows%20executable.zip)| +|MacOS build|[![Qt Build](https://github.com/wheremyfoodat/Panda3DS/actions/workflows/Qt_Build.yml/badge.svg)](https://github.com/wheremyfoodat/Panda3DS/actions/workflows/Qt_Build.yml)|[MacOS App Bundle](https://nightly.link/wheremyfoodat/Panda3DS/workflows/Qt_Build/master/MacOS%20Alber%20App%20Bundle.zip)| +|Linux build|[![Qt Build](https://github.com/wheremyfoodat/Panda3DS/actions/workflows/Qt_Build.yml/badge.svg)](https://github.com/wheremyfoodat/Panda3DS/actions/workflows/Qt_Build.yml)|[Linux AppImage](https://nightly.link/wheremyfoodat/Panda3DS/workflows/Qt_Build/master/Linux%20executable.zip)| # Compatibility From 0cccde79f4f5fb4eb06b5c8f315be5c998e392fa Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Wed, 13 Dec 2023 23:45:53 +0200 Subject: [PATCH 060/162] Specify the SDL/Qt frontend split only happens on PC --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 6e4d5ac0..9e27194c 100644 --- a/readme.md +++ b/readme.md @@ -13,7 +13,7 @@ Join our Discord server by pressing on the banner below, or find us on other pla ![screenshot1](docs/img/KirbyRobobot.png) ![screenshot2](docs/img/OoT_Title.png) ![screenshot3](docs/img/pokegang.png) # Download -You can download stable builds from the Releases tab, or you can download the latest build from the tables below. Panda3DS comes in two flavours: A minimal SDL frontend, which does not have a GUI, and an experimental Qt 6 frontend with a proper user interface. +You can download stable builds from the Releases tab, or you can download the latest build from the tables below. Additionally, Panda3DS comes in 2 flavours on PC: A minimal SDL frontend, which does not have a GUI, and an experimental Qt 6 frontend with a proper user interface. SDL builds (No GUI): |Platform|Status|Download| From 101cc4d0aca532ce9ce96e19aa54e1ebf49d6ea0 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Thu, 14 Dec 2023 00:43:12 +0200 Subject: [PATCH 061/162] [Qt] Add separate configuration window for theme --- CMakeLists.txt | 4 +- include/panda_qt/config_window.hpp | 29 +++++++++ include/panda_qt/main_window.hpp | 14 +---- src/panda_qt/config_window.cpp | 99 ++++++++++++++++++++++++++++++ src/panda_qt/main_window.cpp | 97 ++--------------------------- 5 files changed, 136 insertions(+), 107 deletions(-) create mode 100644 include/panda_qt/config_window.hpp create mode 100644 src/panda_qt/config_window.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index c202f80c..cea72785 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -185,8 +185,8 @@ 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) + 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 src/panda_qt/config_window.cpp) + set(FRONTEND_HEADER_FILES include/panda_qt/screen.hpp include/panda_qt/main_window.hpp include/panda_qt/about_window.hpp include/panda_qt/config_window.hpp) source_group("Source Files\\Qt" FILES ${FRONTEND_SOURCE_FILES}) source_group("Header Files\\Qt" FILES ${FRONTEND_HEADER_FILES}) diff --git a/include/panda_qt/config_window.hpp b/include/panda_qt/config_window.hpp new file mode 100644 index 00000000..e91936c4 --- /dev/null +++ b/include/panda_qt/config_window.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +class ConfigWindow : public QDialog { + Q_OBJECT + + private: + enum class Theme : int { + System = 0, + Light = 1, + Dark = 2, + GreetingsCat = 3, + }; + + Theme currentTheme; + QComboBox* themeSelect = nullptr; + + void setTheme(Theme theme); + + public: + ConfigWindow(QWidget* parent = nullptr); + ~ConfigWindow(); +}; \ No newline at end of file diff --git a/include/panda_qt/main_window.hpp b/include/panda_qt/main_window.hpp index a50ee9a1..e2fd8596 100644 --- a/include/panda_qt/main_window.hpp +++ b/include/panda_qt/main_window.hpp @@ -1,9 +1,7 @@ #pragma once #include -#include #include -#include #include #include #include @@ -13,6 +11,7 @@ #include "emulator.hpp" #include "panda_qt/about_window.hpp" +#include "panda_qt/config_window.hpp" #include "panda_qt/screen.hpp" #include "services/hid.hpp" @@ -20,13 +19,6 @@ class MainWindow : public QMainWindow { Q_OBJECT private: - enum class Theme : int { - System = 0, - Light = 1, - Dark = 2, - GreetingsCat = 3, - }; - // Types of messages we might send from the GUI thread to the emulator thread enum class MessageType { LoadROM, Reset, Pause, Resume, TogglePause, DumpRomFS, PressKey, ReleaseKey @@ -58,11 +50,9 @@ class MainWindow : public QMainWindow { ScreenWidget screen; AboutWindow* aboutWindow; - QComboBox* themeSelect = nullptr; + ConfigWindow* configWindow; QMenuBar* menuBar = nullptr; - Theme currentTheme; - void setTheme(Theme theme); void swapEmuBuffer(); void emuThreadMainLoop(); void selectROM(); diff --git a/src/panda_qt/config_window.cpp b/src/panda_qt/config_window.cpp new file mode 100644 index 00000000..44debc32 --- /dev/null +++ b/src/panda_qt/config_window.cpp @@ -0,0 +1,99 @@ +#include "panda_qt/config_window.hpp" + +ConfigWindow::ConfigWindow(QWidget* parent) : QDialog(parent) { + setWindowTitle(tr("Configuration")); + + // Set up theme selection + setTheme(Theme::Dark); + themeSelect = new QComboBox(this); + themeSelect->addItem(tr("System")); + themeSelect->addItem(tr("Light")); + themeSelect->addItem(tr("Dark")); + themeSelect->addItem(tr("Greetings Cat")); + themeSelect->setCurrentIndex(static_cast(currentTheme)); + + themeSelect->setGeometry(40, 40, 100, 50); + themeSelect->show(); + connect(themeSelect, &QComboBox::currentIndexChanged, this, [&](int index) { setTheme(static_cast(index)); }); +} + +void ConfigWindow::setTheme(Theme theme) { + currentTheme = theme; + + switch (theme) { + case Theme::Dark: { + QApplication::setStyle(QStyleFactory::create("Fusion")); + + QPalette p; + p.setColor(QPalette::Window, QColor(53, 53, 53)); + p.setColor(QPalette::WindowText, Qt::white); + p.setColor(QPalette::Base, QColor(25, 25, 25)); + p.setColor(QPalette::AlternateBase, QColor(53, 53, 53)); + p.setColor(QPalette::ToolTipBase, Qt::white); + p.setColor(QPalette::ToolTipText, Qt::white); + p.setColor(QPalette::Text, Qt::white); + p.setColor(QPalette::Button, QColor(53, 53, 53)); + p.setColor(QPalette::ButtonText, Qt::white); + p.setColor(QPalette::BrightText, Qt::red); + p.setColor(QPalette::Link, QColor(42, 130, 218)); + + p.setColor(QPalette::Highlight, QColor(42, 130, 218)); + p.setColor(QPalette::HighlightedText, Qt::black); + qApp->setPalette(p); + break; + } + + case Theme::Light: { + QApplication::setStyle(QStyleFactory::create("Fusion")); + + QPalette p; + p.setColor(QPalette::Window, Qt::white); + p.setColor(QPalette::WindowText, Qt::black); + p.setColor(QPalette::Base, QColor(243, 243, 243)); + p.setColor(QPalette::AlternateBase, Qt::white); + p.setColor(QPalette::ToolTipBase, Qt::black); + p.setColor(QPalette::ToolTipText, Qt::black); + p.setColor(QPalette::Text, Qt::black); + p.setColor(QPalette::Button, Qt::white); + p.setColor(QPalette::ButtonText, Qt::black); + p.setColor(QPalette::BrightText, Qt::red); + p.setColor(QPalette::Link, QColor(42, 130, 218)); + + p.setColor(QPalette::Highlight, QColor(42, 130, 218)); + p.setColor(QPalette::HighlightedText, Qt::white); + qApp->setPalette(p); + break; + } + + case Theme::GreetingsCat: { + QApplication::setStyle(QStyleFactory::create("Fusion")); + + QPalette p; + p.setColor(QPalette::Window, QColor(250, 207, 228)); + p.setColor(QPalette::WindowText, QColor(225, 22, 137)); + p.setColor(QPalette::Base, QColor(250, 207, 228)); + p.setColor(QPalette::AlternateBase, QColor(250, 207, 228)); + p.setColor(QPalette::ToolTipBase, QColor(225, 22, 137)); + p.setColor(QPalette::ToolTipText, QColor(225, 22, 137)); + p.setColor(QPalette::Text, QColor(225, 22, 137)); + p.setColor(QPalette::Button, QColor(250, 207, 228)); + p.setColor(QPalette::ButtonText, QColor(225, 22, 137)); + p.setColor(QPalette::BrightText, Qt::black); + p.setColor(QPalette::Link, QColor(42, 130, 218)); + + p.setColor(QPalette::Highlight, QColor(42, 130, 218)); + p.setColor(QPalette::HighlightedText, Qt::black); + qApp->setPalette(p); + break; + } + + case Theme::System: { + qApp->setPalette(this->style()->standardPalette()); + qApp->setStyle(QStyleFactory::create("WindowsVista")); + qApp->setStyleSheet(""); + break; + } + } +} + +ConfigWindow::~ConfigWindow() { delete themeSelect; } \ No newline at end of file diff --git a/src/panda_qt/main_window.cpp b/src/panda_qt/main_window.cpp index 804dc63a..3dcb246f 100644 --- a/src/panda_qt/main_window.cpp +++ b/src/panda_qt/main_window.cpp @@ -29,9 +29,11 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent) auto pauseAction = emulationMenu->addAction(tr("Pause")); auto resumeAction = emulationMenu->addAction(tr("Resume")); auto resetAction = emulationMenu->addAction(tr("Reset")); + auto configureAction = emulationMenu->addAction(tr("Configure")); connect(pauseAction, &QAction::triggered, this, [this]() { sendMessage(EmulatorMessage{.type = MessageType::Pause}); }); connect(resumeAction, &QAction::triggered, this, [this]() { sendMessage(EmulatorMessage{.type = MessageType::Resume}); }); connect(resetAction, &QAction::triggered, this, [this]() { sendMessage(EmulatorMessage{.type = MessageType::Reset}); }); + connect(configureAction, &QAction::triggered, this, [this]() { configWindow->show(); }); auto dumpRomFSAction = toolsMenu->addAction(tr("Dump RomFS")); connect(dumpRomFSAction, &QAction::triggered, this, &MainWindow::dumpRomFS); @@ -39,21 +41,9 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent) auto aboutAction = aboutMenu->addAction(tr("About Panda3DS")); connect(aboutAction, &QAction::triggered, this, &MainWindow::showAboutMenu); - // Set up theme selection - setTheme(Theme::Dark); - themeSelect = new QComboBox(this); - themeSelect->addItem(tr("System")); - themeSelect->addItem(tr("Light")); - themeSelect->addItem(tr("Dark")); - themeSelect->addItem(tr("Greetings Cat")); - themeSelect->setCurrentIndex(static_cast(currentTheme)); - - themeSelect->setGeometry(40, 40, 100, 50); - themeSelect->show(); - connect(themeSelect, &QComboBox::currentIndexChanged, this, [&](int index) { setTheme(static_cast(index)); }); - // Set up misc objects aboutWindow = new AboutWindow(nullptr); + configWindow = new ConfigWindow(this); emu = new Emulator(); emu->setOutputSize(screen.surfaceWidth, screen.surfaceHeight); @@ -142,7 +132,7 @@ MainWindow::~MainWindow() { delete emu; delete menuBar; delete aboutWindow; - delete themeSelect; + delete configWindow; } // Send a message to the emulator thread. Lock the mutex and just push back to the vector. @@ -151,85 +141,6 @@ void MainWindow::sendMessage(const EmulatorMessage& message) { messageQueue.push_back(message); } -void MainWindow::setTheme(Theme theme) { - currentTheme = theme; - - switch (theme) { - case Theme::Dark: { - QApplication::setStyle(QStyleFactory::create("Fusion")); - - QPalette p; - p.setColor(QPalette::Window, QColor(53, 53, 53)); - p.setColor(QPalette::WindowText, Qt::white); - p.setColor(QPalette::Base, QColor(25, 25, 25)); - p.setColor(QPalette::AlternateBase, QColor(53, 53, 53)); - p.setColor(QPalette::ToolTipBase, Qt::white); - p.setColor(QPalette::ToolTipText, Qt::white); - p.setColor(QPalette::Text, Qt::white); - p.setColor(QPalette::Button, QColor(53, 53, 53)); - p.setColor(QPalette::ButtonText, Qt::white); - p.setColor(QPalette::BrightText, Qt::red); - p.setColor(QPalette::Link, QColor(42, 130, 218)); - - p.setColor(QPalette::Highlight, QColor(42, 130, 218)); - p.setColor(QPalette::HighlightedText, Qt::black); - qApp->setPalette(p); - break; - } - - case Theme::Light: { - QApplication::setStyle(QStyleFactory::create("Fusion")); - - QPalette p; - p.setColor(QPalette::Window, Qt::white); - p.setColor(QPalette::WindowText, Qt::black); - p.setColor(QPalette::Base, QColor(243, 243, 243)); - p.setColor(QPalette::AlternateBase, Qt::white); - p.setColor(QPalette::ToolTipBase, Qt::black); - p.setColor(QPalette::ToolTipText, Qt::black); - p.setColor(QPalette::Text, Qt::black); - p.setColor(QPalette::Button, Qt::white); - p.setColor(QPalette::ButtonText, Qt::black); - p.setColor(QPalette::BrightText, Qt::red); - p.setColor(QPalette::Link, QColor(42, 130, 218)); - - p.setColor(QPalette::Highlight, QColor(42, 130, 218)); - p.setColor(QPalette::HighlightedText, Qt::white); - qApp->setPalette(p); - break; - } - - case Theme::GreetingsCat: { - QApplication::setStyle(QStyleFactory::create("Fusion")); - - QPalette p; - p.setColor(QPalette::Window, QColor(250, 207, 228)); - p.setColor(QPalette::WindowText, QColor(225, 22, 137)); - p.setColor(QPalette::Base, QColor(250, 207, 228)); - p.setColor(QPalette::AlternateBase, QColor(250, 207, 228)); - p.setColor(QPalette::ToolTipBase, QColor(225, 22, 137)); - p.setColor(QPalette::ToolTipText, QColor(225, 22, 137)); - p.setColor(QPalette::Text, QColor(225, 22, 137)); - p.setColor(QPalette::Button, QColor(250, 207, 228)); - p.setColor(QPalette::ButtonText, QColor(225, 22, 137)); - p.setColor(QPalette::BrightText, Qt::black); - p.setColor(QPalette::Link, QColor(42, 130, 218)); - - p.setColor(QPalette::Highlight, QColor(42, 130, 218)); - p.setColor(QPalette::HighlightedText, Qt::black); - qApp->setPalette(p); - break; - } - - case Theme::System: { - qApp->setPalette(this->style()->standardPalette()); - qApp->setStyle(QStyleFactory::create("WindowsVista")); - qApp->setStyleSheet(""); - break; - } - } -} - void MainWindow::dumpRomFS() { auto folder = QFileDialog::getExistingDirectory( this, tr("Select folder to dump RomFS files to"), "", QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks From 53c3b5096ca9e6118d5c0dfb24d722ed8d05fa10 Mon Sep 17 00:00:00 2001 From: Gabriel Machado <97042217+GabrielBRDeveloper@users.noreply.github.com> Date: Thu, 14 Dec 2023 00:56:35 -0400 Subject: [PATCH 062/162] Best Screen Gamepad accessibility --- .../main/res/drawable/analog_background.xml | 26 +++++ .../main/res/drawable/analog_foreground.xml | 41 ++++++++ .../app/src/main/res/drawable/button_a.xml | 39 ++++++++ .../app/src/main/res/drawable/button_b.xml | 39 ++++++++ .../app/src/main/res/drawable/button_l.xml | 24 +++++ .../app/src/main/res/drawable/button_r.xml | 24 +++++ .../src/main/res/drawable/button_select.xml | 38 +++++++ .../src/main/res/drawable/button_start.xml | 38 +++++++ .../app/src/main/res/drawable/button_x.xml | 39 ++++++++ .../app/src/main/res/drawable/button_y.xml | 39 ++++++++ .../app/src/main/res/drawable/dpad_down.xml | 39 ++++++++ .../app/src/main/res/drawable/dpad_left.xml | 39 ++++++++ .../app/src/main/res/drawable/dpad_right.xml | 39 ++++++++ .../app/src/main/res/drawable/dpad_up.xml | 39 ++++++++ .../app/src/main/res/drawable/ic_add.xml | 5 + .../res/drawable/simple_analog_background.xml | 19 ---- .../res/drawable/simple_circle_button.xml | 10 -- .../src/main/res/layout/controller_dpad.xml | 30 +++--- .../main/res/layout/controller_gamepad.xml | 34 ++++--- .../main/res/layout/controller_joystick.xml | 11 +++ .../app/src/main/res/layout/controller_l.xml | 10 ++ .../app/src/main/res/layout/controller_r.xml | 10 ++ .../src/main/res/layout/controller_select.xml | 10 ++ .../src/main/res/layout/controller_start.xml | 10 ++ .../app/src/main/res/layout/game_activity.xml | 98 +++---------------- .../app/src/main/res/values/styleable.xml | 10 +- 26 files changed, 612 insertions(+), 148 deletions(-) create mode 100644 src/pandroid/app/src/main/res/drawable/analog_background.xml create mode 100644 src/pandroid/app/src/main/res/drawable/analog_foreground.xml create mode 100644 src/pandroid/app/src/main/res/drawable/button_a.xml create mode 100644 src/pandroid/app/src/main/res/drawable/button_b.xml create mode 100644 src/pandroid/app/src/main/res/drawable/button_l.xml create mode 100644 src/pandroid/app/src/main/res/drawable/button_r.xml create mode 100644 src/pandroid/app/src/main/res/drawable/button_select.xml create mode 100644 src/pandroid/app/src/main/res/drawable/button_start.xml create mode 100644 src/pandroid/app/src/main/res/drawable/button_x.xml create mode 100644 src/pandroid/app/src/main/res/drawable/button_y.xml create mode 100644 src/pandroid/app/src/main/res/drawable/dpad_down.xml create mode 100644 src/pandroid/app/src/main/res/drawable/dpad_left.xml create mode 100644 src/pandroid/app/src/main/res/drawable/dpad_right.xml create mode 100644 src/pandroid/app/src/main/res/drawable/dpad_up.xml create mode 100644 src/pandroid/app/src/main/res/drawable/ic_add.xml delete mode 100644 src/pandroid/app/src/main/res/drawable/simple_analog_background.xml delete mode 100644 src/pandroid/app/src/main/res/drawable/simple_circle_button.xml create mode 100644 src/pandroid/app/src/main/res/layout/controller_joystick.xml create mode 100644 src/pandroid/app/src/main/res/layout/controller_l.xml create mode 100644 src/pandroid/app/src/main/res/layout/controller_r.xml create mode 100644 src/pandroid/app/src/main/res/layout/controller_select.xml create mode 100644 src/pandroid/app/src/main/res/layout/controller_start.xml diff --git a/src/pandroid/app/src/main/res/drawable/analog_background.xml b/src/pandroid/app/src/main/res/drawable/analog_background.xml new file mode 100644 index 00000000..3f3747cd --- /dev/null +++ b/src/pandroid/app/src/main/res/drawable/analog_background.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + diff --git a/src/pandroid/app/src/main/res/drawable/analog_foreground.xml b/src/pandroid/app/src/main/res/drawable/analog_foreground.xml new file mode 100644 index 00000000..c339e69f --- /dev/null +++ b/src/pandroid/app/src/main/res/drawable/analog_foreground.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/pandroid/app/src/main/res/drawable/button_a.xml b/src/pandroid/app/src/main/res/drawable/button_a.xml new file mode 100644 index 00000000..d04306b9 --- /dev/null +++ b/src/pandroid/app/src/main/res/drawable/button_a.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + diff --git a/src/pandroid/app/src/main/res/drawable/button_b.xml b/src/pandroid/app/src/main/res/drawable/button_b.xml new file mode 100644 index 00000000..1611f28d --- /dev/null +++ b/src/pandroid/app/src/main/res/drawable/button_b.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + diff --git a/src/pandroid/app/src/main/res/drawable/button_l.xml b/src/pandroid/app/src/main/res/drawable/button_l.xml new file mode 100644 index 00000000..a2b2f5ad --- /dev/null +++ b/src/pandroid/app/src/main/res/drawable/button_l.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + diff --git a/src/pandroid/app/src/main/res/drawable/button_r.xml b/src/pandroid/app/src/main/res/drawable/button_r.xml new file mode 100644 index 00000000..24cf739b --- /dev/null +++ b/src/pandroid/app/src/main/res/drawable/button_r.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + diff --git a/src/pandroid/app/src/main/res/drawable/button_select.xml b/src/pandroid/app/src/main/res/drawable/button_select.xml new file mode 100644 index 00000000..c71a3bff --- /dev/null +++ b/src/pandroid/app/src/main/res/drawable/button_select.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + diff --git a/src/pandroid/app/src/main/res/drawable/button_start.xml b/src/pandroid/app/src/main/res/drawable/button_start.xml new file mode 100644 index 00000000..33e0a58b --- /dev/null +++ b/src/pandroid/app/src/main/res/drawable/button_start.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + diff --git a/src/pandroid/app/src/main/res/drawable/button_x.xml b/src/pandroid/app/src/main/res/drawable/button_x.xml new file mode 100644 index 00000000..4050c539 --- /dev/null +++ b/src/pandroid/app/src/main/res/drawable/button_x.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + diff --git a/src/pandroid/app/src/main/res/drawable/button_y.xml b/src/pandroid/app/src/main/res/drawable/button_y.xml new file mode 100644 index 00000000..87799691 --- /dev/null +++ b/src/pandroid/app/src/main/res/drawable/button_y.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + diff --git a/src/pandroid/app/src/main/res/drawable/dpad_down.xml b/src/pandroid/app/src/main/res/drawable/dpad_down.xml new file mode 100644 index 00000000..3a904b2d --- /dev/null +++ b/src/pandroid/app/src/main/res/drawable/dpad_down.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + diff --git a/src/pandroid/app/src/main/res/drawable/dpad_left.xml b/src/pandroid/app/src/main/res/drawable/dpad_left.xml new file mode 100644 index 00000000..8dad75be --- /dev/null +++ b/src/pandroid/app/src/main/res/drawable/dpad_left.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + diff --git a/src/pandroid/app/src/main/res/drawable/dpad_right.xml b/src/pandroid/app/src/main/res/drawable/dpad_right.xml new file mode 100644 index 00000000..e0bea7ec --- /dev/null +++ b/src/pandroid/app/src/main/res/drawable/dpad_right.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + diff --git a/src/pandroid/app/src/main/res/drawable/dpad_up.xml b/src/pandroid/app/src/main/res/drawable/dpad_up.xml new file mode 100644 index 00000000..4bc0a96b --- /dev/null +++ b/src/pandroid/app/src/main/res/drawable/dpad_up.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + diff --git a/src/pandroid/app/src/main/res/drawable/ic_add.xml b/src/pandroid/app/src/main/res/drawable/ic_add.xml new file mode 100644 index 00000000..89633bb1 --- /dev/null +++ b/src/pandroid/app/src/main/res/drawable/ic_add.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 deleted file mode 100644 index 81855e14..00000000 --- a/src/pandroid/app/src/main/res/drawable/simple_analog_background.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - \ 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 deleted file mode 100644 index 15879540..00000000 --- a/src/pandroid/app/src/main/res/drawable/simple_circle_button.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/src/pandroid/app/src/main/res/layout/controller_dpad.xml b/src/pandroid/app/src/main/res/layout/controller_dpad.xml index 0eea66ac..e3320daf 100644 --- a/src/pandroid/app/src/main/res/layout/controller_dpad.xml +++ b/src/pandroid/app/src/main/res/layout/controller_dpad.xml @@ -1,28 +1,34 @@ + android:layout_width="57pt" + android:layout_height="57pt" + android:layout_gravity="bottom" + android:layout_marginBottom="40pt"> + style="@style/ControllerStyle.SimpleButton" + android:background="@drawable/dpad_up" + android:layout_marginLeft="19pt"/> + style="@style/ControllerStyle.SimpleButton" + android:background="@drawable/dpad_left" + android:layout_marginTop="19pt"/> + style="@style/ControllerStyle.SimpleButton" + android:background="@drawable/dpad_down" + android:layout_marginTop="38pt" + android:layout_marginLeft="19pt"/> + style="@style/ControllerStyle.SimpleButton" + android:background="@drawable/dpad_right" + android:layout_marginTop="19pt" + android:layout_marginLeft="38pt"/> \ No newline at end of file diff --git a/src/pandroid/app/src/main/res/layout/controller_gamepad.xml b/src/pandroid/app/src/main/res/layout/controller_gamepad.xml index 16b5ba72..dce6a60e 100644 --- a/src/pandroid/app/src/main/res/layout/controller_gamepad.xml +++ b/src/pandroid/app/src/main/res/layout/controller_gamepad.xml @@ -1,32 +1,34 @@ + android:layout_width="57pt" + android:layout_height="57pt" + android:layout_gravity="end|bottom" + android:layout_marginBottom="30pt"> + style="@style/ControllerStyle.SimpleButton" + android:layout_marginStart="19pt" + android:background="@drawable/button_x"/> + style="@style/ControllerStyle.SimpleButton" + android:layout_marginTop="19pt" + android:background="@drawable/button_y"/> + style="@style/ControllerStyle.SimpleButton" + android:layout_marginTop="38pt" + android:layout_marginStart="19pt" + android:background="@drawable/button_b"/> + style="@style/ControllerStyle.SimpleButton" + android:layout_marginTop="19pt" + android:layout_marginStart="38pt" + android:background="@drawable/button_a"/> \ No newline at end of file diff --git a/src/pandroid/app/src/main/res/layout/controller_joystick.xml b/src/pandroid/app/src/main/res/layout/controller_joystick.xml new file mode 100644 index 00000000..d758822f --- /dev/null +++ b/src/pandroid/app/src/main/res/layout/controller_joystick.xml @@ -0,0 +1,11 @@ + + \ No newline at end of file diff --git a/src/pandroid/app/src/main/res/layout/controller_l.xml b/src/pandroid/app/src/main/res/layout/controller_l.xml new file mode 100644 index 00000000..96ec7582 --- /dev/null +++ b/src/pandroid/app/src/main/res/layout/controller_l.xml @@ -0,0 +1,10 @@ + + diff --git a/src/pandroid/app/src/main/res/layout/controller_r.xml b/src/pandroid/app/src/main/res/layout/controller_r.xml new file mode 100644 index 00000000..8aa2fba1 --- /dev/null +++ b/src/pandroid/app/src/main/res/layout/controller_r.xml @@ -0,0 +1,10 @@ + + diff --git a/src/pandroid/app/src/main/res/layout/controller_select.xml b/src/pandroid/app/src/main/res/layout/controller_select.xml new file mode 100644 index 00000000..a77855be --- /dev/null +++ b/src/pandroid/app/src/main/res/layout/controller_select.xml @@ -0,0 +1,10 @@ + + \ No newline at end of file diff --git a/src/pandroid/app/src/main/res/layout/controller_start.xml b/src/pandroid/app/src/main/res/layout/controller_start.xml new file mode 100644 index 00000000..94f12eb7 --- /dev/null +++ b/src/pandroid/app/src/main/res/layout/controller_start.xml @@ -0,0 +1,10 @@ + + \ No newline at end of file diff --git a/src/pandroid/app/src/main/res/layout/game_activity.xml b/src/pandroid/app/src/main/res/layout/game_activity.xml index 6f6f65d7..9536a847 100644 --- a/src/pandroid/app/src/main/res/layout/game_activity.xml +++ b/src/pandroid/app/src/main/res/layout/game_activity.xml @@ -1,5 +1,6 @@ @@ -18,95 +19,18 @@ + android:layout_height="match_parent" + android:alpha="0.6" + android:padding="14pt"> - + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/pandroid/app/src/main/res/values/styleable.xml b/src/pandroid/app/src/main/res/values/styleable.xml index c5fc4329..69219724 100644 --- a/src/pandroid/app/src/main/res/values/styleable.xml +++ b/src/pandroid/app/src/main/res/values/styleable.xml @@ -1,10 +1,12 @@ - \ No newline at end of file From f553e800801bcb93d28c3155a3ad4cf54776a9af Mon Sep 17 00:00:00 2001 From: GabrielBRDeveloper Date: Thu, 14 Dec 2023 12:55:28 -0400 Subject: [PATCH 063/162] GlobalConfig and create node data storage --- src/pandroid/app/src/main/AndroidManifest.xml | 1 + .../pandroid/app/PandaApplication.java | 21 +++ .../pandroid/data/config/GlobalConfig.java | 61 +++++++ .../panda3ds/pandroid/data/node/Caster.java | 40 +++++ .../pandroid/data/node/NodeArray.java | 133 ++++++++++++++ .../panda3ds/pandroid/data/node/NodeBase.java | 34 ++++ .../pandroid/data/node/NodeObject.java | 164 ++++++++++++++++++ .../com/panda3ds/pandroid/lang/Function.java | 5 + .../panda3ds/pandroid/utils/Constants.java | 2 + 9 files changed, 461 insertions(+) create mode 100644 src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/PandaApplication.java create mode 100644 src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/config/GlobalConfig.java create mode 100644 src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/node/Caster.java create mode 100644 src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/node/NodeArray.java create mode 100644 src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/node/NodeBase.java create mode 100644 src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/node/NodeObject.java create mode 100644 src/pandroid/app/src/main/java/com/panda3ds/pandroid/lang/Function.java diff --git a/src/pandroid/app/src/main/AndroidManifest.xml b/src/pandroid/app/src/main/AndroidManifest.xml index 291496f4..a4379fb2 100644 --- a/src/pandroid/app/src/main/AndroidManifest.xml +++ b/src/pandroid/app/src/main/AndroidManifest.xml @@ -11,6 +11,7 @@ android:glEsVersion="0x0030001"/> T get(Key key) { + Serializable value; + if (key.defValue instanceof String) { + value = data.getString(key.name, (String) key.defValue); + } else if (key.defValue instanceof Integer) { + value = data.getInt(key.name, (int) key.defValue); + } else if (key.defValue instanceof Boolean) { + value = data.getBoolean(key.name, (Boolean) key.defValue); + } else if (key.defValue instanceof Long) { + value = data.getLong(key.name, (Long) key.defValue); + } else { + value = data.getFloat(key.name, ((Number) key.defValue).floatValue()); + } + return (T) value; + } + + //Need synchronized why SharedPreferences don't support aysnc write + private static synchronized void set(Key key, T value) { + if (value instanceof String) { + data.edit().putString(key.name, (String) value).apply(); + } else if (value instanceof Integer) { + data.edit().putInt(key.name, (Integer) value).apply(); + } else if (value instanceof Boolean) { + data.edit().putBoolean(key.name, (Boolean) value).apply(); + } else if (value instanceof Long) { + data.edit().putLong(key.name, (Long) value).apply(); + } else if (value instanceof Float) { + data.edit().putFloat(key.name, (Float) value).apply(); + } else { + throw new IllegalArgumentException("Invalid global config value instance"); + } + } + + private static class Key { + private final String name; + private final T defValue; + + private Key(String name, T defValue) { + this.name = name; + this.defValue = defValue; + } + } +} \ No newline at end of file diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/node/Caster.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/node/Caster.java new file mode 100644 index 00000000..68f45302 --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/node/Caster.java @@ -0,0 +1,40 @@ +package com.panda3ds.pandroid.data.node; + +/** + * JAVA THINGS: + * Java allow cast primary (int, double, float and long) + * but crash on cast object number why!! + **/ +class Caster { + public static int intValue(Object value){ + if (value instanceof Number){ + return ((Number)value).intValue(); + } else { + return Integer.parseInt((String) value); + } + } + + public static float floatValue(Object value){ + if (value instanceof Number){ + return ((Number)value).floatValue(); + } else { + return Float.parseFloat((String) value); + } + } + + public static long longValue(Object value){ + if (value instanceof Number){ + return ((Number)value).longValue(); + } else { + return Long.parseLong((String) value); + } + } + + public static double doubleValue(Object value){ + if (value instanceof Number){ + return ((Number)value).doubleValue(); + } else { + return Double.parseDouble((String) value); + } + } +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/node/NodeArray.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/node/NodeArray.java new file mode 100644 index 00000000..7f8a318b --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/node/NodeArray.java @@ -0,0 +1,133 @@ +package com.panda3ds.pandroid.data.node; + +import androidx.annotation.NonNull; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; + +public class NodeArray extends NodeBase { + public static final String EMPTY_SOURCE = "[]"; + + private final ArrayList list = new ArrayList<>(); + + NodeArray(JSONArray array) { + init(array); + } + + public NodeArray() { + this(EMPTY_SOURCE); + } + + public NodeArray(String source) { + try { + init(new JSONArray(source)); + } catch (JSONException e) { + throw new IllegalArgumentException(e); + } + } + + private void init(JSONArray array) { + try { + for (int i = 0; i < array.length(); i++) { + Object item = array.get(i); + if (item instanceof JSONArray) { + item = new NodeArray((JSONArray) item); + ((NodeArray) item).setParent(this); + } else if (item instanceof JSONObject) { + item = new NodeObject((JSONObject) item); + ((NodeObject) item).setParent(this); + } + list.add(item); + } + } catch (Exception e) { + throw new IllegalArgumentException(e); + } + } + + private void add(Object obj) { + list.add(obj); + changed(); + } + + public String getString(int index) { + return (String) list.get(index); + } + + public int getInteger(int index) { + return Caster.intValue(list.get(index)); + } + + public long getLong(int index) { + return Caster.longValue(list.get(index)); + } + + public boolean getBoolean(int index) { + return (boolean) list.get(index); + } + + public double getDouble(int index) { + return Caster.doubleValue(list.get(index)); + } + + public NodeArray getArray(int index) { + return (NodeArray) list.get(index); + } + + public NodeObject getObject(int index) { + return (NodeObject) list.get(index); + } + + public void add(String val) { + list.add(val); + } + + public void add(int val) { + add((Object) val); + } + + public void add(long val) { + add((Object) val); + } + + public void add(double val) { + add((Object) val); + } + + public void add(boolean val) { + add((Object) val); + } + + public void add(NodeBase val) { + add((Object) val); + } + + public int getSize() { + return list.size(); + } + + @NonNull + @Override + public String toString() { + try { + return ((JSONArray) buildValue()).toString(4); + } catch (JSONException e) { + throw new RuntimeException(e); + } + } + + @Override + protected Object buildValue() { + JSONArray array = new JSONArray(); + for (Object obj : list) { + if (obj instanceof NodeBase) { + array.put(((NodeBase) obj).buildValue()); + } else { + array.put(obj); + } + } + return array; + } +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/node/NodeBase.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/node/NodeBase.java new file mode 100644 index 00000000..12961a4f --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/node/NodeBase.java @@ -0,0 +1,34 @@ +package com.panda3ds.pandroid.data.node; + +import androidx.annotation.NonNull; + +import com.panda3ds.pandroid.lang.Function; + +abstract class NodeBase { + private NodeBase parent = null; + private Function changeListener; + + protected void setParent(NodeBase parent) { + this.parent = parent; + } + + protected void changed() { + if (parent != null) + parent.changed(); + if (changeListener != null) + changeListener.run(this); + } + + public void setChangeListener(Function listener) { + changeListener = val -> listener.run((T) val); + } + + protected NodeBase getParent() { + return parent; + } + + protected abstract Object buildValue(); + + @NonNull + public abstract String toString(); +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/node/NodeObject.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/node/NodeObject.java new file mode 100644 index 00000000..40cb1682 --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/node/NodeObject.java @@ -0,0 +1,164 @@ +package com.panda3ds.pandroid.data.node; + +import androidx.annotation.NonNull; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.HashMap; +import java.util.Iterator; + +public class NodeObject extends NodeBase { + + public static final String EMPTY_SOURCE = "{}"; + private final HashMap map = new HashMap<>(); + + NodeObject(JSONObject obj) { + init(obj); + } + + public NodeObject() { + this(EMPTY_SOURCE); + } + + public NodeObject(String source) { + try { + init(new JSONObject(source)); + } catch (JSONException e) { + throw new IllegalArgumentException(e); + } + } + + private void init(JSONObject base) { + try { + Iterator keys = base.keys(); + while (keys.hasNext()) { + String key = keys.next(); + Object item = base.get(key); + if (item instanceof JSONObject) { + item = new NodeObject((JSONObject) item); + ((NodeObject) item).setParent(this); + } else if (item instanceof JSONArray) { + item = new NodeArray((JSONArray) item); + ((NodeArray) item).setParent(this); + } + map.put(key, item); + } + } catch (Exception e) { + throw new IllegalArgumentException(e); + } + } + + private Object get(String key, Object def) { + if (!has(key)) + return def; + + return map.get(key); + } + + private void put(String key, Object value) { + map.put(key, value); + changed(); + } + + public String getString(String key, String def) { + return (String) get(key, def); + } + + public int getInteger(String key, int def) { + return Caster.intValue(get(key, def)); + } + + public long getLong(String key, long def) { + return Caster.longValue(get(key, def)); + } + + public boolean getBoolean(String key, boolean def) { + return (boolean) get(key, def); + } + + public double getDouble(String key, double def) { + return Caster.doubleValue(get(key, def)); + } + + public NodeArray getArray(String key, NodeArray def) { + return (NodeArray) get(key, def); + } + + public NodeObject getObject(String key, NodeObject def) { + return (NodeObject) get(key, def); + } + + public void put(String key, String val) { + put(key, (Object) val); + } + + public void put(String key, int val) { + put(key, (Object) val); + } + + public void put(String key, double val) { + put(key, (Object) val); + } + + public void put(String key, boolean val) { + put(key, (Object) val); + } + + public void put(String key, NodeBase val) { + put(key, (Object) val); + } + + public boolean has(String key) { + return map.containsKey(key); + } + + public void remove(String key) { + map.remove(key); + changed(); + } + + + public void clear() { + map.clear(); + changed(); + } + + public String[] getKeys() { + return map.keySet().toArray(new String[0]); + } + + + public int getSize() { + return map.size(); + } + + @NonNull + @Override + public String toString() { + try { + return ((JSONObject) buildValue()).toString(4); + } catch (JSONException e) { + throw new RuntimeException(e); + } + } + + @Override + protected Object buildValue() { + try { + JSONObject dest = new JSONObject(); + for (String key : getKeys()) { + Object obj = map.get(key); + if (obj instanceof NodeBase) { + obj = ((NodeBase) obj).buildValue(); + } + dest.put(key, obj); + } + + return dest; + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/lang/Function.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/lang/Function.java new file mode 100644 index 00000000..c5e6a4f4 --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/lang/Function.java @@ -0,0 +1,5 @@ +package com.panda3ds.pandroid.lang; + +public interface Function { + void run(T val); +} 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 index d94164c7..1aac0a4d 100644 --- 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 @@ -20,4 +20,6 @@ public class Constants { public static final String ACTIVITY_PARAMETER_PATH = "path"; public static final String LOG_TAG = "pandroid"; + + public static final String PREF_GLOBAL_CONFIG = "app.GlobalConfig"; } From 71109d701d37fd7117768fb1c41c0718dc0b5db4 Mon Sep 17 00:00:00 2001 From: GabrielBRDeveloper Date: Thu, 14 Dec 2023 14:22:55 -0400 Subject: [PATCH 064/162] Remove Node storage for replace by Gson --- src/pandroid/app/build.gradle.kts | 1 + src/pandroid/app/src/main/AndroidManifest.xml | 2 +- ...lication.java => PandroidApplication.java} | 2 +- .../pandroid/data/config/GlobalConfig.java | 4 +- .../panda3ds/pandroid/data/node/Caster.java | 40 ----- .../pandroid/data/node/NodeArray.java | 133 -------------- .../panda3ds/pandroid/data/node/NodeBase.java | 34 ---- .../pandroid/data/node/NodeObject.java | 164 ------------------ 8 files changed, 5 insertions(+), 375 deletions(-) rename src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/{PandaApplication.java => PandroidApplication.java} (88%) delete mode 100644 src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/node/Caster.java delete mode 100644 src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/node/NodeArray.java delete mode 100644 src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/node/NodeBase.java delete mode 100644 src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/node/NodeObject.java diff --git a/src/pandroid/app/build.gradle.kts b/src/pandroid/app/build.gradle.kts index 276eb552..91a36c6e 100644 --- a/src/pandroid/app/build.gradle.kts +++ b/src/pandroid/app/build.gradle.kts @@ -39,4 +39,5 @@ dependencies { implementation("androidx.appcompat:appcompat:1.6.1") implementation("com.google.android.material:material:1.8.0") implementation("androidx.constraintlayout:constraintlayout:2.1.4") + implementation("com.google.code.gson:gson:2.10.1") } \ No newline at end of file diff --git a/src/pandroid/app/src/main/AndroidManifest.xml b/src/pandroid/app/src/main/AndroidManifest.xml index a4379fb2..15bf6270 100644 --- a/src/pandroid/app/src/main/AndroidManifest.xml +++ b/src/pandroid/app/src/main/AndroidManifest.xml @@ -11,7 +11,7 @@ android:glEsVersion="0x0030001"/> list = new ArrayList<>(); - - NodeArray(JSONArray array) { - init(array); - } - - public NodeArray() { - this(EMPTY_SOURCE); - } - - public NodeArray(String source) { - try { - init(new JSONArray(source)); - } catch (JSONException e) { - throw new IllegalArgumentException(e); - } - } - - private void init(JSONArray array) { - try { - for (int i = 0; i < array.length(); i++) { - Object item = array.get(i); - if (item instanceof JSONArray) { - item = new NodeArray((JSONArray) item); - ((NodeArray) item).setParent(this); - } else if (item instanceof JSONObject) { - item = new NodeObject((JSONObject) item); - ((NodeObject) item).setParent(this); - } - list.add(item); - } - } catch (Exception e) { - throw new IllegalArgumentException(e); - } - } - - private void add(Object obj) { - list.add(obj); - changed(); - } - - public String getString(int index) { - return (String) list.get(index); - } - - public int getInteger(int index) { - return Caster.intValue(list.get(index)); - } - - public long getLong(int index) { - return Caster.longValue(list.get(index)); - } - - public boolean getBoolean(int index) { - return (boolean) list.get(index); - } - - public double getDouble(int index) { - return Caster.doubleValue(list.get(index)); - } - - public NodeArray getArray(int index) { - return (NodeArray) list.get(index); - } - - public NodeObject getObject(int index) { - return (NodeObject) list.get(index); - } - - public void add(String val) { - list.add(val); - } - - public void add(int val) { - add((Object) val); - } - - public void add(long val) { - add((Object) val); - } - - public void add(double val) { - add((Object) val); - } - - public void add(boolean val) { - add((Object) val); - } - - public void add(NodeBase val) { - add((Object) val); - } - - public int getSize() { - return list.size(); - } - - @NonNull - @Override - public String toString() { - try { - return ((JSONArray) buildValue()).toString(4); - } catch (JSONException e) { - throw new RuntimeException(e); - } - } - - @Override - protected Object buildValue() { - JSONArray array = new JSONArray(); - for (Object obj : list) { - if (obj instanceof NodeBase) { - array.put(((NodeBase) obj).buildValue()); - } else { - array.put(obj); - } - } - return array; - } -} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/node/NodeBase.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/node/NodeBase.java deleted file mode 100644 index 12961a4f..00000000 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/node/NodeBase.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.panda3ds.pandroid.data.node; - -import androidx.annotation.NonNull; - -import com.panda3ds.pandroid.lang.Function; - -abstract class NodeBase { - private NodeBase parent = null; - private Function changeListener; - - protected void setParent(NodeBase parent) { - this.parent = parent; - } - - protected void changed() { - if (parent != null) - parent.changed(); - if (changeListener != null) - changeListener.run(this); - } - - public void setChangeListener(Function listener) { - changeListener = val -> listener.run((T) val); - } - - protected NodeBase getParent() { - return parent; - } - - protected abstract Object buildValue(); - - @NonNull - public abstract String toString(); -} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/node/NodeObject.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/node/NodeObject.java deleted file mode 100644 index 40cb1682..00000000 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/node/NodeObject.java +++ /dev/null @@ -1,164 +0,0 @@ -package com.panda3ds.pandroid.data.node; - -import androidx.annotation.NonNull; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.HashMap; -import java.util.Iterator; - -public class NodeObject extends NodeBase { - - public static final String EMPTY_SOURCE = "{}"; - private final HashMap map = new HashMap<>(); - - NodeObject(JSONObject obj) { - init(obj); - } - - public NodeObject() { - this(EMPTY_SOURCE); - } - - public NodeObject(String source) { - try { - init(new JSONObject(source)); - } catch (JSONException e) { - throw new IllegalArgumentException(e); - } - } - - private void init(JSONObject base) { - try { - Iterator keys = base.keys(); - while (keys.hasNext()) { - String key = keys.next(); - Object item = base.get(key); - if (item instanceof JSONObject) { - item = new NodeObject((JSONObject) item); - ((NodeObject) item).setParent(this); - } else if (item instanceof JSONArray) { - item = new NodeArray((JSONArray) item); - ((NodeArray) item).setParent(this); - } - map.put(key, item); - } - } catch (Exception e) { - throw new IllegalArgumentException(e); - } - } - - private Object get(String key, Object def) { - if (!has(key)) - return def; - - return map.get(key); - } - - private void put(String key, Object value) { - map.put(key, value); - changed(); - } - - public String getString(String key, String def) { - return (String) get(key, def); - } - - public int getInteger(String key, int def) { - return Caster.intValue(get(key, def)); - } - - public long getLong(String key, long def) { - return Caster.longValue(get(key, def)); - } - - public boolean getBoolean(String key, boolean def) { - return (boolean) get(key, def); - } - - public double getDouble(String key, double def) { - return Caster.doubleValue(get(key, def)); - } - - public NodeArray getArray(String key, NodeArray def) { - return (NodeArray) get(key, def); - } - - public NodeObject getObject(String key, NodeObject def) { - return (NodeObject) get(key, def); - } - - public void put(String key, String val) { - put(key, (Object) val); - } - - public void put(String key, int val) { - put(key, (Object) val); - } - - public void put(String key, double val) { - put(key, (Object) val); - } - - public void put(String key, boolean val) { - put(key, (Object) val); - } - - public void put(String key, NodeBase val) { - put(key, (Object) val); - } - - public boolean has(String key) { - return map.containsKey(key); - } - - public void remove(String key) { - map.remove(key); - changed(); - } - - - public void clear() { - map.clear(); - changed(); - } - - public String[] getKeys() { - return map.keySet().toArray(new String[0]); - } - - - public int getSize() { - return map.size(); - } - - @NonNull - @Override - public String toString() { - try { - return ((JSONObject) buildValue()).toString(4); - } catch (JSONException e) { - throw new RuntimeException(e); - } - } - - @Override - protected Object buildValue() { - try { - JSONObject dest = new JSONObject(); - for (String key : getKeys()) { - Object obj = map.get(key); - if (obj instanceof NodeBase) { - obj = ((NodeBase) obj).buildValue(); - } - dest.put(key, obj); - } - - return dest; - } catch (Exception e) { - throw new RuntimeException(e); - } - } -} From cc44801acdb59887ca88c069e85b85e11912a548 Mon Sep 17 00:00:00 2001 From: GabrielBRDeveloper Date: Fri, 15 Dec 2023 11:08:42 -0400 Subject: [PATCH 065/162] paris changes --- src/pandroid/app/build.gradle.kts | 1 - .../pandroid/data/config/GlobalConfig.java | 28 +++++++++---------- .../com/panda3ds/pandroid/lang/Function.java | 5 ---- 3 files changed, 14 insertions(+), 20 deletions(-) delete mode 100644 src/pandroid/app/src/main/java/com/panda3ds/pandroid/lang/Function.java diff --git a/src/pandroid/app/build.gradle.kts b/src/pandroid/app/build.gradle.kts index 91a36c6e..276eb552 100644 --- a/src/pandroid/app/build.gradle.kts +++ b/src/pandroid/app/build.gradle.kts @@ -39,5 +39,4 @@ dependencies { implementation("androidx.appcompat:appcompat:1.6.1") implementation("com.google.android.material:material:1.8.0") implementation("androidx.constraintlayout:constraintlayout:2.1.4") - implementation("com.google.code.gson:gson:2.10.1") } \ No newline at end of file diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/config/GlobalConfig.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/config/GlobalConfig.java index 40a1ebec..d1427dba 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/config/GlobalConfig.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/config/GlobalConfig.java @@ -16,24 +16,24 @@ public class GlobalConfig { .getSharedPreferences(Constants.PREF_GLOBAL_CONFIG, Context.MODE_PRIVATE); } - private static T get(Key key) { + public static T get(Key key) { Serializable value; - if (key.defValue instanceof String) { - value = data.getString(key.name, (String) key.defValue); - } else if (key.defValue instanceof Integer) { - value = data.getInt(key.name, (int) key.defValue); - } else if (key.defValue instanceof Boolean) { - value = data.getBoolean(key.name, (Boolean) key.defValue); - } else if (key.defValue instanceof Long) { - value = data.getLong(key.name, (Long) key.defValue); + if (key.defaultValue instanceof String) { + value = data.getString(key.name, (String) key.defaultValue); + } else if (key.defaultValue instanceof Integer) { + value = data.getInt(key.name, (int) key.defaultValue); + } else if (key.defaultValue instanceof Boolean) { + value = data.getBoolean(key.name, (boolean) key.defaultValue); + } else if (key.defaultValue instanceof Long) { + value = data.getLong(key.name, (long) key.defaultValue); } else { - value = data.getFloat(key.name, ((Number) key.defValue).floatValue()); + value = data.getFloat(key.name, (float) key.defaultValue); } return (T) value; } //Need synchronized why SharedPreferences don't support aysnc write - private static synchronized void set(Key key, T value) { + public static synchronized void set(Key key, T value) { if (value instanceof String) { data.edit().putString(key.name, (String) value).apply(); } else if (value instanceof Integer) { @@ -51,11 +51,11 @@ public class GlobalConfig { private static class Key { private final String name; - private final T defValue; + private final T defaultValue; - private Key(String name, T defValue) { + private Key(String name, T defaultValue) { this.name = name; - this.defValue = defValue; + this.defaultValue = defaultValue; } } } \ No newline at end of file diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/lang/Function.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/lang/Function.java deleted file mode 100644 index c5e6a4f4..00000000 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/lang/Function.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.panda3ds.pandroid.lang; - -public interface Function { - void run(T val); -} From 102361174a1abcaa4ab5535a0bc7478c560586c1 Mon Sep 17 00:00:00 2001 From: GabrielBRDeveloper Date: Fri, 15 Dec 2023 11:11:53 -0400 Subject: [PATCH 066/162] paris changes --- .../com/panda3ds/pandroid/data/config/GlobalConfig.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/config/GlobalConfig.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/config/GlobalConfig.java index d1427dba..185eb9d1 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/config/GlobalConfig.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/config/GlobalConfig.java @@ -37,13 +37,13 @@ public class GlobalConfig { if (value instanceof String) { data.edit().putString(key.name, (String) value).apply(); } else if (value instanceof Integer) { - data.edit().putInt(key.name, (Integer) value).apply(); + data.edit().putInt(key.name, (int) value).apply(); } else if (value instanceof Boolean) { - data.edit().putBoolean(key.name, (Boolean) value).apply(); + data.edit().putBoolean(key.name, (boolean) value).apply(); } else if (value instanceof Long) { - data.edit().putLong(key.name, (Long) value).apply(); + data.edit().putLong(key.name, (long) value).apply(); } else if (value instanceof Float) { - data.edit().putFloat(key.name, (Float) value).apply(); + data.edit().putFloat(key.name, (float) value).apply(); } else { throw new IllegalArgumentException("Invalid global config value instance"); } From ab37cb571d7574db0dba94ede7af1ac4730a1763 Mon Sep 17 00:00:00 2001 From: GabrielBRDeveloper Date: Fri, 15 Dec 2023 11:12:48 -0400 Subject: [PATCH 067/162] paris changes --- .../java/com/panda3ds/pandroid/data/config/GlobalConfig.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/config/GlobalConfig.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/config/GlobalConfig.java index 185eb9d1..ddfee09e 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/config/GlobalConfig.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/config/GlobalConfig.java @@ -32,7 +32,6 @@ public class GlobalConfig { return (T) value; } - //Need synchronized why SharedPreferences don't support aysnc write public static synchronized void set(Key key, T value) { if (value instanceof String) { data.edit().putString(key.name, (String) value).apply(); From 9fd94f0b1712b8d5b72e9b753e55527d2ac8a9c6 Mon Sep 17 00:00:00 2001 From: Gabriel Machado <97042217+GabrielBRDeveloper@users.noreply.github.com> Date: Fri, 15 Dec 2023 11:17:27 -0400 Subject: [PATCH 068/162] Update GlobalConfig.java --- .../panda3ds/pandroid/data/config/GlobalConfig.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/config/GlobalConfig.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/config/GlobalConfig.java index d1427dba..d25e34c7 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/config/GlobalConfig.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/config/GlobalConfig.java @@ -32,18 +32,17 @@ public class GlobalConfig { return (T) value; } - //Need synchronized why SharedPreferences don't support aysnc write public static synchronized void set(Key key, T value) { if (value instanceof String) { data.edit().putString(key.name, (String) value).apply(); } else if (value instanceof Integer) { - data.edit().putInt(key.name, (Integer) value).apply(); + data.edit().putInt(key.name, (int) value).apply(); } else if (value instanceof Boolean) { - data.edit().putBoolean(key.name, (Boolean) value).apply(); + data.edit().putBoolean(key.name, (boolean) value).apply(); } else if (value instanceof Long) { - data.edit().putLong(key.name, (Long) value).apply(); + data.edit().putLong(key.name, (long) value).apply(); } else if (value instanceof Float) { - data.edit().putFloat(key.name, (Float) value).apply(); + data.edit().putFloat(key.name, (float) value).apply(); } else { throw new IllegalArgumentException("Invalid global config value instance"); } @@ -58,4 +57,4 @@ public class GlobalConfig { this.defaultValue = defaultValue; } } -} \ No newline at end of file +} From c57f2db6c0de7b155d5cf527eed9681ede5ab676 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Sat, 16 Dec 2023 15:51:45 +0200 Subject: [PATCH 069/162] [Qt] Add text editor --- .gitmodules | 3 +++ CMakeLists.txt | 15 +++++++++++++-- include/panda_qt/main_window.hpp | 9 +++++---- include/panda_qt/text_editor.hpp | 22 ++++++++++++++++++++++ src/panda_qt/main_window.cpp | 6 ++++++ src/panda_qt/text_editor.cpp | 20 ++++++++++++++++++++ src/panda_qt/zep.cpp | 2 ++ third_party/zep | 1 + 8 files changed, 72 insertions(+), 6 deletions(-) create mode 100644 include/panda_qt/text_editor.hpp create mode 100644 src/panda_qt/text_editor.cpp create mode 100644 src/panda_qt/zep.cpp create mode 160000 third_party/zep diff --git a/.gitmodules b/.gitmodules index 8a6cac49..428ca1d1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -43,3 +43,6 @@ [submodule "third_party/hydra_core"] path = third_party/hydra_core url = https://github.com/hydra-emu/core +[submodule "third_party/zep"] + path = third_party/zep + url = https://github.com/Panda3DS-emu/zep diff --git a/CMakeLists.txt b/CMakeLists.txt index 5e49c27c..732ec793 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -186,12 +186,20 @@ set(RENDERER_SW_SOURCE_FILES src/core/renderer_sw/renderer_sw.cpp) # Frontend source 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 src/panda_qt/config_window.cpp) - set(FRONTEND_HEADER_FILES include/panda_qt/screen.hpp include/panda_qt/main_window.hpp include/panda_qt/about_window.hpp include/panda_qt/config_window.hpp) + 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 + src/panda_qt/config_window.cpp src/panda_qt/zep.cpp src/panda_qt/text_editor.cpp + ) + set(FRONTEND_HEADER_FILES include/panda_qt/screen.hpp include/panda_qt/main_window.hpp include/panda_qt/about_window.hpp + include/panda_qt/config_window.hpp include/panda_qt/text_editor.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}) + + include_directories(third_party/zep/include) # Include zep for text editor usage + configure_file(third_party/zep/cmake/config_app.h.cmake ${CMAKE_BINARY_DIR}/zep_config/config_app.h) + include_directories(${CMAKE_BINARY_DIR}/zep_config) else() set(FRONTEND_SOURCE_FILES src/panda_sdl/main.cpp src/panda_sdl/frontend_sdl.cpp) set(FRONTEND_HEADER_FILES "") @@ -428,6 +436,9 @@ endif() if(ENABLE_QT_GUI) target_compile_definitions(Alber PUBLIC "PANDA3DS_FRONTEND_QT=1") + target_compile_definitions(Alber PUBLIC "ZEP_QT=1") + target_compile_definitions(Alber PUBLIC "ZEP_FEATURE_CPP_FILE_SYSTEM=1") + target_link_libraries(Alber PRIVATE Qt6::Widgets) if(LINUX OR FREEBSD) diff --git a/include/panda_qt/main_window.hpp b/include/panda_qt/main_window.hpp index e2fd8596..ee24efdd 100644 --- a/include/panda_qt/main_window.hpp +++ b/include/panda_qt/main_window.hpp @@ -13,6 +13,7 @@ #include "panda_qt/about_window.hpp" #include "panda_qt/config_window.hpp" #include "panda_qt/screen.hpp" +#include "panda_qt/text_editor.hpp" #include "services/hid.hpp" class MainWindow : public QMainWindow { @@ -20,9 +21,7 @@ class MainWindow : public QMainWindow { private: // Types of messages we might send from the GUI thread to the emulator thread - enum class MessageType { - LoadROM, Reset, Pause, Resume, TogglePause, DumpRomFS, PressKey, ReleaseKey - }; + enum class MessageType { LoadROM, Reset, Pause, Resume, TogglePause, DumpRomFS, PressKey, ReleaseKey }; // Tagged union representing our message queue messages struct EmulatorMessage { @@ -43,7 +42,7 @@ class MainWindow : public QMainWindow { Emulator* emu = nullptr; std::thread emuThread; - std::atomic appRunning = true; // Is the application itself running? + std::atomic appRunning = true; // Is the application itself running? // Used for synchronizing messages between the emulator and UI std::mutex messageQueueMutex; std::vector messageQueue; @@ -51,12 +50,14 @@ class MainWindow : public QMainWindow { ScreenWidget screen; AboutWindow* aboutWindow; ConfigWindow* configWindow; + TextEditorWindow* luaEditor; QMenuBar* menuBar = nullptr; void swapEmuBuffer(); void emuThreadMainLoop(); void selectROM(); void dumpRomFS(); + void openLuaEditor(); void showAboutMenu(); void sendMessage(const EmulatorMessage& message); void dispatchMessage(const EmulatorMessage& message); diff --git a/include/panda_qt/text_editor.hpp b/include/panda_qt/text_editor.hpp new file mode 100644 index 00000000..fd0f7884 --- /dev/null +++ b/include/panda_qt/text_editor.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include +#include +#include +#include + +#include "zep.h" +#include "zep/mode_repl.h" +#include "zep/regress.h" + +class TextEditorWindow : public QDialog { + Q_OBJECT + + private: + Zep::ZepWidget_Qt zepWidget; + Zep::IZepReplProvider replProvider; + static constexpr float fontSize = 14.0f; + + public: + TextEditorWindow(QWidget* parent, const std::string& filename, const std::string& initialText); +}; \ No newline at end of file diff --git a/src/panda_qt/main_window.cpp b/src/panda_qt/main_window.cpp index 8dbfc7aa..95b36661 100644 --- a/src/panda_qt/main_window.cpp +++ b/src/panda_qt/main_window.cpp @@ -36,7 +36,9 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent) connect(configureAction, &QAction::triggered, this, [this]() { configWindow->show(); }); auto dumpRomFSAction = toolsMenu->addAction(tr("Dump RomFS")); + auto luaEditorAction = toolsMenu->addAction(tr("Open Lua Editor")); connect(dumpRomFSAction, &QAction::triggered, this, &MainWindow::dumpRomFS); + connect(luaEditorAction, &QAction::triggered, this, &MainWindow::openLuaEditor); auto aboutAction = aboutMenu->addAction(tr("About Panda3DS")); connect(aboutAction, &QAction::triggered, this, &MainWindow::showAboutMenu); @@ -44,6 +46,7 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent) // Set up misc objects aboutWindow = new AboutWindow(nullptr); configWindow = new ConfigWindow(this); + luaEditor = new TextEditorWindow(this, "script.lua", ""); emu = new Emulator(); emu->setOutputSize(screen.surfaceWidth, screen.surfaceHeight); @@ -133,6 +136,7 @@ MainWindow::~MainWindow() { delete menuBar; delete aboutWindow; delete configWindow; + delete luaEditor; } // Send a message to the emulator thread. Lock the mutex and just push back to the vector. @@ -181,6 +185,8 @@ void MainWindow::showAboutMenu() { about.exec(); } +void MainWindow::openLuaEditor() { luaEditor->show(); } + void MainWindow::dispatchMessage(const EmulatorMessage& message) { switch (message.type) { case MessageType::LoadROM: diff --git a/src/panda_qt/text_editor.cpp b/src/panda_qt/text_editor.cpp new file mode 100644 index 00000000..646db7d0 --- /dev/null +++ b/src/panda_qt/text_editor.cpp @@ -0,0 +1,20 @@ +#include "panda_qt/text_editor.hpp" +#include + +using namespace Zep; + +TextEditorWindow::TextEditorWindow(QWidget* parent, const std::string& filename, const std::string& initialText) + : QDialog(parent), zepWidget(this, qApp->applicationDirPath().toStdString(), fontSize) { + resize(600, 600); + + // Register our extensions + ZepRegressExCommand::Register(zepWidget.GetEditor()); + ZepReplExCommand::Register(zepWidget.GetEditor(), &replProvider); + + zepWidget.GetEditor().SetGlobalMode(Zep::ZepMode_Standard::StaticName()); + zepWidget.GetEditor().InitWithText(filename, initialText); + + QVBoxLayout* mainLayout = new QVBoxLayout(); + setLayout(mainLayout); + mainLayout->addWidget(&zepWidget); +} \ No newline at end of file diff --git a/src/panda_qt/zep.cpp b/src/panda_qt/zep.cpp new file mode 100644 index 00000000..570f0e64 --- /dev/null +++ b/src/panda_qt/zep.cpp @@ -0,0 +1,2 @@ +#define ZEP_SINGLE_HEADER_BUILD +#include "zep.h" \ No newline at end of file diff --git a/third_party/zep b/third_party/zep new file mode 160000 index 00000000..c125fcc3 --- /dev/null +++ b/third_party/zep @@ -0,0 +1 @@ +Subproject commit c125fcc3961dcb7c4ac073f7259edb4675209676 From 6386605b9758bcc9bd11f7ec3295f91a681a44a6 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Sat, 16 Dec 2023 16:36:03 +0200 Subject: [PATCH 070/162] We can now load lua scripts properly --- include/emulator.hpp | 2 ++ include/lua_manager.hpp | 5 +++++ include/panda_qt/main_window.hpp | 7 ++++++- src/lua.cpp | 25 +++++++++++++++++++++++++ src/panda_qt/main_window.cpp | 13 +++++++++++++ src/panda_qt/text_editor.cpp | 23 ++++++++++++++++++++++- 6 files changed, 73 insertions(+), 2 deletions(-) diff --git a/include/emulator.hpp b/include/emulator.hpp index d992b331..fcf93329 100644 --- a/include/emulator.hpp +++ b/include/emulator.hpp @@ -119,6 +119,8 @@ class Emulator { EmulatorConfig& getConfig() { return config; } Cheats& getCheats() { return cheats; } ServiceManager& getServiceManager() { return kernel.getServiceManager(); } + LuaManager& getLua() { return lua; } + RendererType getRendererType() const { return config.rendererType; } Renderer* getRenderer() { return gpu.getRenderer(); } u64 getTicks() { return cpu.getTicks(); } diff --git a/include/lua_manager.hpp b/include/lua_manager.hpp index 7ef96f18..50b8dd61 100644 --- a/include/lua_manager.hpp +++ b/include/lua_manager.hpp @@ -1,4 +1,6 @@ #pragma once +#include + #include "helpers.hpp" #include "memory.hpp" @@ -36,6 +38,8 @@ class LuaManager { void initialize(); void initializeThunks(); void loadFile(const char* path); + void loadString(const std::string& code); + void reset(); void signalEvent(LuaEvent e) { if (haveScript) [[unlikely]] { @@ -52,6 +56,7 @@ class LuaManager { void close() {} void initialize() {} void loadFile(const char* path) {} + void loadString(const std::string& code) {} void reset() {} void signalEvent(LuaEvent e) {} }; diff --git a/include/panda_qt/main_window.hpp b/include/panda_qt/main_window.hpp index ee24efdd..070ceb29 100644 --- a/include/panda_qt/main_window.hpp +++ b/include/panda_qt/main_window.hpp @@ -21,7 +21,7 @@ class MainWindow : public QMainWindow { private: // Types of messages we might send from the GUI thread to the emulator thread - enum class MessageType { LoadROM, Reset, Pause, Resume, TogglePause, DumpRomFS, PressKey, ReleaseKey }; + enum class MessageType { LoadROM, Reset, Pause, Resume, TogglePause, DumpRomFS, PressKey, ReleaseKey, LoadLuaScript }; // Tagged union representing our message queue messages struct EmulatorMessage { @@ -35,6 +35,10 @@ class MainWindow : public QMainWindow { struct { u32 key; } key; + + struct { + std::string* str; + } string; }; }; @@ -72,4 +76,5 @@ class MainWindow : public QMainWindow { void keyPressEvent(QKeyEvent* event) override; void keyReleaseEvent(QKeyEvent* event) override; + void loadLuaScript(const std::string& code); }; \ No newline at end of file diff --git a/src/lua.cpp b/src/lua.cpp index 729b6581..ec1287bd 100644 --- a/src/lua.cpp +++ b/src/lua.cpp @@ -51,6 +51,31 @@ void LuaManager::loadFile(const char* path) { } } +void LuaManager::loadString(const std::string& code) { + // Initialize Lua if it has not been initialized + if (!initialized) { + initialize(); + } + + // If init failed, don't execute + if (!initialized) { + printf("Lua initialization failed, file won't run\n"); + haveScript = false; + + return; + } + + int status = luaL_loadstring(L, code.c_str()); // load Lua script + int ret = lua_pcall(L, 0, 0, 0); // tell Lua to run the script + + if (ret != 0) { + haveScript = false; + fprintf(stderr, "%s\n", lua_tostring(L, -1)); // tell us what mistake we made + } else { + haveScript = true; + } +} + void LuaManager::signalEventInternal(LuaEvent e) { lua_getglobal(L, "eventHandler"); // We want to call the event handler lua_pushnumber(L, static_cast(e)); // Push event type diff --git a/src/panda_qt/main_window.cpp b/src/panda_qt/main_window.cpp index 95b36661..be3509da 100644 --- a/src/panda_qt/main_window.cpp +++ b/src/panda_qt/main_window.cpp @@ -195,6 +195,11 @@ void MainWindow::dispatchMessage(const EmulatorMessage& message) { delete message.path.p; break; + case MessageType::LoadLuaScript: + emu->getLua().loadString(*message.string.str); + delete message.string.str; + break; + case MessageType::Pause: emu->pause(); break; case MessageType::Resume: emu->resume(); break; case MessageType::TogglePause: emu->togglePause(); break; @@ -259,3 +264,11 @@ void MainWindow::keyReleaseEvent(QKeyEvent* event) { case Qt::Key_Backspace: releaseKey(HID::Keys::Select); break; } } + +void MainWindow::loadLuaScript(const std::string& code) { + EmulatorMessage message{.type = MessageType::LoadLuaScript}; + + // Make a copy of the code on the heap to send via the message queue + message.string.str = new std::string(code); + sendMessage(message); +} \ No newline at end of file diff --git a/src/panda_qt/text_editor.cpp b/src/panda_qt/text_editor.cpp index 646db7d0..bef4ec9f 100644 --- a/src/panda_qt/text_editor.cpp +++ b/src/panda_qt/text_editor.cpp @@ -1,6 +1,10 @@ #include "panda_qt/text_editor.hpp" + +#include #include +#include "panda_qt/main_window.hpp" + using namespace Zep; TextEditorWindow::TextEditorWindow(QWidget* parent, const std::string& filename, const std::string& initialText) @@ -13,8 +17,25 @@ TextEditorWindow::TextEditorWindow(QWidget* parent, const std::string& filename, zepWidget.GetEditor().SetGlobalMode(Zep::ZepMode_Standard::StaticName()); zepWidget.GetEditor().InitWithText(filename, initialText); - + QVBoxLayout* mainLayout = new QVBoxLayout(); setLayout(mainLayout); + + QPushButton* button = new QPushButton(tr("Load script"), this); + button->setFixedSize(100, 20); + + connect(button, &QPushButton::pressed, this, [this]() { + if (parentWidget()) { + auto buffer = zepWidget.GetEditor().GetMRUBuffer(); + const std::string text = buffer->GetBufferText(buffer->Begin(), buffer->End()); + + static_cast(parentWidget())->loadLuaScript(text); + } else { + // This should be unreachable, only here for safety purposes + printf("Text editor does not have any parent widget, click doesn't work :(\n"); + } + }); + + mainLayout->addWidget(button); mainLayout->addWidget(&zepWidget); } \ No newline at end of file From c30fbd3801315ddf743a41307301c7bbb612df31 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Sat, 16 Dec 2023 16:54:31 +0200 Subject: [PATCH 071/162] Fix syntax highlighting --- third_party/zep | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/third_party/zep b/third_party/zep index c125fcc3..0a47740d 160000 --- a/third_party/zep +++ b/third_party/zep @@ -1 +1 @@ -Subproject commit c125fcc3961dcb7c4ac073f7259edb4675209676 +Subproject commit 0a47740db9962b3d022a3c098d57784d223d203f From 7c6b4989185c5103dc77d9e9318d80ffddb6a2ed Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Sat, 16 Dec 2023 17:18:52 +0200 Subject: [PATCH 072/162] [Qt] Add capability to load scripts from files --- include/panda_qt/main_window.hpp | 1 + include/panda_qt/text_editor.hpp | 1 + src/panda_qt/main_window.cpp | 35 ++++++++++++++++++++++++++++++-- src/panda_qt/text_editor.cpp | 3 +++ 4 files changed, 38 insertions(+), 2 deletions(-) diff --git a/include/panda_qt/main_window.hpp b/include/panda_qt/main_window.hpp index 070ceb29..7dfb91b7 100644 --- a/include/panda_qt/main_window.hpp +++ b/include/panda_qt/main_window.hpp @@ -59,6 +59,7 @@ class MainWindow : public QMainWindow { void swapEmuBuffer(); void emuThreadMainLoop(); + void selectLuaFile(); void selectROM(); void dumpRomFS(); void openLuaEditor(); diff --git a/include/panda_qt/text_editor.hpp b/include/panda_qt/text_editor.hpp index fd0f7884..0da98294 100644 --- a/include/panda_qt/text_editor.hpp +++ b/include/panda_qt/text_editor.hpp @@ -19,4 +19,5 @@ class TextEditorWindow : public QDialog { public: TextEditorWindow(QWidget* parent, const std::string& filename, const std::string& initialText); + void setText(const std::string& text) { zepWidget.GetEditor().GetMRUBuffer()->SetText(text); } }; \ No newline at end of file diff --git a/src/panda_qt/main_window.cpp b/src/panda_qt/main_window.cpp index be3509da..672fc2a1 100644 --- a/src/panda_qt/main_window.cpp +++ b/src/panda_qt/main_window.cpp @@ -2,6 +2,7 @@ #include #include +#include MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent), screen(this) { setWindowTitle("Alber"); @@ -23,8 +24,10 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent) auto aboutMenu = menuBar->addMenu(tr("About")); // Create and bind actions for them - auto pandaAction = fileMenu->addAction(tr("Load game")); - connect(pandaAction, &QAction::triggered, this, &MainWindow::selectROM); + auto loadGameAction = fileMenu->addAction(tr("Load game")); + auto loadLuaAction = fileMenu->addAction(tr("Load Lua script")); + connect(loadGameAction, &QAction::triggered, this, &MainWindow::selectROM); + connect(loadLuaAction, &QAction::triggered, this, &MainWindow::selectLuaFile); auto pauseAction = emulationMenu->addAction(tr("Pause")); auto resumeAction = emulationMenu->addAction(tr("Resume")); @@ -124,6 +127,34 @@ void MainWindow::selectROM() { } } +void MainWindow::selectLuaFile() { + auto path = QFileDialog::getOpenFileName(this, tr("Select Lua script to load"), "", tr("Lua scripts (*.lua *.txt)")); + + if (!path.isEmpty()) { + std::ifstream file(std::filesystem::path(path.toStdU16String()), std::ios::in); + + if (file.fail()) { + printf("Failed to load selected lua file\n"); + return; + } + + // Read whole file into an std::string string + // Get file size, preallocate std::string to avoid furthermemory allocations + std::string code; + file.seekg(0, std::ios::end); + code.resize(file.tellg()); + + // Rewind and read the whole file + file.seekg(0, std::ios::beg); + file.read(&code[0], code.size()); + file.close(); + + loadLuaScript(code); + // Copy the Lua script to the Lua editor + luaEditor->setText(code); + } +} + // Cleanup when the main window closes MainWindow::~MainWindow() { appRunning = false; // Set our running atomic to false in order to make the emulator thread stop, and join it diff --git a/src/panda_qt/text_editor.cpp b/src/panda_qt/text_editor.cpp index bef4ec9f..c189c2ce 100644 --- a/src/panda_qt/text_editor.cpp +++ b/src/panda_qt/text_editor.cpp @@ -15,15 +15,18 @@ TextEditorWindow::TextEditorWindow(QWidget* parent, const std::string& filename, ZepRegressExCommand::Register(zepWidget.GetEditor()); ZepReplExCommand::Register(zepWidget.GetEditor(), &replProvider); + // Default to standard mode instead of vim mode, initialize text box zepWidget.GetEditor().SetGlobalMode(Zep::ZepMode_Standard::StaticName()); zepWidget.GetEditor().InitWithText(filename, initialText); + // Layout for widgets QVBoxLayout* mainLayout = new QVBoxLayout(); setLayout(mainLayout); QPushButton* button = new QPushButton(tr("Load script"), this); button->setFixedSize(100, 20); + // When the Load Script button is pressed, send the current text to the MainWindow, which will upload it to the emulator's lua object connect(button, &QPushButton::pressed, this, [this]() { if (parentWidget()) { auto buffer = zepWidget.GetEditor().GetMRUBuffer(); From fa0ee8a080d49aa08de1c33ba5c8526f1b8da73f Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Sat, 16 Dec 2023 17:43:45 +0200 Subject: [PATCH 073/162] Update zep --- third_party/zep | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/third_party/zep b/third_party/zep index 0a47740d..75406e1c 160000 --- a/third_party/zep +++ b/third_party/zep @@ -1 +1 @@ -Subproject commit 0a47740db9962b3d022a3c098d57784d223d203f +Subproject commit 75406e1c854b9fa6ede697d6165664e0e11b09ff From 18f1ea54532581205a752827d31528dbb6c27386 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Sat, 16 Dec 2023 22:11:14 +0200 Subject: [PATCH 074/162] [APT] Gracefully handle SetApplicationCpuTimeLimit with invalid inputs --- src/core/services/apt.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/core/services/apt.cpp b/src/core/services/apt.cpp index 754baa13..9d02bf94 100644 --- a/src/core/services/apt.cpp +++ b/src/core/services/apt.cpp @@ -2,6 +2,7 @@ #include "ipc.hpp" #include "kernel.hpp" +#include #include namespace APTCommands { @@ -311,12 +312,14 @@ void APTService::setApplicationCpuTimeLimit(u32 messagePointer) { log("APT::SetApplicationCpuTimeLimit (percentage = %d%%)\n", percentage); if (percentage < 5 || percentage > 89 || fixed != 1) { - Helpers::panic("Invalid parameters passed to APT::SetApplicationCpuTimeLimit"); - } else { - mem.write32(messagePointer, IPC::responseHeader(0x4F, 1, 0)); - mem.write32(messagePointer + 4, Result::Success); - cpuTimeLimit = percentage; + Helpers::warn("Invalid parameter passed to APT::SetApplicationCpuTimeLimit: (percentage, fixed) = (%d, %d)\n", percentage, fixed); + // TODO: Does the clamp operation happen? Verify with getApplicationCpuTimeLimit on hardware + percentage = std::clamp(percentage, 5, 89); } + + mem.write32(messagePointer, IPC::responseHeader(0x4F, 1, 0)); + mem.write32(messagePointer + 4, Result::Success); + cpuTimeLimit = percentage; } void APTService::getApplicationCpuTimeLimit(u32 messagePointer) { From 3298bd14dbc78055afef2157c65ce4bd8ee5b7bc Mon Sep 17 00:00:00 2001 From: GabrielBRDeveloper Date: Sat, 16 Dec 2023 23:22:11 -0400 Subject: [PATCH 075/162] Pandroid UI --- src/pandroid/app/build.gradle.kts | 2 + src/pandroid/app/src/main/AndroidManifest.xml | 9 +- .../panda3ds/pandroid/app/GameActivity.java | 35 ++++- .../panda3ds/pandroid/app/MainActivity.java | 58 +++++--- .../pandroid/app/PandroidApplication.java | 4 + .../pandroid/app/PreferenceActivity.java | 54 ++++++++ .../app/base/BasePreferenceFragment.java | 19 +++ .../pandroid/app/game/AlberInputListener.java | 54 ++++++++ .../pandroid/app/main/GamesFragment.java | 75 +++++++++++ .../pandroid/app/main/SearchFragment.java | 67 ++++++++++ .../pandroid/app/main/SettingsFragment.java | 18 +++ .../app/preferences/InputMapActivity.java | 76 +++++++++++ .../app/preferences/InputMapPreferences.java | 72 ++++++++++ .../pandroid/data/game/GameMetadata.java | 44 ++++++ .../pandroid/data/game/GameRegion.java | 12 ++ .../panda3ds/pandroid/input/InputEvent.java | 23 ++++ .../panda3ds/pandroid/input/InputHandler.java | 116 ++++++++++++++++ .../com/panda3ds/pandroid/input/InputMap.java | 42 ++++++ .../com/panda3ds/pandroid/input/KeyName.java | 38 ++++++ .../com/panda3ds/pandroid/lang/Function.java | 5 + .../panda3ds/pandroid/utils/Constants.java | 3 + .../panda3ds/pandroid/utils/FileUtils.java | 63 +++++++++ .../panda3ds/pandroid/utils/GameUtils.java | 64 +++++++++ .../panda3ds/pandroid/utils/PathUtils.java | 5 +- .../panda3ds/pandroid/utils/SearchAgent.java | 91 +++++++++++++ .../pandroid/view/SimpleTextWatcher.java | 19 +++ .../pandroid/view/gamesgrid/GameAdapter.java | 42 ++++++ .../pandroid/view/gamesgrid/GameIconView.java | 42 ++++++ .../view/gamesgrid/GamesGridView.java | 58 ++++++++ .../pandroid/view/gamesgrid/ItemHolder.java | 28 ++++ .../app/src/main/res/drawable/ic_key_a.xml | 5 + .../app/src/main/res/drawable/ic_search.xml | 5 + .../app/src/main/res/drawable/ic_settings.xml | 5 + .../src/main/res/drawable/ic_videogame.xml | 5 + .../res/drawable/search_bar_background.xml | 9 ++ .../res/drawable/simple_card_background.xml | 9 ++ .../main/res/drawable/simple_card_button.xml | 12 -- .../res/drawable/simple_card_button_left.xml | 12 -- .../res/drawable/simple_card_button_right.xml | 12 -- .../app/src/main/res/drawable/temp_thumb.jpg | Bin 0 -> 51816 bytes .../main/res/layout-land/activity_main.xml | 29 ++++ .../main/res/layout/activity_input_map.xml | 23 ++++ .../app/src/main/res/layout/activity_main.xml | 32 +++-- .../main/res/layout/activity_preference.xml | 19 +++ .../src/main/res/layout/fragment_games.xml | 25 ++++ .../src/main/res/layout/fragment_search.xml | 49 +++++++ .../app/src/main/res/layout/holder_game.xml | 41 ++++++ .../res/layout/preference_simple_about.xml | 29 ++++ .../main/res/layout/preference_start_item.xml | 54 ++++++++ .../res/menu/main_activity_navigation.xml | 24 ++++ .../app/src/main/res/values-night/themes.xml | 7 - .../src/main/res/values-pt-rBR/strings.xml | 16 +++ .../app/src/main/res/values/strings.xml | 13 ++ .../app/src/main/res/values/styleable.xml | 3 + .../main/res/xml/input_map_preferences.xml | 125 ++++++++++++++++++ .../src/main/res/xml/start_preferences.xml | 17 +++ 56 files changed, 1741 insertions(+), 77 deletions(-) create mode 100644 src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/PreferenceActivity.java create mode 100644 src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/base/BasePreferenceFragment.java create mode 100644 src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/game/AlberInputListener.java create mode 100644 src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/main/GamesFragment.java create mode 100644 src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/main/SearchFragment.java create mode 100644 src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/main/SettingsFragment.java create mode 100644 src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/InputMapActivity.java create mode 100644 src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/InputMapPreferences.java create mode 100644 src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/game/GameMetadata.java create mode 100644 src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/game/GameRegion.java create mode 100644 src/pandroid/app/src/main/java/com/panda3ds/pandroid/input/InputEvent.java create mode 100644 src/pandroid/app/src/main/java/com/panda3ds/pandroid/input/InputHandler.java create mode 100644 src/pandroid/app/src/main/java/com/panda3ds/pandroid/input/InputMap.java create mode 100644 src/pandroid/app/src/main/java/com/panda3ds/pandroid/input/KeyName.java create mode 100644 src/pandroid/app/src/main/java/com/panda3ds/pandroid/lang/Function.java create mode 100644 src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/FileUtils.java create mode 100644 src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/GameUtils.java create mode 100644 src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/SearchAgent.java create mode 100644 src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/SimpleTextWatcher.java create mode 100644 src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/gamesgrid/GameAdapter.java create mode 100644 src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/gamesgrid/GameIconView.java create mode 100644 src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/gamesgrid/GamesGridView.java create mode 100644 src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/gamesgrid/ItemHolder.java create mode 100644 src/pandroid/app/src/main/res/drawable/ic_key_a.xml create mode 100644 src/pandroid/app/src/main/res/drawable/ic_search.xml create mode 100644 src/pandroid/app/src/main/res/drawable/ic_settings.xml create mode 100644 src/pandroid/app/src/main/res/drawable/ic_videogame.xml create mode 100644 src/pandroid/app/src/main/res/drawable/search_bar_background.xml create mode 100644 src/pandroid/app/src/main/res/drawable/simple_card_background.xml delete mode 100644 src/pandroid/app/src/main/res/drawable/simple_card_button.xml delete mode 100644 src/pandroid/app/src/main/res/drawable/simple_card_button_left.xml delete mode 100644 src/pandroid/app/src/main/res/drawable/simple_card_button_right.xml create mode 100644 src/pandroid/app/src/main/res/drawable/temp_thumb.jpg create mode 100644 src/pandroid/app/src/main/res/layout-land/activity_main.xml create mode 100644 src/pandroid/app/src/main/res/layout/activity_input_map.xml create mode 100644 src/pandroid/app/src/main/res/layout/activity_preference.xml create mode 100644 src/pandroid/app/src/main/res/layout/fragment_games.xml create mode 100644 src/pandroid/app/src/main/res/layout/fragment_search.xml create mode 100644 src/pandroid/app/src/main/res/layout/holder_game.xml create mode 100644 src/pandroid/app/src/main/res/layout/preference_simple_about.xml create mode 100644 src/pandroid/app/src/main/res/layout/preference_start_item.xml create mode 100644 src/pandroid/app/src/main/res/menu/main_activity_navigation.xml delete mode 100644 src/pandroid/app/src/main/res/values-night/themes.xml create mode 100644 src/pandroid/app/src/main/res/values-pt-rBR/strings.xml create mode 100644 src/pandroid/app/src/main/res/xml/input_map_preferences.xml create mode 100644 src/pandroid/app/src/main/res/xml/start_preferences.xml diff --git a/src/pandroid/app/build.gradle.kts b/src/pandroid/app/build.gradle.kts index 276eb552..f1feaf0d 100644 --- a/src/pandroid/app/build.gradle.kts +++ b/src/pandroid/app/build.gradle.kts @@ -38,5 +38,7 @@ android { dependencies { implementation("androidx.appcompat:appcompat:1.6.1") implementation("com.google.android.material:material:1.8.0") + implementation("androidx.preference:preference:1.2.1") implementation("androidx.constraintlayout:constraintlayout:2.1.4") + implementation("com.google.code.gson:gson:2.10.1") } \ No newline at end of file diff --git a/src/pandroid/app/src/main/AndroidManifest.xml b/src/pandroid/app/src/main/AndroidManifest.xml index 15bf6270..8caf9bb0 100644 --- a/src/pandroid/app/src/main/AndroidManifest.xml +++ b/src/pandroid/app/src/main/AndroidManifest.xml @@ -24,7 +24,8 @@ tools:targetApi="31"> + android:exported="true" + android:configChanges="orientation"> @@ -34,5 +35,11 @@ android:name=".app.GameActivity" android:configChanges="screenSize|screenLayout|orientation|density|uiMode"> + + + 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 index 2da73b97..286a7ed3 100644 --- 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 @@ -2,6 +2,8 @@ package com.panda3ds.pandroid.app; import android.content.Intent; import android.os.Bundle; +import android.view.KeyEvent; +import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; @@ -12,11 +14,17 @@ import android.widget.Toast; import androidx.annotation.Nullable; import com.panda3ds.pandroid.R; +import com.panda3ds.pandroid.app.game.AlberInputListener; +import com.panda3ds.pandroid.input.InputHandler; +import com.panda3ds.pandroid.input.InputMap; import com.panda3ds.pandroid.utils.Constants; import com.panda3ds.pandroid.view.PandaGlSurfaceView; import com.panda3ds.pandroid.view.PandaLayoutController; public class GameActivity extends BaseActivity { + + private final AlberInputListener inputListener = new AlberInputListener(); + @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -33,7 +41,7 @@ public class GameActivity extends BaseActivity { 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)); + .addView(pandaSurface, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); PandaLayoutController controllerLayout = findViewById(R.id.controller_layout); controllerLayout.initialize(); @@ -46,5 +54,30 @@ public class GameActivity extends BaseActivity { super.onResume(); getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + InputHandler.reset(); + InputHandler.setMotionDeadZone(InputMap.getDeadZone()); + InputHandler.setEventListener(inputListener); + } + + @Override + protected void onPause() { + super.onPause(); + InputHandler.reset(); + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + if (InputHandler.processKeyEvent(event)) + return true; + + return super.dispatchKeyEvent(event); + } + + @Override + public boolean dispatchGenericMotionEvent(MotionEvent ev) { + if (InputHandler.processMotionEvent(ev)) + return true; + + return super.dispatchGenericMotionEvent(ev); } } 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 index f4fc27bf..29070a88 100644 --- 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 @@ -8,17 +8,27 @@ 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 androidx.core.app.ActivityCompat; -import com.panda3ds.pandroid.R; -import com.panda3ds.pandroid.utils.Constants; -import com.panda3ds.pandroid.utils.PathUtils; +import android.view.MenuItem; -public class MainActivity extends BaseActivity { +import androidx.annotation.NonNull; +import androidx.core.app.ActivityCompat; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; + +import com.google.android.material.navigation.NavigationBarView; +import com.panda3ds.pandroid.R; +import com.panda3ds.pandroid.app.main.GamesFragment; +import com.panda3ds.pandroid.app.main.SearchFragment; +import com.panda3ds.pandroid.app.main.SettingsFragment; + +public class MainActivity extends BaseActivity implements NavigationBarView.OnItemSelectedListener { private static final int PICK_ROM = 2; private static final int PERMISSION_REQUEST_CODE = 3; + private final GamesFragment gamesFragment = new GamesFragment(); + private final SearchFragment searchFragment = new SearchFragment(); + private final SettingsFragment settingsFragment = new SettingsFragment(); + private void openFile() { Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); intent.addCategory(Intent.CATEGORY_OPENABLE); @@ -35,23 +45,35 @@ public class MainActivity extends BaseActivity { startActivity(intent); } } else { - ActivityCompat.requestPermissions(this, new String[] {READ_EXTERNAL_STORAGE}, PERMISSION_REQUEST_CODE); - ActivityCompat.requestPermissions(this, new String[] {WRITE_EXTERNAL_STORAGE}, PERMISSION_REQUEST_CODE); + ActivityCompat.requestPermissions(this, new String[]{READ_EXTERNAL_STORAGE}, PERMISSION_REQUEST_CODE); + ActivityCompat.requestPermissions(this, new String[]{WRITE_EXTERNAL_STORAGE}, PERMISSION_REQUEST_CODE); } setContentView(R.layout.activity_main); - findViewById(R.id.load_rom).setOnClickListener(v -> { openFile(); }); + + NavigationBarView bar = findViewById(R.id.navigation); + bar.setOnItemSelectedListener(this); + bar.postDelayed(() -> bar.setSelectedItemId(bar.getSelectedItemId()), 5); } @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - if (requestCode == PICK_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.ACTIVITY_PARAMETER_PATH, path)); - } - super.onActivityResult(requestCode, resultCode, data); + public boolean onNavigationItemSelected(@NonNull MenuItem item) { + int id = item.getItemId(); + FragmentManager manager = getSupportFragmentManager(); + Fragment fragment; + if (id == R.id.games) { + fragment = gamesFragment; + } else if (id == R.id.search) { + fragment = searchFragment; + } else if (id == R.id.settings) { + fragment = settingsFragment; + } else { + return false; } + + manager.beginTransaction() + .replace(R.id.fragment_container, fragment) + .commitNow(); + return true; } } \ No newline at end of file diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/PandroidApplication.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/PandroidApplication.java index 0e284db6..246decec 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/PandroidApplication.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/PandroidApplication.java @@ -4,6 +4,8 @@ import android.app.Application; import android.content.Context; import com.panda3ds.pandroid.data.config.GlobalConfig; +import com.panda3ds.pandroid.input.InputMap; +import com.panda3ds.pandroid.utils.GameUtils; public class PandroidApplication extends Application { private static Context appContext; @@ -13,6 +15,8 @@ public class PandroidApplication extends Application { super.onCreate(); appContext = this; GlobalConfig.initialize(); + GameUtils.initialize(); + InputMap.initialize(); } public static Context getAppContext() { diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/PreferenceActivity.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/PreferenceActivity.java new file mode 100644 index 00000000..3b8ebb2e --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/PreferenceActivity.java @@ -0,0 +1,54 @@ +package com.panda3ds.pandroid.app; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.view.MenuItem; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +import com.panda3ds.pandroid.R; +import com.panda3ds.pandroid.utils.Constants; + +public class PreferenceActivity extends BaseActivity { + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Intent intent = getIntent(); + + setContentView(R.layout.activity_preference); + setSupportActionBar(findViewById(R.id.toolbar)); + + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + if (!intent.hasExtra(Constants.ACTIVITY_PARAMETER_FRAGMENT)) { + finish(); + return; + } + + try { + Class clazz = getClassLoader().loadClass(intent.getStringExtra(Constants.ACTIVITY_PARAMETER_FRAGMENT)); + getSupportFragmentManager() + .beginTransaction() + .replace(R.id.fragment_container, (Fragment) clazz.newInstance()) + .commitNow(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static void launch(Context context, Class clazz) { + context.startActivity(new Intent(context, PreferenceActivity.class) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + .putExtra(Constants.ACTIVITY_PARAMETER_FRAGMENT, clazz.getName())); + } + + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + if (item.getItemId() == android.R.id.home) + finish(); + return super.onOptionsItemSelected(item); + } +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/base/BasePreferenceFragment.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/base/BasePreferenceFragment.java new file mode 100644 index 00000000..f459aa6d --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/base/BasePreferenceFragment.java @@ -0,0 +1,19 @@ +package com.panda3ds.pandroid.app.base; + +import android.annotation.SuppressLint; + +import androidx.preference.Preference; +import androidx.preference.PreferenceFragmentCompat; + +import com.panda3ds.pandroid.lang.Function; + +public abstract class BasePreferenceFragment extends PreferenceFragmentCompat { + @SuppressLint("RestrictedApi") + protected void setItemClick(String key, Function listener){ + findPreference(key).setOnPreferenceClickListener(preference -> { + listener.run(preference); + getPreferenceScreen().performClick(); + return false; + }); + } +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/game/AlberInputListener.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/game/AlberInputListener.java new file mode 100644 index 00000000..c500d970 --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/game/AlberInputListener.java @@ -0,0 +1,54 @@ +package com.panda3ds.pandroid.app.game; + +import com.panda3ds.pandroid.AlberDriver; +import com.panda3ds.pandroid.input.InputEvent; +import com.panda3ds.pandroid.input.InputMap; +import com.panda3ds.pandroid.input.KeyName; +import com.panda3ds.pandroid.lang.Function; +import com.panda3ds.pandroid.math.Vector2; + +public class AlberInputListener implements Function { + + private final Vector2 axis = new Vector2(0.0f, 0.0f); + + @Override + public void run(InputEvent event) { + KeyName key = InputMap.relative(event.getName()); + + if (key == KeyName.NULL) + return; + + boolean axisChanged = false; + + switch (key) { + case AXIS_UP: + axis.y = event.getValue(); + axisChanged = true; + break; + case AXIS_DOWN: + axis.y = -event.getValue(); + axisChanged = true; + break; + case AXIS_LEFT: + axis.x = -event.getValue(); + axisChanged = true; + break; + case AXIS_RIGHT: + axis.x = event.getValue(); + axisChanged = true; + break; + default: + if (event.isDown()) { + AlberDriver.KeyDown(key.getKeyId()); + } else { + AlberDriver.KeyUp(key.getKeyId()); + } + break; + } + + if (axisChanged) { + AlberDriver.SetCirclepadAxis(Math.round(axis.x * 0x9C), Math.round(axis.y * 0x9C)); + } + } + +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/main/GamesFragment.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/main/GamesFragment.java new file mode 100644 index 00000000..ef49f6f1 --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/main/GamesFragment.java @@ -0,0 +1,75 @@ +package com.panda3ds.pandroid.app.main; + +import android.net.Uri; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.activity.result.ActivityResultCallback; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +import com.panda3ds.pandroid.R; +import com.panda3ds.pandroid.data.game.GameMetadata; +import com.panda3ds.pandroid.utils.FileUtils; +import com.panda3ds.pandroid.utils.GameUtils; +import com.panda3ds.pandroid.view.gamesgrid.GamesGridView; + +public class GamesFragment extends Fragment implements ActivityResultCallback { + + private final ActivityResultContracts.OpenDocument openRomContract = new ActivityResultContracts.OpenDocument(); + private ActivityResultLauncher pickFileRequest; + private GamesGridView gameListView; + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_games, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + gameListView = view.findViewById(R.id.games); + + view.findViewById(R.id.add_rom).setOnClickListener((v) -> pickFileRequest.launch(new String[]{"*/*"})); + } + + @Override + public void onResume() { + super.onResume(); + gameListView.setGameList(GameUtils.getGames()); + } + + @Override + public void onActivityResult(Uri result) { + if (result != null) { + String uri = result.toString(); + if (GameUtils.findByRomPath(uri) == null) { + FileUtils.makeUriPermanent(uri, FileUtils.MODE_READ); + GameMetadata game = new GameMetadata(FileUtils.getName(uri).split("\\.")[0], uri, "Unknown"); + GameUtils.addGame(game); + GameUtils.launch(requireActivity(), game); + } + } + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + pickFileRequest = registerForActivityResult(openRomContract, this); + } + + @Override + public void onDestroy() { + if (pickFileRequest != null) { + pickFileRequest.unregister(); + pickFileRequest = null; + } + super.onDestroy(); + } +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/main/SearchFragment.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/main/SearchFragment.java new file mode 100644 index 00000000..213d55fc --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/main/SearchFragment.java @@ -0,0 +1,67 @@ +package com.panda3ds.pandroid.app.main; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.AppCompatEditText; +import androidx.fragment.app.Fragment; + +import com.panda3ds.pandroid.R; +import com.panda3ds.pandroid.data.game.GameMetadata; +import com.panda3ds.pandroid.utils.GameUtils; +import com.panda3ds.pandroid.utils.SearchAgent; +import com.panda3ds.pandroid.view.SimpleTextWatcher; +import com.panda3ds.pandroid.view.gamesgrid.GamesGridView; + +import java.util.ArrayList; +import java.util.List; + +public class SearchFragment extends Fragment { + + private final SearchAgent searchAgent = new SearchAgent(); + private GamesGridView gamesListView; + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_search, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + gamesListView = view.findViewById(R.id.games); + + ((AppCompatEditText) view.findViewById(R.id.search_bar)) + .addTextChangedListener((SimpleTextWatcher) this::search); + } + + @Override + public void onResume() { + super.onResume(); + searchAgent.clearBuffer(); + for (GameMetadata game : GameUtils.getGames()) { + searchAgent.addToBuffer(game.getId(), game.getTitle(), game.getPublisher()); + } + search(""); + } + + private void search(String query) { + List resultIds = searchAgent.search(query); + ArrayList games = new ArrayList<>(GameUtils.getGames()); + Object[] resultObj = games.stream() + .filter(gameMetadata -> resultIds.contains(gameMetadata.getId())) + .toArray(); + + games.clear(); + for (Object res : resultObj) + games.add((GameMetadata) res); + + gamesListView.setGameList(games); + } +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/main/SettingsFragment.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/main/SettingsFragment.java new file mode 100644 index 00000000..22f888fe --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/main/SettingsFragment.java @@ -0,0 +1,18 @@ +package com.panda3ds.pandroid.app.main; + +import android.os.Bundle; + +import androidx.annotation.Nullable; + +import com.panda3ds.pandroid.R; +import com.panda3ds.pandroid.app.PreferenceActivity; +import com.panda3ds.pandroid.app.base.BasePreferenceFragment; +import com.panda3ds.pandroid.app.preferences.InputMapPreferences; + +public class SettingsFragment extends BasePreferenceFragment { + @Override + public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) { + setPreferencesFromResource(R.xml.start_preferences, rootKey); + setItemClick("inputMap", (item) -> PreferenceActivity.launch(requireContext(), InputMapPreferences.class)); + } +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/InputMapActivity.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/InputMapActivity.java new file mode 100644 index 00000000..b7a2cbb4 --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/InputMapActivity.java @@ -0,0 +1,76 @@ +package com.panda3ds.pandroid.app.preferences; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.widget.Toast; + +import androidx.activity.result.contract.ActivityResultContract; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.panda3ds.pandroid.R; +import com.panda3ds.pandroid.app.BaseActivity; +import com.panda3ds.pandroid.input.InputEvent; +import com.panda3ds.pandroid.input.InputHandler; + +import java.util.Objects; + +public class InputMapActivity extends BaseActivity { + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_input_map); + } + + @Override + protected void onResume() { + super.onResume(); + InputHandler.reset(); + InputHandler.setMotionDeadZone(0.8F); + InputHandler.setEventListener(this::onInputEvent); + } + + @Override + protected void onPause() { + super.onPause(); + InputHandler.reset(); + } + + @Override + public boolean dispatchGenericMotionEvent(MotionEvent ev) { + return InputHandler.processMotionEvent(ev); + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + return InputHandler.processKeyEvent(event); + } + + private void onInputEvent(InputEvent event) { + if (Objects.equals(event.getName(), "KEYCODE_BACK")) { + onBackPressed(); + return; + } + setResult(RESULT_OK, new Intent(event.getName())); + Toast.makeText(this, event.getName(), Toast.LENGTH_SHORT).show(); + finish(); + } + + + public static final class Contract extends ActivityResultContract { + + @NonNull + @Override + public Intent createIntent(@NonNull Context context, String s) { + return new Intent(context, InputMapActivity.class); + } + + @Override + public String parseResult(int i, @Nullable Intent intent) { + return i == RESULT_OK ? intent.getAction() : null; + } + } +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/InputMapPreferences.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/InputMapPreferences.java new file mode 100644 index 00000000..007920bd --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/InputMapPreferences.java @@ -0,0 +1,72 @@ +package com.panda3ds.pandroid.app.preferences; + +import android.content.Context; +import android.os.Bundle; + +import androidx.activity.result.ActivityResultCallback; +import androidx.activity.result.ActivityResultLauncher; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.preference.Preference; + +import com.panda3ds.pandroid.R; +import com.panda3ds.pandroid.app.base.BasePreferenceFragment; +import com.panda3ds.pandroid.input.InputMap; +import com.panda3ds.pandroid.input.KeyName; + +public class InputMapPreferences extends BasePreferenceFragment implements ActivityResultCallback { + + private ActivityResultLauncher requestKey; + private String currentKey; + + @Override + public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) { + setPreferencesFromResource(R.xml.input_map_preferences, rootKey); + for (KeyName key : KeyName.values()) { + if (key == KeyName.NULL) return; + setItemClick(key.name(), this::onItemPressed); + } + refreshList(); + } + + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + requestKey = registerForActivityResult(new InputMapActivity.Contract(), this); + } + + @Override + public void onDetach() { + super.onDetach(); + if (requestKey != null) { + requestKey.unregister(); + requestKey = null; + } + } + + private void onItemPressed(Preference pref) { + currentKey = pref.getKey(); + requestKey.launch(null); + } + + @Override + public void onResume() { + super.onResume(); + refreshList(); + } + + private void refreshList() { + for (KeyName key : KeyName.values()) { + if (key == KeyName.NULL) continue; + findPreference(key.name()).setSummary(InputMap.relative(key)); + } + } + + @Override + public void onActivityResult(String result) { + if (result != null) { + InputMap.set(KeyName.valueOf(currentKey), result); + refreshList(); + } + } +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/game/GameMetadata.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/game/GameMetadata.java new file mode 100644 index 00000000..46407302 --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/game/GameMetadata.java @@ -0,0 +1,44 @@ +package com.panda3ds.pandroid.data.game; + +import java.util.UUID; + +public class GameMetadata { + + private final String id; + private final String romPath; + private final String title; + private final int[] icon = new int[48 * 48]; + private final String publisher; + private final GameRegion[] regions = new GameRegion[]{GameRegion.None}; + + public GameMetadata(String title, String romPath, String publisher) { + this.id = UUID.randomUUID().toString(); + this.title = title; + this.publisher = publisher; + this.romPath = romPath; + } + + public String getRomPath() { + return romPath; + } + + public String getId() { + return id; + } + + public String getTitle() { + return title; + } + + public String getPublisher() { + return publisher; + } + + public int[] getIcon() { + return icon; + } + + public GameRegion[] getRegions() { + return regions; + } +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/game/GameRegion.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/game/GameRegion.java new file mode 100644 index 00000000..9b99b095 --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/game/GameRegion.java @@ -0,0 +1,12 @@ +package com.panda3ds.pandroid.data.game; + +public enum GameRegion { + NorthAmerican, + Japan, + Europe, + Australia, + China, + Korean, + Taiwan, + None +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/input/InputEvent.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/input/InputEvent.java new file mode 100644 index 00000000..7869e00a --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/input/InputEvent.java @@ -0,0 +1,23 @@ +package com.panda3ds.pandroid.input; + +public class InputEvent { + private final String name; + private final float value; + + public InputEvent(String name, float value) { + this.name = name; + this.value = Math.max(0.0f, Math.min(1.0f, value)); + } + + public boolean isDown() { + return value > 0.0f; + } + + public String getName() { + return name; + } + + public float getValue() { + return value; + } +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/input/InputHandler.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/input/InputHandler.java new file mode 100644 index 00000000..08142d06 --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/input/InputHandler.java @@ -0,0 +1,116 @@ +package com.panda3ds.pandroid.input; + +import android.view.InputDevice; +import android.view.KeyEvent; +import android.view.MotionEvent; + +import com.panda3ds.pandroid.lang.Function; + +import java.util.HashMap; + +public class InputHandler { + private static Function eventListener; + private static float motionDeadZone = 0.0f; + + private static final int[] gamepadSources = { + InputDevice.SOURCE_GAMEPAD, + InputDevice.SOURCE_JOYSTICK + }; + + private static final int[] validSources = { + InputDevice.SOURCE_GAMEPAD, + InputDevice.SOURCE_JOYSTICK, + InputDevice.SOURCE_DPAD, + InputDevice.SOURCE_KEYBOARD + }; + + private static final HashMap motionDownEvents = new HashMap<>(); + + private static boolean containsSource(int[] sources, int sourceMasked) { + for (int source : sources) { + if ((sourceMasked & source) == source) + return true; + } + return false; + } + + private static boolean isGamepadSource(int sourceMask) { + return containsSource(gamepadSources, sourceMask); + } + + private static boolean isSourceValid(int sourceMasked) { + return containsSource(validSources, sourceMasked); + } + + public static void setEventListener(Function eventListener) { + InputHandler.eventListener = eventListener; + } + + private static void handleEvent(InputEvent event) { + if (eventListener != null) { + eventListener.run(event); + } + } + + public static void setMotionDeadZone(float motionDeadZone) { + InputHandler.motionDeadZone = motionDeadZone; + } + + public static boolean processMotionEvent(MotionEvent event) { + if (!isSourceValid(event.getSource())) + return false; + + if (isGamepadSource(event.getSource())) { + for (InputDevice.MotionRange range : event.getDevice().getMotionRanges()) { + float axisValue = event.getAxisValue(range.getAxis()); + float value = Math.abs(axisValue); + String name = (MotionEvent.axisToString(range.getAxis()) + (axisValue >= 0 ? "+" : "-")).toUpperCase(); + String reverseName = (MotionEvent.axisToString(range.getAxis()) + (axisValue >= 0 ? "-" : "+")).toUpperCase(); + + if (motionDownEvents.containsKey(reverseName)) { + motionDownEvents.remove(reverseName); + handleEvent(new InputEvent(reverseName.toUpperCase(), 0.0f)); + } + + if (value > motionDeadZone) { + motionDownEvents.put(name, value); + handleEvent(new InputEvent(name.toUpperCase(), (value - motionDeadZone) / (1.0f - motionDeadZone))); + } else if (motionDownEvents.containsKey(name)) { + motionDownEvents.remove(name); + handleEvent(new InputEvent(name.toUpperCase(), 0.0f)); + } + + } + } + + return true; + } + + public static boolean processKeyEvent(KeyEvent event) { + if (!isSourceValid(event.getSource())) + return false; + + if (isGamepadSource(event.getSource())) { + // Dpad return motion event + key event, this remove the key event + switch (event.getKeyCode()) { + case KeyEvent.KEYCODE_DPAD_UP: + case KeyEvent.KEYCODE_DPAD_UP_LEFT: + case KeyEvent.KEYCODE_DPAD_UP_RIGHT: + case KeyEvent.KEYCODE_DPAD_DOWN: + case KeyEvent.KEYCODE_DPAD_DOWN_LEFT: + case KeyEvent.KEYCODE_DPAD_DOWN_RIGHT: + case KeyEvent.KEYCODE_DPAD_RIGHT: + case KeyEvent.KEYCODE_DPAD_LEFT: + return true; + } + } + handleEvent(new InputEvent(KeyEvent.keyCodeToString(event.getKeyCode()), event.getAction() == KeyEvent.ACTION_UP ? 0.0f : 1.0f)); + return true; + } + + public static void reset() { + eventListener = null; + motionDeadZone = 0.0f; + motionDownEvents.clear(); + } +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/input/InputMap.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/input/InputMap.java new file mode 100644 index 00000000..6e61345c --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/input/InputMap.java @@ -0,0 +1,42 @@ +package com.panda3ds.pandroid.input; + +import android.content.Context; +import android.content.SharedPreferences; + +import com.panda3ds.pandroid.app.PandroidApplication; +import com.panda3ds.pandroid.utils.Constants; + +public class InputMap { + + private static SharedPreferences data; + private static final String KEY_DEAD_ZONE = "deadZone"; + + public static void initialize() { + data = PandroidApplication.getAppContext().getSharedPreferences(Constants.PREF_INPUT_MAP, Context.MODE_PRIVATE); + } + + public static float getDeadZone() { + return data.getFloat(KEY_DEAD_ZONE, 0.2f); + } + + public static void set(KeyName key, String name) { + data.edit().putString(key.name(), name).apply(); + } + + public static String relative(KeyName key) { + return data.getString(key.name(), "-"); + } + + public static KeyName relative(String name) { + for (KeyName key : KeyName.values()) { + if (relative(key).equalsIgnoreCase(name)) + return key; + } + return KeyName.NULL; + } + + public static void setDeadZone(float value) { + data.edit().putFloat(KEY_DEAD_ZONE, Math.max(0, Math.min(1.0F, value))).apply(); + } + +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/input/KeyName.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/input/KeyName.java new file mode 100644 index 00000000..1253529f --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/input/KeyName.java @@ -0,0 +1,38 @@ +package com.panda3ds.pandroid.input; + +import com.panda3ds.pandroid.utils.Constants; + +public enum KeyName { + A(Constants.INPUT_KEY_A), + B(Constants.INPUT_KEY_B), + X(Constants.INPUT_KEY_X), + Y(Constants.INPUT_KEY_Y), + UP(Constants.INPUT_KEY_UP), + DOWN(Constants.INPUT_KEY_DOWN), + LEFT(Constants.INPUT_KEY_LEFT), + RIGHT(Constants.INPUT_KEY_RIGHT), + AXIS_LEFT, + AXIS_RIGHT, + AXIS_UP, + AXIS_DOWN, + START(Constants.INPUT_KEY_START), + SELECT(Constants.INPUT_KEY_SELECT), + L(Constants.INPUT_KEY_L), + R(Constants.INPUT_KEY_R), + NULL; + + private final int keyId; + + KeyName() { + this(-1); + } + + KeyName(int keyId) { + this.keyId = keyId; + } + + public int getKeyId() { + return keyId; + } + +} \ No newline at end of file diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/lang/Function.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/lang/Function.java new file mode 100644 index 00000000..25a15875 --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/lang/Function.java @@ -0,0 +1,5 @@ +package com.panda3ds.pandroid.lang; + +public interface Function { + void run(T arg); +} 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 index 1aac0a4d..7adf2e47 100644 --- 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 @@ -19,7 +19,10 @@ public class Constants { public static final int N3DS_HALF_HEIGHT = N3DS_FULL_HEIGHT / 2; public static final String ACTIVITY_PARAMETER_PATH = "path"; + public static final String ACTIVITY_PARAMETER_FRAGMENT = "fragment"; public static final String LOG_TAG = "pandroid"; public static final String PREF_GLOBAL_CONFIG = "app.GlobalConfig"; + public static final String PREF_GAME_UTILS = "app.GameUtils"; + public static final String PREF_INPUT_MAP = "app.InputMap"; } diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/FileUtils.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/FileUtils.java new file mode 100644 index 00000000..4ebd7241 --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/FileUtils.java @@ -0,0 +1,63 @@ +package com.panda3ds.pandroid.utils; + +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.ParcelFileDescriptor; +import android.util.Log; + +import androidx.documentfile.provider.DocumentFile; + +import com.panda3ds.pandroid.app.PandroidApplication; + +public class FileUtils { + + public static final String MODE_READ = "r"; + + private static Uri parseUri(String value) { + return Uri.parse(value); + } + + private static Context getContext() { + return PandroidApplication.getAppContext(); + } + + public static String getName(String path) { + DocumentFile file = DocumentFile.fromSingleUri(getContext(), parseUri(path)); + return file.getName(); + } + + public static long getSize(String path) { + return DocumentFile.fromSingleUri(getContext(), parseUri(path)).length(); + } + + + public static String getCacheDir() { + return getContext().getCacheDir().getAbsolutePath(); + } + + public static void makeUriPermanent(String uri, String mode) { + + int flags = Intent.FLAG_GRANT_READ_URI_PERMISSION; + if (mode.toLowerCase().contains("w")) + flags &= Intent.FLAG_GRANT_WRITE_URI_PERMISSION; + + getContext().getContentResolver().takePersistableUriPermission(parseUri(uri), flags); + } + + public static int openContentUri(String path, String mode) { + try { + Uri uri = parseUri(path); + ParcelFileDescriptor descriptor = getContext().getContentResolver().openFileDescriptor(uri, mode); + int fd = descriptor.getFd(); + descriptor.detachFd(); + descriptor.close(); + return fd; + } catch (Exception e) { + Log.e(Constants.LOG_TAG, "Error on openContentUri: " + e); + } + + return -1; + } + +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/GameUtils.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/GameUtils.java new file mode 100644 index 00000000..e4c2f53e --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/GameUtils.java @@ -0,0 +1,64 @@ +package com.panda3ds.pandroid.utils; + +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.net.Uri; + +import com.google.gson.Gson; +import com.panda3ds.pandroid.app.GameActivity; +import com.panda3ds.pandroid.app.PandroidApplication; +import com.panda3ds.pandroid.data.game.GameMetadata; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Objects; + +public class GameUtils { + private static final String KEY_GAME_LIST = "gameList"; + private static final ArrayList games = new ArrayList<>(); + private static SharedPreferences data; + private static final Gson gson = new Gson(); + + public static void initialize() { + data = PandroidApplication.getAppContext().getSharedPreferences(Constants.PREF_GAME_UTILS, Context.MODE_PRIVATE); + + GameMetadata[] list = gson.fromJson(data.getString(KEY_GAME_LIST, "[]"), GameMetadata[].class); + games.clear(); + games.addAll(Arrays.asList(list)); + } + + public static GameMetadata findByRomPath(String romPath) { + for (GameMetadata game : games) { + if (Objects.equals(romPath, game.getRomPath())) { + return game; + } + } + return null; + } + + public static void launch(Context context, GameMetadata game) { + String path = PathUtils.getPath(Uri.parse(game.getRomPath())); + context.startActivity(new Intent(context, GameActivity.class).putExtra(Constants.ACTIVITY_PARAMETER_PATH, path)); + } + + public static void removeGame(GameMetadata game) { + games.remove(game); + saveAll(); + } + + public static void addGame(GameMetadata game) { + games.add(game); + saveAll(); + } + + public static ArrayList getGames() { + return new ArrayList<>(games); + } + + private static synchronized void saveAll() { + data.edit() + .putString(KEY_GAME_LIST, gson.toJson(games.toArray(new GameMetadata[0]))) + .apply(); + } +} 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 index 9bfaa0e4..c4682de2 100644 --- 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 @@ -9,8 +9,11 @@ import android.os.Environment; import android.provider.DocumentsContract; import android.provider.MediaStore; +import com.panda3ds.pandroid.app.PandroidApplication; + public class PathUtils { - public static String getPath(final Context context, final Uri uri) { + public static String getPath(final Uri uri) { + final Context context = PandroidApplication.getAppContext(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && DocumentsContract.isDocumentUri(context, uri)) { if (isExternalStorageDocument(uri)) { final String docId = DocumentsContract.getDocumentId(uri); diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/SearchAgent.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/SearchAgent.java new file mode 100644 index 00000000..212c1a1d --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/SearchAgent.java @@ -0,0 +1,91 @@ +package com.panda3ds.pandroid.utils; + +import java.text.Normalizer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; + +public class SearchAgent { + + /** + * Store all possibles results in map + * id->words + */ + private final HashMap searchBuffer = new HashMap<>(); + + // Add search item to list + public void addToBuffer(String id, String... words) { + StringBuilder string = new StringBuilder(); + for (String word : words) { + string.append(normalize(word)).append(" "); + } + searchBuffer.put(id, string.toString()); + } + + /** + * Convert string to simple string with only a-z 0-9 for do this first it get the input string + * and apply lower case, after convert all chars to ASCII + * Ex: ç => c, á => a + * after replace all double space for single space + */ + private String normalize(String string) { + string = Normalizer.normalize(string, Normalizer.Form.NFD).replaceAll("[^\\p{ASCII}]", ""); + + return string.toLowerCase() + .replaceAll("(?!([a-z0-9 ])).*", "") + .replaceAll("\\s\\s", " "); + } + + // Execute search and return array with item id. + public List search(String query) { + String[] words = normalize(query).split("\\s"); + + if (words.length == 0) + return Collections.emptyList(); + + // Map for add all search result: id -> probability + HashMap results = new HashMap<>(); + for (String key : searchBuffer.keySet()) { + int probability = 0; + String value = searchBuffer.get(key); + for (String word : words) { + if (value.contains(word)) + probability++; + } + if (probability > 0) + results.put(key, probability); + } + + + // Filter by probability average + // Ex: A = 10% B = 30% C = 70% (calc is (10+30+70)/3=36) + // After remove all result with probability < 36 + int average = 0; + for (String key : results.keySet()) { + average += results.get(key); + } + average = average / Math.max(1, results.size()); + + int i = 0; + ArrayList resultKeys = new ArrayList<>(Arrays.asList(results.keySet().toArray(new String[0]))); + while ((i < resultKeys.size() && resultKeys.size() > 1)) { + if (results.get(resultKeys.get(i)) < average) { + String key = resultKeys.get(i); + resultKeys.remove(i); + results.remove(key); + i = 0; + continue; + } + i++; + } + + return Arrays.asList(results.keySet().toArray(new String[0])); + } + + // Clear search buffer + public void clearBuffer() { + searchBuffer.clear(); + } +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/SimpleTextWatcher.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/SimpleTextWatcher.java new file mode 100644 index 00000000..46a43ddb --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/SimpleTextWatcher.java @@ -0,0 +1,19 @@ +package com.panda3ds.pandroid.view; + +import android.text.Editable; +import android.text.TextWatcher; + +public interface SimpleTextWatcher extends TextWatcher { + void onChange(String value); + + @Override + default void onTextChanged(CharSequence s, int start, int before, int count) {} + + @Override + default void beforeTextChanged(CharSequence s, int start, int count, int after) {} + + @Override + default void afterTextChanged(Editable s){ + onChange(s.toString()); + } +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/gamesgrid/GameAdapter.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/gamesgrid/GameAdapter.java new file mode 100644 index 00000000..1a3febd4 --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/gamesgrid/GameAdapter.java @@ -0,0 +1,42 @@ +package com.panda3ds.pandroid.view.gamesgrid; + +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.panda3ds.pandroid.R; +import com.panda3ds.pandroid.data.game.GameMetadata; + +import java.util.ArrayList; +import java.util.List; + +class GameAdapter extends RecyclerView.Adapter { + private final ArrayList games = new ArrayList<>(); + + @NonNull + @Override + public ItemHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new ItemHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.holder_game, parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull ItemHolder holder, int position) { + holder.apply(games.get(position)); + } + + public void replace(List games) { + int oldCount = getItemCount(); + this.games.clear(); + notifyItemRangeRemoved(0, oldCount); + this.games.addAll(games); + notifyItemRangeInserted(0, getItemCount()); + } + + @Override + public int getItemCount() { + return games.size(); + } + +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/gamesgrid/GameIconView.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/gamesgrid/GameIconView.java new file mode 100644 index 00000000..df4d2a5c --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/gamesgrid/GameIconView.java @@ -0,0 +1,42 @@ +package com.panda3ds.pandroid.view.gamesgrid; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.AppCompatImageView; + +public class GameIconView extends AppCompatImageView { + + public GameIconView(@NonNull Context context) { + super(context); + } + + public GameIconView(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public GameIconView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + int size = getMeasuredWidth(); + setMeasuredDimension(size, size); + } + + @Override + public void setImageBitmap(Bitmap bm) { + super.setImageBitmap(bm); + Drawable bitmapDrawable = getDrawable(); + if (bitmapDrawable instanceof BitmapDrawable) { + bitmapDrawable.setFilterBitmap(false); + } + } +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/gamesgrid/GamesGridView.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/gamesgrid/GamesGridView.java new file mode 100644 index 00000000..d218d467 --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/gamesgrid/GamesGridView.java @@ -0,0 +1,58 @@ +package com.panda3ds.pandroid.view.gamesgrid; + +import android.content.Context; +import android.util.AttributeSet; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.panda3ds.pandroid.data.game.GameMetadata; + +import java.util.List; + +public class GamesGridView extends RecyclerView { + private int iconSize = 170; + private final GameAdapter adapter; + + public GamesGridView(@NonNull Context context) { + this(context, null); + } + + public GamesGridView(@NonNull Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public GamesGridView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + setLayoutManager(new AutoFitLayout()); + setAdapter(adapter = new GameAdapter()); + } + + public void setGameList(List games) { + adapter.replace(games); + } + + public void setIconSize(int iconSize) { + this.iconSize = iconSize; + requestLayout(); + measure(MeasureSpec.EXACTLY, MeasureSpec.EXACTLY); + } + + private final class AutoFitLayout extends GridLayoutManager { + public AutoFitLayout() { + super(GamesGridView.this.getContext(), 1); + } + + @Override + public void onMeasure(@NonNull Recycler recycler, @NonNull State state, int widthSpec, int heightSpec) { + super.onMeasure(recycler, state, widthSpec, heightSpec); + int width = getMeasuredWidth(); + int iconSize = (int) (GamesGridView.this.iconSize * getResources().getDisplayMetrics().density); + int iconCount = Math.max(1, width / iconSize); + if (getSpanCount() != iconCount) + setSpanCount(iconCount); + } + } +} \ No newline at end of file diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/gamesgrid/ItemHolder.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/gamesgrid/ItemHolder.java new file mode 100644 index 00000000..d2ccba1f --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/gamesgrid/ItemHolder.java @@ -0,0 +1,28 @@ +package com.panda3ds.pandroid.view.gamesgrid; + +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.appcompat.widget.AppCompatTextView; +import androidx.recyclerview.widget.RecyclerView; + +import com.panda3ds.pandroid.R; +import com.panda3ds.pandroid.data.game.GameMetadata; +import com.panda3ds.pandroid.utils.GameUtils; + +class ItemHolder extends RecyclerView.ViewHolder { + public ItemHolder(@NonNull View itemView) { + super(itemView); + } + + public void apply(GameMetadata game) { + ((AppCompatTextView) itemView.findViewById(R.id.title)) + .setText(game.getTitle()); + ((AppCompatTextView) itemView.findViewById(R.id.description)) + .setText(game.getPublisher()); + + itemView.setOnClickListener((v) -> { + GameUtils.launch(v.getContext(), game); + }); + } +} \ No newline at end of file diff --git a/src/pandroid/app/src/main/res/drawable/ic_key_a.xml b/src/pandroid/app/src/main/res/drawable/ic_key_a.xml new file mode 100644 index 00000000..3081c462 --- /dev/null +++ b/src/pandroid/app/src/main/res/drawable/ic_key_a.xml @@ -0,0 +1,5 @@ + + + diff --git a/src/pandroid/app/src/main/res/drawable/ic_search.xml b/src/pandroid/app/src/main/res/drawable/ic_search.xml new file mode 100644 index 00000000..a5687c63 --- /dev/null +++ b/src/pandroid/app/src/main/res/drawable/ic_search.xml @@ -0,0 +1,5 @@ + + + diff --git a/src/pandroid/app/src/main/res/drawable/ic_settings.xml b/src/pandroid/app/src/main/res/drawable/ic_settings.xml new file mode 100644 index 00000000..298a5a1f --- /dev/null +++ b/src/pandroid/app/src/main/res/drawable/ic_settings.xml @@ -0,0 +1,5 @@ + + + diff --git a/src/pandroid/app/src/main/res/drawable/ic_videogame.xml b/src/pandroid/app/src/main/res/drawable/ic_videogame.xml new file mode 100644 index 00000000..8693be5f --- /dev/null +++ b/src/pandroid/app/src/main/res/drawable/ic_videogame.xml @@ -0,0 +1,5 @@ + + + diff --git a/src/pandroid/app/src/main/res/drawable/search_bar_background.xml b/src/pandroid/app/src/main/res/drawable/search_bar_background.xml new file mode 100644 index 00000000..44a1c5b4 --- /dev/null +++ b/src/pandroid/app/src/main/res/drawable/search_bar_background.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/pandroid/app/src/main/res/drawable/simple_card_background.xml b/src/pandroid/app/src/main/res/drawable/simple_card_background.xml new file mode 100644 index 00000000..88845ce4 --- /dev/null +++ b/src/pandroid/app/src/main/res/drawable/simple_card_background.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ 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 deleted file mode 100644 index d58e9c4f..00000000 --- a/src/pandroid/app/src/main/res/drawable/simple_card_button.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - \ 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 deleted file mode 100644 index baf1f293..00000000 --- a/src/pandroid/app/src/main/res/drawable/simple_card_button_left.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - \ 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 deleted file mode 100644 index 2f69341c..00000000 --- a/src/pandroid/app/src/main/res/drawable/simple_card_button_right.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/src/pandroid/app/src/main/res/drawable/temp_thumb.jpg b/src/pandroid/app/src/main/res/drawable/temp_thumb.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e66782be3443db4a00c8ea22027718fb270ae2bf GIT binary patch literal 51816 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 + + + + + + + \ No newline at end of file diff --git a/src/pandroid/app/src/main/res/layout/activity_input_map.xml b/src/pandroid/app/src/main/res/layout/activity_input_map.xml new file mode 100644 index 00000000..cbacc64e --- /dev/null +++ b/src/pandroid/app/src/main/res/layout/activity_input_map.xml @@ -0,0 +1,23 @@ + + + + + + + + \ 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 index 89a17ce9..1facabd3 100644 --- a/src/pandroid/app/src/main/res/layout/activity_main.xml +++ b/src/pandroid/app/src/main/res/layout/activity_main.xml @@ -1,5 +1,5 @@ - - -