diff options
36 files changed, 1281 insertions, 267 deletions
diff --git a/.ci/scripts/windows/docker.sh b/.ci/scripts/windows/docker.sh index 0be3613aa..45f75c874 100755 --- a/.ci/scripts/windows/docker.sh +++ b/.ci/scripts/windows/docker.sh @@ -56,7 +56,6 @@ for i in package/*.exe; do x86_64-w64-mingw32-strip "${i}" done -pip3 install pefile python3 .ci/scripts/windows/scan_dll.py package/*.exe package/imageformats/*.dll "package/" # copy FFmpeg libraries diff --git a/.ci/scripts/windows/install-vulkan-sdk.ps1 b/.ci/scripts/windows/install-vulkan-sdk.ps1 new file mode 100644 index 000000000..de218d90a --- /dev/null +++ b/.ci/scripts/windows/install-vulkan-sdk.ps1 @@ -0,0 +1,33 @@ +# SPDX-FileCopyrightText: 2023 yuzu Emulator Project +# SPDX-License-Identifier: GPL-3.0-or-later + +$ErrorActionPreference = "Stop" + +$VulkanSDKVer = "1.3.250.1" +$ExeFile = "VulkanSDK-$VulkanSDKVer-Installer.exe" +$Uri = "https://sdk.lunarg.com/sdk/download/$VulkanSDKVer/windows/$ExeFile" +$Destination = "./$ExeFile" + +echo "Downloading Vulkan SDK $VulkanSDKVer from $Uri" +$WebClient = New-Object System.Net.WebClient +$WebClient.DownloadFile($Uri, $Destination) +echo "Finished downloading $ExeFile" + +$VULKAN_SDK = "C:/VulkanSDK/$VulkanSDKVer" +$Arguments = "--root `"$VULKAN_SDK`" --accept-licenses --default-answer --confirm-command install" + +echo "Installing Vulkan SDK $VulkanSDKVer" +$InstallProcess = Start-Process -FilePath $Destination -NoNewWindow -PassThru -Wait -ArgumentList $Arguments +$ExitCode = $InstallProcess.ExitCode + +if ($ExitCode -ne 0) { + echo "Error installing Vulkan SDK $VulkanSDKVer (Error: $ExitCode)" + Exit $ExitCode +} + +echo "Finished installing Vulkan SDK $VulkanSDKVer" + +if ("$env:GITHUB_ACTIONS" -eq "true") { + echo "VULKAN_SDK=$VULKAN_SDK" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + echo "$VULKAN_SDK/Bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append +} diff --git a/.ci/templates/build-msvc.yml b/.ci/templates/build-msvc.yml index ceb7e0c32..d069fa9c3 100644 --- a/.ci/templates/build-msvc.yml +++ b/.ci/templates/build-msvc.yml @@ -7,9 +7,12 @@ parameters: version: '' steps: -- script: choco install vulkan-sdk - displayName: 'Install vulkan-sdk' -- script: refreshenv && mkdir build && cd build && cmake -E env CXXFLAGS="/Gw /GA /Gr /Ob2" cmake -G "Visual Studio 17 2022" -A x64 -DCMAKE_POLICY_DEFAULT_CMP0069=NEW -DYUZU_ENABLE_LTO=ON -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_QT_WEB_ENGINE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DYUZU_TESTS=OFF -DUSE_DISCORD_PRESENCE=ON -DENABLE_QT_TRANSLATION=ON -DDISPLAY_VERSION=${{ parameters['version'] }} -DCMAKE_BUILD_TYPE=Release -DYUZU_CRASH_DUMPS=ON .. && cd .. +- task: Powershell@2 + displayName: 'Install Vulkan SDK' + inputs: + targetType: 'filePath' + filePath: './.ci/scripts/windows/install-vulkan-sdk.ps1' +- script: refreshenv && glslangValidator --version && mkdir build && cd build && cmake -E env CXXFLAGS="/Gw /GA /Gr /Ob2" cmake -G "Visual Studio 17 2022" -A x64 -DCMAKE_POLICY_DEFAULT_CMP0069=NEW -DYUZU_ENABLE_LTO=ON -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_QT_WEB_ENGINE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DYUZU_TESTS=OFF -DUSE_DISCORD_PRESENCE=ON -DENABLE_QT_TRANSLATION=ON -DDISPLAY_VERSION=${{ parameters['version'] }} -DCMAKE_BUILD_TYPE=Release -DYUZU_CRASH_DUMPS=ON .. && cd .. displayName: 'Configure CMake' - task: MSBuild@1 displayName: 'Build' diff --git a/.github/workflows/android-build.yml b/.github/workflows/android-build.yml new file mode 100644 index 000000000..5893f860e --- /dev/null +++ b/.github/workflows/android-build.yml @@ -0,0 +1,80 @@ +# SPDX-FileCopyrightText: 2022 yuzu Emulator Project +# SPDX-License-Identifier: GPL-3.0-or-later + +name: 'yuzu-android-build' + +on: + push: + tags: [ "*" ] + +jobs: + android: + runs-on: ubuntu-latest + if: ${{ github.repository == 'yuzu-emu/yuzu-android' }} + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + fetch-depth: 0 + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + - name: Set up cache + uses: actions/cache@v3 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + ~/.ccache + key: ${{ runner.os }}-android-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-android- + - name: Query tag name + uses: olegtarasov/get-tag@v2.1.2 + id: tagName + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y ccache apksigner glslang-dev glslang-tools + - name: Build + run: ./.ci/scripts/android/build.sh + - name: Copy and sign artifacts + env: + ANDROID_KEYSTORE_B64: ${{ secrets.ANDROID_KEYSTORE_B64 }} + ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} + ANDROID_KEYSTORE_PASS: ${{ secrets.ANDROID_KEYSTORE_PASS }} + run: ./.ci/scripts/android/upload.sh + - name: Upload + uses: actions/upload-artifact@v3 + with: + name: android + path: artifacts/ + # release steps + release-android: + runs-on: ubuntu-latest + needs: [android] + if: ${{ startsWith(github.ref, 'refs/tags/') }} + permissions: + contents: write + steps: + - uses: actions/download-artifact@v3 + - name: Query tag name + uses: olegtarasov/get-tag@v2.1.2 + id: tagName + - name: Create release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ steps.tagName.outputs.tag }} + release_name: ${{ steps.tagName.outputs.tag }} + draft: false + prerelease: false + - name: Upload artifacts + uses: alexellis/upload-assets@0.2.3 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + asset_paths: '["./**/*.apk","./**/*.aab"]' diff --git a/.github/workflows/android-merge.js b/.github/workflows/android-merge.js new file mode 100644 index 000000000..7e02dc9e5 --- /dev/null +++ b/.github/workflows/android-merge.js @@ -0,0 +1,218 @@ +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// Note: This is a GitHub Actions script +// It is not meant to be executed directly on your machine without modifications + +const fs = require("fs"); +// which label to check for changes +const CHANGE_LABEL = 'android-merge'; +// how far back in time should we consider the changes are "recent"? (default: 24 hours) +const DETECTION_TIME_FRAME = (parseInt(process.env.DETECTION_TIME_FRAME)) || (24 * 3600 * 1000); + +async function checkBaseChanges(github, context) { + // query the commit date of the latest commit on this branch + const query = `query($owner:String!, $name:String!, $ref:String!) { + repository(name:$name, owner:$owner) { + ref(qualifiedName:$ref) { + target { + ... on Commit { id pushedDate oid } + } + } + } + }`; + const variables = { + owner: context.repo.owner, + name: context.repo.repo, + ref: 'refs/heads/master', + }; + const result = await github.graphql(query, variables); + const pushedAt = result.repository.ref.target.pushedDate; + console.log(`Last commit pushed at ${pushedAt}.`); + const delta = new Date() - new Date(pushedAt); + if (delta <= DETECTION_TIME_FRAME) { + console.info('New changes detected, triggering a new build.'); + return true; + } + console.info('No new changes detected.'); + return false; +} + +async function checkAndroidChanges(github, context) { + if (checkBaseChanges(github, context)) return true; + const query = `query($owner:String!, $name:String!, $label:String!) { + repository(name:$name, owner:$owner) { + pullRequests(labels: [$label], states: OPEN, first: 100) { + nodes { number headRepository { pushedAt } } + } + } + }`; + const variables = { + owner: context.repo.owner, + name: context.repo.repo, + label: CHANGE_LABEL, + }; + const result = await github.graphql(query, variables); + const pulls = result.repository.pullRequests.nodes; + for (let i = 0; i < pulls.length; i++) { + let pull = pulls[i]; + if (new Date() - new Date(pull.headRepository.pushedAt) <= DETECTION_TIME_FRAME) { + console.info(`${pull.number} updated at ${pull.headRepository.pushedAt}`); + return true; + } + } + console.info("No changes detected in any tagged pull requests."); + return false; +} + +async function tagAndPush(github, owner, repo, execa, commit=false) { + let altToken = process.env.ALT_GITHUB_TOKEN; + if (!altToken) { + throw `Please set ALT_GITHUB_TOKEN environment variable. This token should have write access to ${owner}/${repo}.`; + } + const query = `query ($owner:String!, $name:String!) { + repository(name:$name, owner:$owner) { + refs(refPrefix: "refs/tags/", orderBy: {field: TAG_COMMIT_DATE, direction: DESC}, first: 10) { + nodes { name } + } + } + }`; + const variables = { + owner: owner, + name: repo, + }; + const tags = await github.graphql(query, variables); + const tagList = tags.repository.refs.nodes; + const lastTag = tagList[0] ? tagList[0].name : 'dummy-0'; + const tagNumber = /\w+-(\d+)/.exec(lastTag)[1] | 0; + const channel = repo.split('-')[1]; + const newTag = `${channel}-${tagNumber + 1}`; + console.log(`New tag: ${newTag}`); + if (commit) { + let channelName = channel[0].toUpperCase() + channel.slice(1); + console.info(`Committing pending commit as ${channelName} #${tagNumber + 1}`); + await execa("git", ['commit', '-m', `${channelName} #${tagNumber + 1}`]); + } + console.info('Pushing tags to GitHub ...'); + await execa("git", ['tag', newTag]); + await execa("git", ['remote', 'add', 'target', `https://${altToken}@github.com/${owner}/${repo}.git`]); + await execa("git", ['push', 'target', 'master', '-f']); + await execa("git", ['push', 'target', 'master', '--tags']); + console.info('Successfully pushed new changes.'); +} + +async function generateReadme(pulls, context, mergeResults, execa) { + let baseUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/`; + let output = + "| Pull Request | Commit | Title | Author | Merged? |\n|----|----|----|----|----|\n"; + for (let pull of pulls) { + let pr = pull.number; + let result = mergeResults[pr]; + output += `| [${pr}](${baseUrl}/pull/${pr}) | [\`${result.rev || "N/A"}\`](${baseUrl}/pull/${pr}/files) | ${pull.title} | [${pull.author.login}](https://github.com/${pull.author.login}/) | ${result.success ? "Yes" : "No"} |\n`; + } + output += + "\n\nEnd of merge log. You can find the original README.md below the break.\n\n-----\n\n"; + output += fs.readFileSync("./README.md"); + fs.writeFileSync("./README.md", output); + await execa("git", ["add", "README.md"]); +} + +async function fetchPullRequests(pulls, repoUrl, execa) { + console.log("::group::Fetch pull requests"); + for (let pull of pulls) { + let pr = pull.number; + console.info(`Fetching PR ${pr} ...`); + await execa("git", [ + "fetch", + "-f", + "--no-recurse-submodules", + repoUrl, + `pull/${pr}/head:pr-${pr}`, + ]); + } + console.log("::endgroup::"); +} + +async function mergePullRequests(pulls, execa) { + let mergeResults = {}; + console.log("::group::Merge pull requests"); + await execa("git", ["config", "--global", "user.name", "yuzubot"]); + await execa("git", [ + "config", + "--global", + "user.email", + "yuzu\x40yuzu-emu\x2eorg", // prevent email harvesters from scraping the address + ]); + let hasFailed = false; + for (let pull of pulls) { + let pr = pull.number; + console.info(`Merging PR ${pr} ...`); + try { + const process1 = execa("git", [ + "merge", + "--squash", + "--no-edit", + `pr-${pr}`, + ]); + process1.stdout.pipe(process.stdout); + await process1; + + const process2 = execa("git", ["commit", "-m", `Merge PR ${pr}`]); + process2.stdout.pipe(process.stdout); + await process2; + + const process3 = await execa("git", ["rev-parse", "--short", `pr-${pr}`]); + mergeResults[pr] = { + success: true, + rev: process3.stdout, + }; + } catch (err) { + console.log( + `::error title=#${pr} not merged::Failed to merge pull request: ${pr}: ${err}` + ); + mergeResults[pr] = { success: false }; + hasFailed = true; + await execa("git", ["reset", "--hard"]); + } + } + console.log("::endgroup::"); + if (hasFailed) { + throw 'There are merge failures. Aborting!'; + } + return mergeResults; +} + +async function mergebot(github, context, execa) { + const query = `query ($owner:String!, $name:String!, $label:String!) { + repository(name:$name, owner:$owner) { + pullRequests(labels: [$label], states: OPEN, first: 100) { + nodes { + number title author { login } + } + } + } + }`; + const variables = { + owner: context.repo.owner, + name: context.repo.repo, + label: CHANGE_LABEL, + }; + const result = await github.graphql(query, variables); + const pulls = result.repository.pullRequests.nodes; + let displayList = []; + for (let i = 0; i < pulls.length; i++) { + let pull = pulls[i]; + displayList.push({ PR: pull.number, Title: pull.title }); + } + console.info("The following pull requests will be merged:"); + console.table(displayList); + await fetchPullRequests(pulls, "https://github.com/yuzu-emu/yuzu", execa); + const mergeResults = await mergePullRequests(pulls, execa); + await generateReadme(pulls, context, mergeResults, execa); + await tagAndPush(github, context.repo.owner, `${context.repo.repo}-android`, execa, true); +} + +module.exports.mergebot = mergebot; +module.exports.checkAndroidChanges = checkAndroidChanges; +module.exports.tagAndPush = tagAndPush; +module.exports.checkBaseChanges = checkBaseChanges; diff --git a/.github/workflows/android-publish.yml b/.github/workflows/android-publish.yml new file mode 100644 index 000000000..8f46fcf74 --- /dev/null +++ b/.github/workflows/android-publish.yml @@ -0,0 +1,57 @@ +# SPDX-FileCopyrightText: 2023 yuzu Emulator Project +# SPDX-License-Identifier: GPL-2.0-or-later + +name: yuzu-android-publish + +on: + schedule: + - cron: '37 0 * * *' + workflow_dispatch: + inputs: + android: + description: 'Whether to trigger an Android build (true/false/auto)' + required: false + default: 'true' + +jobs: + android: + runs-on: ubuntu-latest + if: ${{ github.event.inputs.android != 'false' && github.repository == 'yuzu-emu/yuzu' }} + steps: + # this checkout is required to make sure the GitHub Actions scripts are available + - uses: actions/checkout@v3 + name: Pre-checkout + with: + submodules: false + - uses: actions/github-script@v6 + id: check-changes + name: 'Check for new changes' + env: + # 24 hours + DETECTION_TIME_FRAME: 86400000 + with: + script: | + if (context.payload.inputs && context.payload.inputs.android === 'true') return true; + const checkAndroidChanges = require('./.github/workflows/android-merge.js').checkAndroidChanges; + return checkAndroidChanges(github, context); + - run: npm install execa@5 + if: ${{ steps.check-changes.outputs.result == 'true' }} + - uses: actions/checkout@v3 + name: Checkout + if: ${{ steps.check-changes.outputs.result == 'true' }} + with: + path: 'yuzu-merge' + fetch-depth: 0 + submodules: true + token: ${{ secrets.ALT_GITHUB_TOKEN }} + - uses: actions/github-script@v5 + name: 'Check and merge Android changes' + if: ${{ steps.check-changes.outputs.result == 'true' }} + env: + ALT_GITHUB_TOKEN: ${{ secrets.ALT_GITHUB_TOKEN }} + with: + script: | + const execa = require("execa"); + const mergebot = require('./.github/workflows/android-merge.js').mergebot; + process.chdir('${{ github.workspace }}/yuzu-merge'); + mergebot(github, context, execa); diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index bd4141f56..cbe6b0fbd 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -73,6 +73,10 @@ jobs: needs: format runs-on: windows-2022 steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + fetch-depth: 0 - name: Set up cache uses: actions/cache@v3 with: @@ -81,22 +85,22 @@ jobs: restore-keys: | ${{ runner.os }}-msvc- - name: Install dependencies - # due to how chocolatey works, only cmd.exe is supported here - shell: cmd + shell: pwsh run: | - choco install vulkan-sdk wget - call refreshenv - wget https://github.com/mbitsnbites/buildcache/releases/download/v0.27.6/buildcache-windows.zip - 7z x buildcache-windows.zip - copy buildcache\bin\buildcache.exe C:\ProgramData\chocolatey\bin - rmdir buildcache - echo %PATH% >> %GITHUB_PATH% + $ErrorActionPreference = "Stop" + $BuildCacheVer = "v0.28.4" + $File = "buildcache-windows.zip" + $Uri = "https://github.com/mbitsnbites/buildcache/releases/download/$BuildCacheVer/$File" + $WebClient = New-Object System.Net.WebClient + $WebClient.DownloadFile($Uri, $File) + 7z x $File + $CurrentDir = Convert-Path . + echo "$CurrentDir/buildcache/bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + - name: Install Vulkan SDK + shell: pwsh + run: .\.ci\scripts\windows\install-vulkan-sdk.ps1 - name: Set up MSVC uses: ilammy/msvc-dev-cmd@v1 - - uses: actions/checkout@v3 - with: - submodules: recursive - fetch-depth: 0 - name: Configure env: CC: cl.exe @@ -129,11 +133,12 @@ jobs: - uses: actions/checkout@v3 with: submodules: recursive + fetch-depth: 0 - name: set up JDK 17 uses: actions/setup-java@v3 with: java-version: '17' - distribution: 'adopt' + distribution: 'temurin' - name: Set up cache uses: actions/cache@v3 with: @@ -151,7 +156,6 @@ jobs: run: | sudo apt-get update sudo apt-get install -y ccache apksigner glslang-dev glslang-tools - git -C ./externals/vcpkg/ fetch --all --unshallow - name: Build run: ./.ci/scripts/android/build.sh - name: Copy and sign artifacts diff --git a/CMakeLists.txt b/CMakeLists.txt index f5ef0ef50..7f8febb90 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -285,7 +285,7 @@ find_package(ZLIB 1.2 REQUIRED) find_package(zstd 1.5 REQUIRED) if (NOT YUZU_USE_EXTERNAL_VULKAN_HEADERS) - find_package(Vulkan 1.3.246 REQUIRED) + find_package(Vulkan 1.3.256 REQUIRED) endif() if (ENABLE_LIBUSB) @@ -489,7 +489,7 @@ if (ENABLE_SDL2) if (YUZU_USE_BUNDLED_SDL2) # Detect toolchain and platform if ((MSVC_VERSION GREATER_EQUAL 1920 AND MSVC_VERSION LESS 1940) AND ARCHITECTURE_x86_64) - set(SDL2_VER "SDL2-2.28.0") + set(SDL2_VER "SDL2-2.28.1") else() message(FATAL_ERROR "No bundled SDL2 binaries for your toolchain. Disable YUZU_USE_BUNDLED_SDL2 and provide your own.") endif() diff --git a/externals/SDL b/externals/SDL -Subproject 491fba1d06a4810645092b2559b9cc94abeb23b +Subproject 116a5344ff4e8b8166eac2db540cd6578b4ba02 diff --git a/externals/Vulkan-Headers b/externals/Vulkan-Headers -Subproject 63af1cf1ee906ba4dcd5a324bdd0201d4f4bfd1 +Subproject ed857118e243fdc0f3a100f00ac9919e874cfe6 diff --git a/src/android/app/src/main/AndroidManifest.xml b/src/android/app/src/main/AndroidManifest.xml index 51d949d65..6184f3eb6 100644 --- a/src/android/app/src/main/AndroidManifest.xml +++ b/src/android/app/src/main/AndroidManifest.xml @@ -22,7 +22,7 @@ SPDX-License-Identifier: GPL-3.0-or-later android:label="@string/app_name_suffixed" android:icon="@drawable/ic_launcher" android:allowBackup="true" - android:hasFragileUserData="true" + android:hasFragileUserData="false" android:supportsRtl="true" android:isGame="true" android:localeConfig="@xml/locales_config" @@ -54,6 +54,7 @@ SPDX-License-Identifier: GPL-3.0-or-later <activity android:name="org.yuzu.yuzu_emu.activities.EmulationActivity" android:theme="@style/Theme.Yuzu.Main" + android:launchMode="singleTop" android:screenOrientation="userLandscape" android:supportsPictureInPicture="true" android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|uiMode" diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt index be6e17e65..a6251bafd 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt @@ -112,25 +112,36 @@ class Settings { const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown" - const val PREF_OVERLAY_INIT = "OverlayInit" + const val PREF_OVERLAY_VERSION = "OverlayVersion" + const val PREF_LANDSCAPE_OVERLAY_VERSION = "LandscapeOverlayVersion" + const val PREF_PORTRAIT_OVERLAY_VERSION = "PortraitOverlayVersion" + const val PREF_FOLDABLE_OVERLAY_VERSION = "FoldableOverlayVersion" + val overlayLayoutPrefs = listOf( + PREF_LANDSCAPE_OVERLAY_VERSION, + PREF_PORTRAIT_OVERLAY_VERSION, + PREF_FOLDABLE_OVERLAY_VERSION + ) + const val PREF_CONTROL_SCALE = "controlScale" const val PREF_CONTROL_OPACITY = "controlOpacity" const val PREF_TOUCH_ENABLED = "isTouchEnabled" - const val PREF_BUTTON_TOGGLE_0 = "buttonToggle0" - const val PREF_BUTTON_TOGGLE_1 = "buttonToggle1" - const val PREF_BUTTON_TOGGLE_2 = "buttonToggle2" - const val PREF_BUTTON_TOGGLE_3 = "buttonToggle3" - const val PREF_BUTTON_TOGGLE_4 = "buttonToggle4" - const val PREF_BUTTON_TOGGLE_5 = "buttonToggle5" - const val PREF_BUTTON_TOGGLE_6 = "buttonToggle6" - const val PREF_BUTTON_TOGGLE_7 = "buttonToggle7" - const val PREF_BUTTON_TOGGLE_8 = "buttonToggle8" - const val PREF_BUTTON_TOGGLE_9 = "buttonToggle9" - const val PREF_BUTTON_TOGGLE_10 = "buttonToggle10" - const val PREF_BUTTON_TOGGLE_11 = "buttonToggle11" - const val PREF_BUTTON_TOGGLE_12 = "buttonToggle12" - const val PREF_BUTTON_TOGGLE_13 = "buttonToggle13" - const val PREF_BUTTON_TOGGLE_14 = "buttonToggle14" + const val PREF_BUTTON_A = "buttonToggle0" + const val PREF_BUTTON_B = "buttonToggle1" + const val PREF_BUTTON_X = "buttonToggle2" + const val PREF_BUTTON_Y = "buttonToggle3" + const val PREF_BUTTON_L = "buttonToggle4" + const val PREF_BUTTON_R = "buttonToggle5" + const val PREF_BUTTON_ZL = "buttonToggle6" + const val PREF_BUTTON_ZR = "buttonToggle7" + const val PREF_BUTTON_PLUS = "buttonToggle8" + const val PREF_BUTTON_MINUS = "buttonToggle9" + const val PREF_BUTTON_DPAD = "buttonToggle10" + const val PREF_STICK_L = "buttonToggle11" + const val PREF_STICK_R = "buttonToggle12" + const val PREF_BUTTON_STICK_L = "buttonToggle13" + const val PREF_BUTTON_STICK_R = "buttonToggle14" + const val PREF_BUTTON_HOME = "buttonToggle15" + const val PREF_BUTTON_SCREENSHOT = "buttonToggle16" const val PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER = "EmulationMenuSettings_JoystickRelCenter" const val PREF_MENU_SETTINGS_DPAD_SLIDE = "EmulationMenuSettings_DpadSlideEnable" @@ -145,6 +156,30 @@ class Settings { private val configFileSectionsMap: MutableMap<String, List<String>> = HashMap() + val overlayPreferences = listOf( + PREF_OVERLAY_VERSION, + PREF_CONTROL_SCALE, + PREF_CONTROL_OPACITY, + PREF_TOUCH_ENABLED, + PREF_BUTTON_A, + PREF_BUTTON_B, + PREF_BUTTON_X, + PREF_BUTTON_Y, + PREF_BUTTON_L, + PREF_BUTTON_R, + PREF_BUTTON_ZL, + PREF_BUTTON_ZR, + PREF_BUTTON_PLUS, + PREF_BUTTON_MINUS, + PREF_BUTTON_DPAD, + PREF_STICK_L, + PREF_STICK_R, + PREF_BUTTON_HOME, + PREF_BUTTON_SCREENSHOT, + PREF_BUTTON_STICK_L, + PREF_BUTTON_STICK_R + ) + const val LayoutOption_Unspecified = 0 const val LayoutOption_MobilePortrait = 4 const val LayoutOption_MobileLandscape = 5 diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt index 09976db62..0e7c1ba88 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt @@ -212,9 +212,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { } if (!isInFoldableLayout) { if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { - binding.surfaceInputOverlay.orientation = InputOverlay.PORTRAIT + binding.surfaceInputOverlay.layout = InputOverlay.PORTRAIT } else { - binding.surfaceInputOverlay.orientation = InputOverlay.LANDSCAPE + binding.surfaceInputOverlay.layout = InputOverlay.LANDSCAPE } } if (!binding.surfaceInputOverlay.isInEditMode) { @@ -260,7 +260,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { .remove(Settings.PREF_CONTROL_SCALE) .remove(Settings.PREF_CONTROL_OPACITY) .apply() - binding.surfaceInputOverlay.post { binding.surfaceInputOverlay.resetButtonPlacement() } + binding.surfaceInputOverlay.post { + binding.surfaceInputOverlay.resetLayoutVisibilityAndPlacement() + } } private fun updateShowFpsOverlay() { @@ -337,7 +339,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { binding.inGameMenu.layoutParams.height = it.bounds.bottom isInFoldableLayout = true - binding.surfaceInputOverlay.orientation = InputOverlay.FOLDABLE + binding.surfaceInputOverlay.layout = InputOverlay.FOLDABLE refreshInputOverlay() } } @@ -410,9 +412,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { R.id.menu_toggle_controls -> { val preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) - val optionsArray = BooleanArray(15) - for (i in 0..14) { - optionsArray[i] = preferences.getBoolean("buttonToggle$i", i < 13) + val optionsArray = BooleanArray(Settings.overlayPreferences.size) + Settings.overlayPreferences.forEachIndexed { i, _ -> + optionsArray[i] = preferences.getBoolean("buttonToggle$i", i < 15) } val dialog = MaterialAlertDialogBuilder(requireContext()) @@ -436,7 +438,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { dialog.getButton(AlertDialog.BUTTON_NEUTRAL) .setOnClickListener { val isChecked = !optionsArray[0] - for (i in 0..14) { + Settings.overlayPreferences.forEachIndexed { i, _ -> optionsArray[i] = isChecked dialog.listView.setItemChecked(i, isChecked) preferences.edit() diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt index 6251ec783..c055c2e35 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt @@ -51,15 +51,23 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : private lateinit var windowInsets: WindowInsets - var orientation = LANDSCAPE + var layout = LANDSCAPE override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { super.onLayout(changed, left, top, right, bottom) windowInsets = rootWindowInsets - if (!preferences.getBoolean("${Settings.PREF_OVERLAY_INIT}$orientation", false)) { - defaultOverlay() + val overlayVersion = preferences.getInt(Settings.PREF_OVERLAY_VERSION, 0) + if (overlayVersion != OVERLAY_VERSION) { + resetAllLayouts() + } else { + val layoutIndex = overlayLayouts.indexOf(layout) + val currentLayoutVersion = + preferences.getInt(Settings.overlayLayoutPrefs[layoutIndex], 0) + if (currentLayoutVersion != overlayLayoutVersions[layoutIndex]) { + resetCurrentLayout() + } } // Load the controls. @@ -266,10 +274,10 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : MotionEvent.ACTION_POINTER_UP -> if (buttonBeingConfigured === button) { // Persist button position by saving new place. saveControlPosition( - buttonBeingConfigured!!.buttonId, + buttonBeingConfigured!!.prefId, buttonBeingConfigured!!.bounds.centerX(), buttonBeingConfigured!!.bounds.centerY(), - orientation + layout ) buttonBeingConfigured = null } @@ -299,10 +307,10 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : MotionEvent.ACTION_POINTER_UP -> if (dpadBeingConfigured === dpad) { // Persist button position by saving new place. saveControlPosition( - dpadBeingConfigured!!.upId, + Settings.PREF_BUTTON_DPAD, dpadBeingConfigured!!.bounds.centerX(), dpadBeingConfigured!!.bounds.centerY(), - orientation + layout ) dpadBeingConfigured = null } @@ -330,10 +338,10 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP -> if (joystickBeingConfigured != null) { saveControlPosition( - joystickBeingConfigured!!.buttonId, + joystickBeingConfigured!!.prefId, joystickBeingConfigured!!.bounds.centerX(), joystickBeingConfigured!!.bounds.centerY(), - orientation + layout ) joystickBeingConfigured = null } @@ -343,9 +351,9 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : return true } - private fun addOverlayControls(orientation: String) { + private fun addOverlayControls(layout: String) { val windowSize = getSafeScreenSize(context) - if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_0, true)) { + if (preferences.getBoolean(Settings.PREF_BUTTON_A, true)) { overlayButtons.add( initializeOverlayButton( context, @@ -353,11 +361,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : R.drawable.facebutton_a, R.drawable.facebutton_a_depressed, ButtonType.BUTTON_A, - orientation + Settings.PREF_BUTTON_A, + layout ) ) } - if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_1, true)) { + if (preferences.getBoolean(Settings.PREF_BUTTON_B, true)) { overlayButtons.add( initializeOverlayButton( context, @@ -365,11 +374,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : R.drawable.facebutton_b, R.drawable.facebutton_b_depressed, ButtonType.BUTTON_B, - orientation + Settings.PREF_BUTTON_B, + layout ) ) } - if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_2, true)) { + if (preferences.getBoolean(Settings.PREF_BUTTON_X, true)) { overlayButtons.add( initializeOverlayButton( context, @@ -377,11 +387,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : R.drawable.facebutton_x, R.drawable.facebutton_x_depressed, ButtonType.BUTTON_X, - orientation + Settings.PREF_BUTTON_X, + layout ) ) } - if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_3, true)) { + if (preferences.getBoolean(Settings.PREF_BUTTON_Y, true)) { overlayButtons.add( initializeOverlayButton( context, @@ -389,11 +400,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : R.drawable.facebutton_y, R.drawable.facebutton_y_depressed, ButtonType.BUTTON_Y, - orientation + Settings.PREF_BUTTON_Y, + layout ) ) } - if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_4, true)) { + if (preferences.getBoolean(Settings.PREF_BUTTON_L, true)) { overlayButtons.add( initializeOverlayButton( context, @@ -401,11 +413,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : R.drawable.l_shoulder, R.drawable.l_shoulder_depressed, ButtonType.TRIGGER_L, - orientation + Settings.PREF_BUTTON_L, + layout ) ) } - if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_5, true)) { + if (preferences.getBoolean(Settings.PREF_BUTTON_R, true)) { overlayButtons.add( initializeOverlayButton( context, @@ -413,11 +426,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : R.drawable.r_shoulder, R.drawable.r_shoulder_depressed, ButtonType.TRIGGER_R, - orientation + Settings.PREF_BUTTON_R, + layout ) ) } - if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_6, true)) { + if (preferences.getBoolean(Settings.PREF_BUTTON_ZL, true)) { overlayButtons.add( initializeOverlayButton( context, @@ -425,11 +439,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : R.drawable.zl_trigger, R.drawable.zl_trigger_depressed, ButtonType.TRIGGER_ZL, - orientation + Settings.PREF_BUTTON_ZL, + layout ) ) } - if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_7, true)) { + if (preferences.getBoolean(Settings.PREF_BUTTON_ZR, true)) { overlayButtons.add( initializeOverlayButton( context, @@ -437,11 +452,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : R.drawable.zr_trigger, R.drawable.zr_trigger_depressed, ButtonType.TRIGGER_ZR, - orientation + Settings.PREF_BUTTON_ZR, + layout ) ) } - if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_8, true)) { + if (preferences.getBoolean(Settings.PREF_BUTTON_PLUS, true)) { overlayButtons.add( initializeOverlayButton( context, @@ -449,11 +465,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : R.drawable.facebutton_plus, R.drawable.facebutton_plus_depressed, ButtonType.BUTTON_PLUS, - orientation + Settings.PREF_BUTTON_PLUS, + layout ) ) } - if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_9, true)) { + if (preferences.getBoolean(Settings.PREF_BUTTON_MINUS, true)) { overlayButtons.add( initializeOverlayButton( context, @@ -461,11 +478,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : R.drawable.facebutton_minus, R.drawable.facebutton_minus_depressed, ButtonType.BUTTON_MINUS, - orientation + Settings.PREF_BUTTON_MINUS, + layout ) ) } - if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_10, true)) { + if (preferences.getBoolean(Settings.PREF_BUTTON_DPAD, true)) { overlayDpads.add( initializeOverlayDpad( context, @@ -473,11 +491,11 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : R.drawable.dpad_standard, R.drawable.dpad_standard_cardinal_depressed, R.drawable.dpad_standard_diagonal_depressed, - orientation + layout ) ) } - if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_11, true)) { + if (preferences.getBoolean(Settings.PREF_STICK_L, true)) { overlayJoysticks.add( initializeOverlayJoystick( context, @@ -487,11 +505,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : R.drawable.joystick_depressed, StickType.STICK_L, ButtonType.STICK_L, - orientation + Settings.PREF_STICK_L, + layout ) ) } - if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_12, true)) { + if (preferences.getBoolean(Settings.PREF_STICK_R, true)) { overlayJoysticks.add( initializeOverlayJoystick( context, @@ -501,11 +520,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : R.drawable.joystick_depressed, StickType.STICK_R, ButtonType.STICK_R, - orientation + Settings.PREF_STICK_R, + layout ) ) } - if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_13, false)) { + if (preferences.getBoolean(Settings.PREF_BUTTON_HOME, false)) { overlayButtons.add( initializeOverlayButton( context, @@ -513,11 +533,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : R.drawable.facebutton_home, R.drawable.facebutton_home_depressed, ButtonType.BUTTON_HOME, - orientation + Settings.PREF_BUTTON_HOME, + layout ) ) } - if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_14, false)) { + if (preferences.getBoolean(Settings.PREF_BUTTON_SCREENSHOT, false)) { overlayButtons.add( initializeOverlayButton( context, @@ -525,7 +546,34 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : R.drawable.facebutton_screenshot, R.drawable.facebutton_screenshot_depressed, ButtonType.BUTTON_CAPTURE, - orientation + Settings.PREF_BUTTON_SCREENSHOT, + layout + ) + ) + } + if (preferences.getBoolean(Settings.PREF_BUTTON_STICK_L, true)) { + overlayButtons.add( + initializeOverlayButton( + context, + windowSize, + R.drawable.button_l3, + R.drawable.button_l3_depressed, + ButtonType.STICK_L, + Settings.PREF_BUTTON_STICK_L, + layout + ) + ) + } + if (preferences.getBoolean(Settings.PREF_BUTTON_STICK_R, true)) { + overlayButtons.add( + initializeOverlayButton( + context, + windowSize, + R.drawable.button_r3, + R.drawable.button_r3_depressed, + ButtonType.STICK_R, + Settings.PREF_BUTTON_STICK_R, + layout ) ) } @@ -539,18 +587,18 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : // Add all the enabled overlay items back to the HashSet. if (EmulationMenuSettings.showOverlay) { - addOverlayControls(orientation) + addOverlayControls(layout) } invalidate() } - private fun saveControlPosition(sharedPrefsId: Int, x: Int, y: Int, orientation: String) { + private fun saveControlPosition(prefId: String, x: Int, y: Int, layout: String) { val windowSize = getSafeScreenSize(context) val min = windowSize.first val max = windowSize.second PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit() - .putFloat("$sharedPrefsId-X$orientation", (x - min.x).toFloat() / max.x) - .putFloat("$sharedPrefsId-Y$orientation", (y - min.y).toFloat() / max.y) + .putFloat("$prefId-X$layout", (x - min.x).toFloat() / max.x) + .putFloat("$prefId-Y$layout", (y - min.y).toFloat() / max.y) .apply() } @@ -558,19 +606,31 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : inEditMode = editMode } - private fun defaultOverlay() { - if (!preferences.getBoolean("${Settings.PREF_OVERLAY_INIT}$orientation", false)) { - defaultOverlayByLayout(orientation) - } - - resetButtonPlacement() + private fun resetCurrentLayout() { + defaultOverlayByLayout(layout) + val layoutIndex = overlayLayouts.indexOf(layout) preferences.edit() - .putBoolean("${Settings.PREF_OVERLAY_INIT}$orientation", true) + .putInt(Settings.overlayLayoutPrefs[layoutIndex], overlayLayoutVersions[layoutIndex]) .apply() } - fun resetButtonPlacement() { - defaultOverlayByLayout(orientation) + private fun resetAllLayouts() { + val editor = preferences.edit() + overlayLayouts.forEachIndexed { i, layout -> + defaultOverlayByLayout(layout) + editor.putInt(Settings.overlayLayoutPrefs[i], overlayLayoutVersions[i]) + } + editor.putInt(Settings.PREF_OVERLAY_VERSION, OVERLAY_VERSION) + editor.apply() + } + + fun resetLayoutVisibilityAndPlacement() { + defaultOverlayByLayout(layout) + val editor = preferences.edit() + Settings.overlayPreferences.forEachIndexed { _, pref -> + editor.remove(pref) + } + editor.apply() refreshControls() } @@ -604,7 +664,11 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : R.integer.SWITCH_STICK_R_X, R.integer.SWITCH_STICK_R_Y, R.integer.SWITCH_STICK_L_X, - R.integer.SWITCH_STICK_L_Y + R.integer.SWITCH_STICK_L_Y, + R.integer.SWITCH_BUTTON_STICK_L_X, + R.integer.SWITCH_BUTTON_STICK_L_Y, + R.integer.SWITCH_BUTTON_STICK_R_X, + R.integer.SWITCH_BUTTON_STICK_R_Y ) private val portraitResources = arrayOf( @@ -637,7 +701,11 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : R.integer.SWITCH_STICK_R_X_PORTRAIT, R.integer.SWITCH_STICK_R_Y_PORTRAIT, R.integer.SWITCH_STICK_L_X_PORTRAIT, - R.integer.SWITCH_STICK_L_Y_PORTRAIT + R.integer.SWITCH_STICK_L_Y_PORTRAIT, + R.integer.SWITCH_BUTTON_STICK_L_X_PORTRAIT, + R.integer.SWITCH_BUTTON_STICK_L_Y_PORTRAIT, + R.integer.SWITCH_BUTTON_STICK_R_X_PORTRAIT, + R.integer.SWITCH_BUTTON_STICK_R_Y_PORTRAIT ) private val foldableResources = arrayOf( @@ -670,139 +738,159 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : R.integer.SWITCH_STICK_R_X_FOLDABLE, R.integer.SWITCH_STICK_R_Y_FOLDABLE, R.integer.SWITCH_STICK_L_X_FOLDABLE, - R.integer.SWITCH_STICK_L_Y_FOLDABLE + R.integer.SWITCH_STICK_L_Y_FOLDABLE, + R.integer.SWITCH_BUTTON_STICK_L_X_FOLDABLE, + R.integer.SWITCH_BUTTON_STICK_L_Y_FOLDABLE, + R.integer.SWITCH_BUTTON_STICK_R_X_FOLDABLE, + R.integer.SWITCH_BUTTON_STICK_R_Y_FOLDABLE ) - private fun getResourceValue(orientation: String, position: Int): Float { - return when (orientation) { + private fun getResourceValue(layout: String, position: Int): Float { + return when (layout) { PORTRAIT -> resources.getInteger(portraitResources[position]).toFloat() / 1000 FOLDABLE -> resources.getInteger(foldableResources[position]).toFloat() / 1000 else -> resources.getInteger(landscapeResources[position]).toFloat() / 1000 } } - private fun defaultOverlayByLayout(orientation: String) { + private fun defaultOverlayByLayout(layout: String) { // Each value represents the position of the button in relation to the screen size without insets. preferences.edit() .putFloat( - ButtonType.BUTTON_A.toString() + "-X$orientation", - getResourceValue(orientation, 0) + "${Settings.PREF_BUTTON_A}-X$layout", + getResourceValue(layout, 0) + ) + .putFloat( + "${Settings.PREF_BUTTON_A}-Y$layout", + getResourceValue(layout, 1) + ) + .putFloat( + "${Settings.PREF_BUTTON_B}-X$layout", + getResourceValue(layout, 2) + ) + .putFloat( + "${Settings.PREF_BUTTON_B}-Y$layout", + getResourceValue(layout, 3) + ) + .putFloat( + "${Settings.PREF_BUTTON_X}-X$layout", + getResourceValue(layout, 4) ) .putFloat( - ButtonType.BUTTON_A.toString() + "-Y$orientation", - getResourceValue(orientation, 1) + "${Settings.PREF_BUTTON_X}-Y$layout", + getResourceValue(layout, 5) ) .putFloat( - ButtonType.BUTTON_B.toString() + "-X$orientation", - getResourceValue(orientation, 2) + "${Settings.PREF_BUTTON_Y}-X$layout", + getResourceValue(layout, 6) ) .putFloat( - ButtonType.BUTTON_B.toString() + "-Y$orientation", - getResourceValue(orientation, 3) + "${Settings.PREF_BUTTON_Y}-Y$layout", + getResourceValue(layout, 7) ) .putFloat( - ButtonType.BUTTON_X.toString() + "-X$orientation", - getResourceValue(orientation, 4) + "${Settings.PREF_BUTTON_ZL}-X$layout", + getResourceValue(layout, 8) ) .putFloat( - ButtonType.BUTTON_X.toString() + "-Y$orientation", - getResourceValue(orientation, 5) + "${Settings.PREF_BUTTON_ZL}-Y$layout", + getResourceValue(layout, 9) ) .putFloat( - ButtonType.BUTTON_Y.toString() + "-X$orientation", - getResourceValue(orientation, 6) + "${Settings.PREF_BUTTON_ZR}-X$layout", + getResourceValue(layout, 10) ) .putFloat( - ButtonType.BUTTON_Y.toString() + "-Y$orientation", - getResourceValue(orientation, 7) + "${Settings.PREF_BUTTON_ZR}-Y$layout", + getResourceValue(layout, 11) ) .putFloat( - ButtonType.TRIGGER_ZL.toString() + "-X$orientation", - getResourceValue(orientation, 8) + "${Settings.PREF_BUTTON_DPAD}-X$layout", + getResourceValue(layout, 12) ) .putFloat( - ButtonType.TRIGGER_ZL.toString() + "-Y$orientation", - getResourceValue(orientation, 9) + "${Settings.PREF_BUTTON_DPAD}-Y$layout", + getResourceValue(layout, 13) ) .putFloat( - ButtonType.TRIGGER_ZR.toString() + "-X$orientation", - getResourceValue(orientation, 10) + "${Settings.PREF_BUTTON_L}-X$layout", + getResourceValue(layout, 14) ) .putFloat( - ButtonType.TRIGGER_ZR.toString() + "-Y$orientation", - getResourceValue(orientation, 11) + "${Settings.PREF_BUTTON_L}-Y$layout", + getResourceValue(layout, 15) ) .putFloat( - ButtonType.DPAD_UP.toString() + "-X$orientation", - getResourceValue(orientation, 12) + "${Settings.PREF_BUTTON_R}-X$layout", + getResourceValue(layout, 16) ) .putFloat( - ButtonType.DPAD_UP.toString() + "-Y$orientation", - getResourceValue(orientation, 13) + "${Settings.PREF_BUTTON_R}-Y$layout", + getResourceValue(layout, 17) ) .putFloat( - ButtonType.TRIGGER_L.toString() + "-X$orientation", - getResourceValue(orientation, 14) + "${Settings.PREF_BUTTON_PLUS}-X$layout", + getResourceValue(layout, 18) ) .putFloat( - ButtonType.TRIGGER_L.toString() + "-Y$orientation", - getResourceValue(orientation, 15) + "${Settings.PREF_BUTTON_PLUS}-Y$layout", + getResourceValue(layout, 19) ) .putFloat( - ButtonType.TRIGGER_R.toString() + "-X$orientation", - getResourceValue(orientation, 16) + "${Settings.PREF_BUTTON_MINUS}-X$layout", + getResourceValue(layout, 20) ) .putFloat( - ButtonType.TRIGGER_R.toString() + "-Y$orientation", - getResourceValue(orientation, 17) + "${Settings.PREF_BUTTON_MINUS}-Y$layout", + getResourceValue(layout, 21) ) .putFloat( - ButtonType.BUTTON_PLUS.toString() + "-X$orientation", - getResourceValue(orientation, 18) + "${Settings.PREF_BUTTON_HOME}-X$layout", + getResourceValue(layout, 22) ) .putFloat( - ButtonType.BUTTON_PLUS.toString() + "-Y$orientation", - getResourceValue(orientation, 19) + "${Settings.PREF_BUTTON_HOME}-Y$layout", + getResourceValue(layout, 23) ) .putFloat( - ButtonType.BUTTON_MINUS.toString() + "-X$orientation", - getResourceValue(orientation, 20) + "${Settings.PREF_BUTTON_SCREENSHOT}-X$layout", + getResourceValue(layout, 24) ) .putFloat( - ButtonType.BUTTON_MINUS.toString() + "-Y$orientation", - getResourceValue(orientation, 21) + "${Settings.PREF_BUTTON_SCREENSHOT}-Y$layout", + getResourceValue(layout, 25) ) .putFloat( - ButtonType.BUTTON_HOME.toString() + "-X$orientation", - getResourceValue(orientation, 22) + "${Settings.PREF_STICK_R}-X$layout", + getResourceValue(layout, 26) ) .putFloat( - ButtonType.BUTTON_HOME.toString() + "-Y$orientation", - getResourceValue(orientation, 23) + "${Settings.PREF_STICK_R}-Y$layout", + getResourceValue(layout, 27) ) .putFloat( - ButtonType.BUTTON_CAPTURE.toString() + "-X$orientation", - getResourceValue(orientation, 24) + "${Settings.PREF_STICK_L}-X$layout", + getResourceValue(layout, 28) ) .putFloat( - ButtonType.BUTTON_CAPTURE.toString() + "-Y$orientation", - getResourceValue(orientation, 25) + "${Settings.PREF_STICK_L}-Y$layout", + getResourceValue(layout, 29) ) .putFloat( - ButtonType.STICK_R.toString() + "-X$orientation", - getResourceValue(orientation, 26) + "${Settings.PREF_BUTTON_STICK_L}-X$layout", + getResourceValue(layout, 30) ) .putFloat( - ButtonType.STICK_R.toString() + "-Y$orientation", - getResourceValue(orientation, 27) + "${Settings.PREF_BUTTON_STICK_L}-Y$layout", + getResourceValue(layout, 31) ) .putFloat( - ButtonType.STICK_L.toString() + "-X$orientation", - getResourceValue(orientation, 28) + "${Settings.PREF_BUTTON_STICK_R}-X$layout", + getResourceValue(layout, 32) ) .putFloat( - ButtonType.STICK_L.toString() + "-Y$orientation", - getResourceValue(orientation, 29) + "${Settings.PREF_BUTTON_STICK_R}-Y$layout", + getResourceValue(layout, 33) ) .apply() } @@ -812,12 +900,30 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : } companion object { + // Increase this number every time there is a breaking change to every overlay layout + const val OVERLAY_VERSION = 1 + + // Increase the corresponding layout version number whenever that layout has a breaking change + private const val LANDSCAPE_OVERLAY_VERSION = 1 + private const val PORTRAIT_OVERLAY_VERSION = 1 + private const val FOLDABLE_OVERLAY_VERSION = 1 + val overlayLayoutVersions = listOf( + LANDSCAPE_OVERLAY_VERSION, + PORTRAIT_OVERLAY_VERSION, + FOLDABLE_OVERLAY_VERSION + ) + private val preferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) - const val LANDSCAPE = "" + const val LANDSCAPE = "_Landscape" const val PORTRAIT = "_Portrait" const val FOLDABLE = "_Foldable" + val overlayLayouts = listOf( + LANDSCAPE, + PORTRAIT, + FOLDABLE + ) /** * Resizes a [Bitmap] by a given scale factor @@ -948,6 +1054,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : * @param defaultResId The resource ID of the [Drawable] to get the [Bitmap] of (Default State). * @param pressedResId The resource ID of the [Drawable] to get the [Bitmap] of (Pressed State). * @param buttonId Identifier for determining what type of button the initialized InputOverlayDrawableButton represents. + * @param prefId Identifier for determining where a button appears on screen. + * @param layout The current screen layout as determined by [LANDSCAPE], [PORTRAIT], or [FOLDABLE]. * @return An [InputOverlayDrawableButton] with the correct drawing bounds set. */ private fun initializeOverlayButton( @@ -956,7 +1064,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : defaultResId: Int, pressedResId: Int, buttonId: Int, - orientation: String + prefId: String, + layout: String ): InputOverlayDrawableButton { // Resources handle for fetching the initial Drawable resource. val res = context.resources @@ -964,17 +1073,20 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : // SharedPreference to retrieve the X and Y coordinates for the InputOverlayDrawableButton. val sPrefs = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) - // Decide scale based on button ID and user preference - var scale: Float = when (buttonId) { - ButtonType.BUTTON_HOME, - ButtonType.BUTTON_CAPTURE, - ButtonType.BUTTON_PLUS, - ButtonType.BUTTON_MINUS -> 0.07f + // Decide scale based on button preference ID and user preference + var scale: Float = when (prefId) { + Settings.PREF_BUTTON_HOME, + Settings.PREF_BUTTON_SCREENSHOT, + Settings.PREF_BUTTON_PLUS, + Settings.PREF_BUTTON_MINUS -> 0.07f - ButtonType.TRIGGER_L, - ButtonType.TRIGGER_R, - ButtonType.TRIGGER_ZL, - ButtonType.TRIGGER_ZR -> 0.26f + Settings.PREF_BUTTON_L, + Settings.PREF_BUTTON_R, + Settings.PREF_BUTTON_ZL, + Settings.PREF_BUTTON_ZR -> 0.26f + + Settings.PREF_BUTTON_STICK_L, + Settings.PREF_BUTTON_STICK_R -> 0.155f else -> 0.11f } @@ -984,8 +1096,13 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : // Initialize the InputOverlayDrawableButton. val defaultStateBitmap = getBitmap(context, defaultResId, scale) val pressedStateBitmap = getBitmap(context, pressedResId, scale) - val overlayDrawable = - InputOverlayDrawableButton(res, defaultStateBitmap, pressedStateBitmap, buttonId) + val overlayDrawable = InputOverlayDrawableButton( + res, + defaultStateBitmap, + pressedStateBitmap, + buttonId, + prefId + ) // Get the minimum and maximum coordinates of the screen where the button can be placed. val min = windowSize.first @@ -993,8 +1110,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : // The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay. // These were set in the input overlay configuration menu. - val xKey = "$buttonId-X$orientation" - val yKey = "$buttonId-Y$orientation" + val xKey = "$prefId-X$layout" + val yKey = "$prefId-Y$layout" val drawableXPercent = sPrefs.getFloat(xKey, 0f) val drawableYPercent = sPrefs.getFloat(yKey, 0f) val drawableX = (drawableXPercent * max.x + min.x).toInt() @@ -1029,7 +1146,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : * @param defaultResId The [Bitmap] resource ID of the default state. * @param pressedOneDirectionResId The [Bitmap] resource ID of the pressed state in one direction. * @param pressedTwoDirectionsResId The [Bitmap] resource ID of the pressed state in two directions. - * @return the initialized [InputOverlayDrawableDpad] + * @param layout The current screen layout as determined by [LANDSCAPE], [PORTRAIT], or [FOLDABLE]. + * @return The initialized [InputOverlayDrawableDpad] */ private fun initializeOverlayDpad( context: Context, @@ -1037,7 +1155,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : defaultResId: Int, pressedOneDirectionResId: Int, pressedTwoDirectionsResId: Int, - orientation: String + layout: String ): InputOverlayDrawableDpad { // Resources handle for fetching the initial Drawable resource. val res = context.resources @@ -1074,8 +1192,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : // The X and Y coordinates of the InputOverlayDrawableDpad on the InputOverlay. // These were set in the input overlay configuration menu. - val drawableXPercent = sPrefs.getFloat("${ButtonType.DPAD_UP}-X$orientation", 0f) - val drawableYPercent = sPrefs.getFloat("${ButtonType.DPAD_UP}-Y$orientation", 0f) + val drawableXPercent = sPrefs.getFloat("${Settings.PREF_BUTTON_DPAD}-X$layout", 0f) + val drawableYPercent = sPrefs.getFloat("${Settings.PREF_BUTTON_DPAD}-Y$layout", 0f) val drawableX = (drawableXPercent * max.x + min.x).toInt() val drawableY = (drawableYPercent * max.y + min.y).toInt() val width = overlayDrawable.width @@ -1107,7 +1225,9 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : * @param pressedResInner Resource ID for the pressed inner image of the joystick. * @param joystick Identifier for which joystick this is. * @param button Identifier for which joystick button this is. - * @return the initialized [InputOverlayDrawableJoystick]. + * @param prefId Identifier for determining where a button appears on screen. + * @param layout The current screen layout as determined by [LANDSCAPE], [PORTRAIT], or [FOLDABLE]. + * @return The initialized [InputOverlayDrawableJoystick]. */ private fun initializeOverlayJoystick( context: Context, @@ -1117,7 +1237,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : pressedResInner: Int, joystick: Int, button: Int, - orientation: String + prefId: String, + layout: String ): InputOverlayDrawableJoystick { // Resources handle for fetching the initial Drawable resource. val res = context.resources @@ -1141,8 +1262,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : // The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay. // These were set in the input overlay configuration menu. - val drawableXPercent = sPrefs.getFloat("$button-X$orientation", 0f) - val drawableYPercent = sPrefs.getFloat("$button-Y$orientation", 0f) + val drawableXPercent = sPrefs.getFloat("$prefId-X$layout", 0f) + val drawableYPercent = sPrefs.getFloat("$prefId-Y$layout", 0f) val drawableX = (drawableXPercent * max.x + min.x).toInt() val drawableY = (drawableYPercent * max.y + min.y).toInt() val outerScale = 1.66f @@ -1168,7 +1289,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : outerRect, innerRect, joystick, - button + button, + prefId ) // Need to set the image's position diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableButton.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableButton.kt index 4a93e0b14..2c28dda88 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableButton.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableButton.kt @@ -24,7 +24,8 @@ class InputOverlayDrawableButton( res: Resources, defaultStateBitmap: Bitmap, pressedStateBitmap: Bitmap, - val buttonId: Int + val buttonId: Int, + val prefId: String ) { // The ID value what motion event is tracking var trackId: Int diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt index fb48f584d..518b1e783 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt @@ -37,7 +37,8 @@ class InputOverlayDrawableJoystick( rectOuter: Rect, rectInner: Rect, val joystickId: Int, - val buttonId: Int + val buttonId: Int, + val prefId: String ) { // The ID value what motion event is tracking var trackId = -1 diff --git a/src/android/app/src/main/res/drawable/button_l3.xml b/src/android/app/src/main/res/drawable/button_l3.xml new file mode 100644 index 000000000..0cb28836e --- /dev/null +++ b/src/android/app/src/main/res/drawable/button_l3.xml @@ -0,0 +1,128 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt" + android:width="34.963dp" + android:height="37.265dp" + android:viewportWidth="34.963" + android:viewportHeight="37.265"> + <path + android:fillAlpha="0.5" + android:pathData="M19.451,19.024A3.498,3.498 0,0 0,21.165 19.508c1.336,0 1.749,-0.852 1.738,-1.49 0,-1.077 -0.982,-1.537 -1.987,-1.537L20.327,16.481L20.327,15.7L20.901,15.7c0.757,0 1.714,-0.392 1.714,-1.302C22.621,13.785 22.224,13.229 21.271,13.229a2.834,2.834 0,0 0,-1.537 0.529l-0.265,-0.757a3.662,3.662 0,0 1,2.008 -0.59c1.513,0 2.201,0.897 2.201,1.834 0,0.794 -0.474,1.466 -1.421,1.807l0,0.024c0.947,0.19 1.714,0.9 1.714,1.976C23.967,19.27 23.017,20.346 21.165,20.346a3.929,3.929 135,0 1,-1.998 -0.529z" + android:strokeAlpha="0.6"> + <aapt:attr name="android:fillColor"> + <gradient + android:endX="21.568" + android:endY="33.938" + android:startX="21.568" + android:startY="16.14" + android:type="linear"> + <item + android:color="#FFC3C4C5" + android:offset="0" /> + <item + android:color="#FFC5C6C6" + android:offset="0.03" /> + <item + android:color="#FFC7C7C7" + android:offset="0.19" /> + <item + android:color="#DBB5B5B5" + android:offset="0.44" /> + <item + android:color="#7F878787" + android:offset="1" /> + </gradient> + </aapt:attr> + </path> + <path + android:fillAlpha="0.5" + android:pathData="M16.062,9.353 L9.624,3.405A1.963,1.963 0,0 1,10.955 0l12.88,0a1.963,1.963 135,0 1,1.323 3.405L18.726,9.353a1.961,1.961 135,0 1,-2.664 0z" + android:strokeAlpha="0.6"> + <aapt:attr name="android:fillColor"> + <gradient + android:endX="17.395" + android:endY="18.74" + android:startX="17.395" + android:startY="-1.296" + android:type="linear"> + <item + android:color="#FFC3C4C5" + android:offset="0" /> + <item + android:color="#FFC5C6C6" + android:offset="0.03" /> + <item + android:color="#FFC7C7C7" + android:offset="0.19" /> + <item + android:color="#DBB5B5B5" + android:offset="0.44" /> + <item + android:color="#7F878787" + android:offset="1" /> + </gradient> + </aapt:attr> + </path> + <path + android:fillAlpha="0.5" + android:pathData="m25.79,5.657l0,0a2.09,2.09 45,0 0,0.23 3.262c3.522,2.402 4.762,5.927 4.741,10.52A13.279,13.279 135,0 1,4.206 19.365c0,-4.516 0.931,-7.71 4.374,-10.107a2.098,2.098 0,0 0,0.233 -3.265l0,0a2.101,2.101 135,0 0,-2.646 -0.169C1.433,9.133 -0.266,13.941 0.033,20.233a17.468,17.468 0,0 0,34.925 -0.868c0,-6.006 -1.971,-10.771 -6.585,-13.917a2.088,2.088 45,0 0,-2.582 0.209z" + android:strokeAlpha="0.6"> + <aapt:attr name="android:fillColor"> + <gradient + android:centerX="17.477" + android:centerY="19.92" + android:gradientRadius="17.201" + android:type="radial"> + <item + android:color="#FFC3C4C5" + android:offset="0.58" /> + <item + android:color="#FFC6C6C6" + android:offset="0.84" /> + <item + android:color="#FFC7C7C7" + android:offset="0.88" /> + <item + android:color="#FFC2C2C2" + android:offset="0.91" /> + <item + android:color="#FFB5B5B5" + android:offset="0.94" /> + <item + android:color="#FF9E9E9E" + android:offset="0.98" /> + <item + android:color="#FF8F8F8F" + android:offset="1" /> + </gradient> + </aapt:attr> + </path> + <path + android:fillAlpha="0.5" + android:pathData="m12.516,12.729l2,0l0,13.822l6.615,0l0,1.68L12.516,28.231Z" + android:strokeAlpha="0.6"> + <aapt:attr name="android:fillColor"> + <gradient + android:endX="16.829" + android:endY="46.882" + android:startX="16.829" + android:startY="20.479" + android:type="linear"> + <item + android:color="#FFC3C4C5" + android:offset="0" /> + <item + android:color="#FFC5C6C6" + android:offset="0.03" /> + <item + android:color="#FFC7C7C7" + android:offset="0.19" /> + <item + android:color="#DBB5B5B5" + android:offset="0.44" /> + <item + android:color="#7F878787" + android:offset="1" /> + </gradient> + </aapt:attr> + </path> +</vector> diff --git a/src/android/app/src/main/res/drawable/button_l3_depressed.xml b/src/android/app/src/main/res/drawable/button_l3_depressed.xml new file mode 100644 index 000000000..b078dedc9 --- /dev/null +++ b/src/android/app/src/main/res/drawable/button_l3_depressed.xml @@ -0,0 +1,75 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt" + android:width="34.963dp" + android:height="37.265dp" + android:viewportWidth="34.963" + android:viewportHeight="37.265"> + <path + android:fillAlpha="0.3" + android:fillColor="#151515" + android:pathData="M16.062,9.353 L9.624,3.405A1.963,1.963 0,0 1,10.955 0l12.88,0a1.963,1.963 135,0 1,1.323 3.405L18.726,9.353a1.961,1.961 135,0 1,-2.664 0z" + android:strokeAlpha="0.3" /> + <path + android:fillAlpha="0.6" + android:fillColor="#151515" + android:pathData="m25.79,5.657l0,0a2.09,2.09 45,0 0,0.23 3.262c3.522,2.402 4.762,5.927 4.741,10.52A13.279,13.279 135,0 1,4.206 19.365c0,-4.516 0.931,-7.71 4.374,-10.107a2.098,2.098 0,0 0,0.233 -3.265l0,0a2.101,2.101 135,0 0,-2.646 -0.169C1.433,9.133 -0.266,13.941 0.033,20.233a17.468,17.468 0,0 0,34.925 -0.868c0,-6.006 -1.971,-10.771 -6.585,-13.917a2.088,2.088 45,0 0,-2.582 0.209z" + android:strokeAlpha="0.6" /> + <path + android:fillAlpha="0.6" + android:pathData="M19.451,19.024A3.498,3.498 0,0 0,21.165 19.508c1.336,0 1.749,-0.852 1.738,-1.49 0,-1.077 -0.982,-1.537 -1.987,-1.537L20.327,16.481L20.327,15.7L20.901,15.7c0.757,0 1.714,-0.392 1.714,-1.302C22.621,13.785 22.224,13.229 21.271,13.229a2.834,2.834 0,0 0,-1.537 0.529l-0.265,-0.757a3.662,3.662 0,0 1,2.008 -0.59c1.513,0 2.201,0.897 2.201,1.834 0,0.794 -0.474,1.466 -1.421,1.807l0,0.024c0.947,0.19 1.714,0.9 1.714,1.976C23.967,19.27 23.017,20.346 21.165,20.346a3.929,3.929 135,0 1,-1.998 -0.529z" + android:strokeAlpha="0.6"> + <aapt:attr name="android:fillColor"> + <gradient + android:endX="21.568" + android:endY="33.938" + android:startX="21.568" + android:startY="16.14" + android:type="linear"> + <item + android:color="#FFC3C4C5" + android:offset="0" /> + <item + android:color="#FFC5C6C6" + android:offset="0.03" /> + <item + android:color="#FFC7C7C7" + android:offset="0.19" /> + <item + android:color="#DBB5B5B5" + android:offset="0.44" /> + <item + android:color="#7F878787" + android:offset="1" /> + </gradient> + </aapt:attr> + </path> + <path + android:fillAlpha="0.6" + android:pathData="m12.516,12.729l2,0l0,13.822l6.615,0l0,1.68L12.516,28.231Z" + android:strokeAlpha="0.6"> + <aapt:attr name="android:fillColor"> + <gradient + android:endX="16.829" + android:endY="46.882" + android:startX="16.829" + android:startY="20.479" + android:type="linear"> + <item + android:color="#FFC3C4C5" + android:offset="0" /> + <item + android:color="#FFC5C6C6" + android:offset="0.03" /> + <item + android:color="#FFC7C7C7" + android:offset="0.19" /> + <item + android:color="#DBB5B5B5" + android:offset="0.44" /> + <item + android:color="#7F878787" + android:offset="1" /> + </gradient> + </aapt:attr> + </path> +</vector> diff --git a/src/android/app/src/main/res/drawable/button_r3.xml b/src/android/app/src/main/res/drawable/button_r3.xml new file mode 100644 index 000000000..5c6864e26 --- /dev/null +++ b/src/android/app/src/main/res/drawable/button_r3.xml @@ -0,0 +1,128 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt" + android:width="34.963dp" + android:height="37.265dp" + android:viewportWidth="34.963" + android:viewportHeight="37.265"> + <path + android:fillAlpha="0.5" + android:pathData="m10.781,12.65a19.579,19.579 0,0 1,3.596 -0.302c2.003,0 3.294,0.368 4.199,1.185a3.622,3.622 0,0 1,1.14 2.757c0,1.916 -1.206,3.175 -2.733,3.704l0,0.063c1.119,0.386 1.786,1.421 2.117,2.929 0.474,2.024 0.818,3.424 1.119,3.982l-1.924,0c-0.238,-0.407 -0.561,-1.656 -0.968,-3.466 -0.431,-2.003 -1.206,-2.757 -2.91,-2.82l-1.762,0l0,6.286l-1.873,0zM12.654,19.264l1.916,0c2.003,0 3.273,-1.098 3.273,-2.757 0,-1.873 -1.357,-2.691 -3.336,-2.712a7.649,7.649 0,0 0,-1.852 0.172z" + android:strokeAlpha="0.6"> + <aapt:attr name="android:fillColor"> + <gradient + android:endX="15.506" + android:endY="48.977" + android:startX="15.506" + android:startY="19.659" + android:type="linear"> + <item + android:color="#FFC3C4C5" + android:offset="0" /> + <item + android:color="#FFC5C6C6" + android:offset="0.03" /> + <item + android:color="#FFC7C7C7" + android:offset="0.19" /> + <item + android:color="#DBB5B5B5" + android:offset="0.44" /> + <item + android:color="#7F878787" + android:offset="1" /> + </gradient> + </aapt:attr> + </path> + <path + android:fillAlpha="0.5" + android:pathData="M16.062,9.353 L9.624,3.405A1.963,1.963 0,0 1,10.955 0l12.88,0a1.963,1.963 135,0 1,1.323 3.405L18.726,9.353a1.961,1.961 135,0 1,-2.664 0z" + android:strokeAlpha="0.6"> + <aapt:attr name="android:fillColor"> + <gradient + android:endX="17.395" + android:endY="18.74" + android:startX="17.395" + android:startY="-1.296" + android:type="linear"> + <item + android:color="#FFC3C4C5" + android:offset="0" /> + <item + android:color="#FFC5C6C6" + android:offset="0.03" /> + <item + android:color="#FFC7C7C7" + android:offset="0.19" /> + <item + android:color="#DBB5B5B5" + android:offset="0.44" /> + <item + android:color="#7F878787" + android:offset="1" /> + </gradient> + </aapt:attr> + </path> + <path + android:fillAlpha="0.5" + android:pathData="m25.79,5.657l0,0a2.09,2.09 45,0 0,0.23 3.262c3.522,2.402 4.762,5.927 4.741,10.52A13.279,13.279 135,0 1,4.206 19.365c0,-4.516 0.931,-7.71 4.374,-10.107a2.098,2.098 0,0 0,0.233 -3.265l0,0a2.101,2.101 135,0 0,-2.646 -0.169C1.433,9.133 -0.266,13.941 0.033,20.233a17.468,17.468 0,0 0,34.925 -0.868c0,-6.006 -1.971,-10.771 -6.585,-13.917a2.088,2.088 45,0 0,-2.582 0.209z" + android:strokeAlpha="0.6"> + <aapt:attr name="android:fillColor"> + <gradient + android:centerX="17.477" + android:centerY="19.92" + android:gradientRadius="17.201" + android:type="radial"> + <item + android:color="#FFC3C4C5" + android:offset="0.58" /> + <item + android:color="#FFC6C6C6" + android:offset="0.84" /> + <item + android:color="#FFC7C7C7" + android:offset="0.88" /> + <item + android:color="#FFC2C2C2" + android:offset="0.91" /> + <item + android:color="#FFB5B5B5" + android:offset="0.94" /> + <item + android:color="#FF9E9E9E" + android:offset="0.98" /> + <item + android:color="#FF8F8F8F" + android:offset="1" /> + </gradient> + </aapt:attr> + </path> + <path + android:fillAlpha="0.5" + android:pathData="M21.832,19.024A3.498,3.498 0,0 0,23.547 19.508c1.336,0 1.749,-0.852 1.738,-1.49 0,-1.077 -0.982,-1.537 -1.987,-1.537L22.708,16.481L22.708,15.7L23.282,15.7c0.757,0 1.714,-0.392 1.714,-1.302C25.002,13.785 24.605,13.229 23.652,13.229a2.834,2.834 0,0 0,-1.537 0.529l-0.265,-0.757a3.662,3.662 0,0 1,2.008 -0.59c1.513,0 2.201,0.897 2.201,1.834 0,0.794 -0.474,1.466 -1.421,1.807l0,0.024c0.947,0.19 1.714,0.9 1.714,1.976C26.349,19.27 25.399,20.346 23.547,20.346a3.929,3.929 135,0 1,-1.998 -0.529z" + android:strokeAlpha="0.6"> + <aapt:attr name="android:fillColor"> + <gradient + android:endX="23.949" + android:endY="33.938" + android:startX="23.949" + android:startY="16.14" + android:type="linear"> + <item + android:color="#FFC3C4C5" + android:offset="0" /> + <item + android:color="#FFC5C6C6" + android:offset="0.03" /> + <item + android:color="#FFC7C7C7" + android:offset="0.19" /> + <item + android:color="#DBB5B5B5" + android:offset="0.44" /> + <item + android:color="#7F878787" + android:offset="1" /> + </gradient> + </aapt:attr> + </path> +</vector> diff --git a/src/android/app/src/main/res/drawable/button_r3_depressed.xml b/src/android/app/src/main/res/drawable/button_r3_depressed.xml new file mode 100644 index 000000000..20f480179 --- /dev/null +++ b/src/android/app/src/main/res/drawable/button_r3_depressed.xml @@ -0,0 +1,75 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt" + android:width="34.963dp" + android:height="37.265dp" + android:viewportWidth="34.963" + android:viewportHeight="37.265"> + <path + android:fillAlpha="0.3" + android:fillColor="#151515" + android:pathData="M16.062,9.353 L9.624,3.405A1.963,1.963 0,0 1,10.955 0l12.88,0a1.963,1.963 135,0 1,1.323 3.405L18.726,9.353a1.961,1.961 135,0 1,-2.664 0z" + android:strokeAlpha="0.3" /> + <path + android:fillAlpha="0.6" + android:fillColor="#151515" + android:pathData="m25.79,5.657l0,0a2.09,2.09 45,0 0,0.23 3.262c3.522,2.402 4.762,5.927 4.741,10.52A13.279,13.279 135,0 1,4.206 19.365c0,-4.516 0.931,-7.71 4.374,-10.107a2.098,2.098 0,0 0,0.233 -3.265l0,0a2.101,2.101 135,0 0,-2.646 -0.169C1.433,9.133 -0.266,13.941 0.033,20.233a17.468,17.468 0,0 0,34.925 -0.868c0,-6.006 -1.971,-10.771 -6.585,-13.917a2.088,2.088 45,0 0,-2.582 0.209z" + android:strokeAlpha="0.6" /> + <path + android:fillAlpha="0.6" + android:pathData="m10.781,12.65a19.579,19.579 0,0 1,3.596 -0.302c2.003,0 3.294,0.368 4.199,1.185a3.622,3.622 0,0 1,1.14 2.757c0,1.916 -1.206,3.175 -2.733,3.704l0,0.063c1.119,0.386 1.786,1.421 2.117,2.929 0.474,2.024 0.818,3.424 1.119,3.982l-1.924,0c-0.238,-0.407 -0.561,-1.656 -0.968,-3.466 -0.431,-2.003 -1.206,-2.757 -2.91,-2.82l-1.762,0l0,6.286l-1.873,0zM12.654,19.264l1.916,0c2.003,0 3.273,-1.098 3.273,-2.757 0,-1.873 -1.357,-2.691 -3.336,-2.712a7.649,7.649 0,0 0,-1.852 0.172z" + android:strokeAlpha="0.6"> + <aapt:attr name="android:fillColor"> + <gradient + android:endX="15.506" + android:endY="48.977" + android:startX="15.506" + android:startY="19.659" + android:type="linear"> + <item + android:color="#FFC3C4C5" + android:offset="0" /> + <item + android:color="#FFC5C6C6" + android:offset="0.03" /> + <item + android:color="#FFC7C7C7" + android:offset="0.19" /> + <item + android:color="#DBB5B5B5" + android:offset="0.44" /> + <item + android:color="#7F878787" + android:offset="1" /> + </gradient> + </aapt:attr> + </path> + <path + android:fillAlpha="0.6" + android:pathData="M21.832,19.024A3.498,3.498 0,0 0,23.547 19.508c1.336,0 1.749,-0.852 1.738,-1.49 0,-1.077 -0.982,-1.537 -1.987,-1.537L22.708,16.481L22.708,15.7L23.282,15.7c0.757,0 1.714,-0.392 1.714,-1.302C25.002,13.785 24.605,13.229 23.652,13.229a2.834,2.834 0,0 0,-1.537 0.529l-0.265,-0.757a3.662,3.662 0,0 1,2.008 -0.59c1.513,0 2.201,0.897 2.201,1.834 0,0.794 -0.474,1.466 -1.421,1.807l0,0.024c0.947,0.19 1.714,0.9 1.714,1.976C26.349,19.27 25.399,20.346 23.547,20.346a3.929,3.929 135,0 1,-1.998 -0.529z" + android:strokeAlpha="0.6"> + <aapt:attr name="android:fillColor"> + <gradient + android:endX="23.949" + android:endY="33.938" + android:startX="23.949" + android:startY="16.14" + android:type="linear"> + <item + android:color="#FFC3C4C5" + android:offset="0" /> + <item + android:color="#FFC5C6C6" + android:offset="0.03" /> + <item + android:color="#FFC7C7C7" + android:offset="0.19" /> + <item + android:color="#DBB5B5B5" + android:offset="0.44" /> + <item + android:color="#7F878787" + android:offset="1" /> + </gradient> + </aapt:attr> + </path> +</vector> diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml index 6d092f7a9..200b99185 100644 --- a/src/android/app/src/main/res/values/arrays.xml +++ b/src/android/app/src/main/res/values/arrays.xml @@ -205,6 +205,8 @@ <item>@string/gamepad_d_pad</item> <item>@string/gamepad_left_stick</item> <item>@string/gamepad_right_stick</item> + <item>L3</item> + <item>R3</item> <item>@string/gamepad_home</item> <item>@string/gamepad_screenshot</item> </string-array> diff --git a/src/android/app/src/main/res/values/integers.xml b/src/android/app/src/main/res/values/integers.xml index 2e93b408c..5e39bc7d9 100644 --- a/src/android/app/src/main/res/values/integers.xml +++ b/src/android/app/src/main/res/values/integers.xml @@ -33,6 +33,10 @@ <integer name="SWITCH_BUTTON_CAPTURE_Y">950</integer> <integer name="SWITCH_BUTTON_DPAD_X">260</integer> <integer name="SWITCH_BUTTON_DPAD_Y">790</integer> + <integer name="SWITCH_BUTTON_STICK_L_X">870</integer> + <integer name="SWITCH_BUTTON_STICK_L_Y">400</integer> + <integer name="SWITCH_BUTTON_STICK_R_X">960</integer> + <integer name="SWITCH_BUTTON_STICK_R_Y">430</integer> <!-- Default SWITCH portrait layout --> <integer name="SWITCH_BUTTON_A_X_PORTRAIT">840</integer> @@ -65,6 +69,10 @@ <integer name="SWITCH_BUTTON_CAPTURE_Y_PORTRAIT">950</integer> <integer name="SWITCH_BUTTON_DPAD_X_PORTRAIT">240</integer> <integer name="SWITCH_BUTTON_DPAD_Y_PORTRAIT">840</integer> + <integer name="SWITCH_BUTTON_STICK_L_X_PORTRAIT">730</integer> + <integer name="SWITCH_BUTTON_STICK_L_Y_PORTRAIT">510</integer> + <integer name="SWITCH_BUTTON_STICK_R_X_PORTRAIT">900</integer> + <integer name="SWITCH_BUTTON_STICK_R_Y_PORTRAIT">540</integer> <!-- Default SWITCH foldable layout --> <integer name="SWITCH_BUTTON_A_X_FOLDABLE">840</integer> @@ -97,5 +105,9 @@ <integer name="SWITCH_BUTTON_CAPTURE_Y_FOLDABLE">470</integer> <integer name="SWITCH_BUTTON_DPAD_X_FOLDABLE">240</integer> <integer name="SWITCH_BUTTON_DPAD_Y_FOLDABLE">390</integer> + <integer name="SWITCH_BUTTON_STICK_L_X_FOLDABLE">550</integer> + <integer name="SWITCH_BUTTON_STICK_L_Y_FOLDABLE">210</integer> + <integer name="SWITCH_BUTTON_STICK_R_X_FOLDABLE">550</integer> + <integer name="SWITCH_BUTTON_STICK_R_Y_FOLDABLE">280</integer> </resources> diff --git a/src/common/settings.cpp b/src/common/settings.cpp index 6cbbea1b2..5972480e5 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -27,8 +27,8 @@ std::string GetTimeZoneString() { std::string location_name; if (time_zone_index == 0) { // Auto #if __cpp_lib_chrono >= 201907L - const struct std::chrono::tzdb& time_zone_data = std::chrono::get_tzdb(); try { + const struct std::chrono::tzdb& time_zone_data = std::chrono::get_tzdb(); const std::chrono::time_zone* current_zone = time_zone_data.current_zone(); std::string_view current_zone_name = current_zone->name(); location_name = current_zone_name; diff --git a/src/core/arm/arm_interface.cpp b/src/core/arm/arm_interface.cpp index beaea64b3..aa0eb9791 100644 --- a/src/core/arm/arm_interface.cpp +++ b/src/core/arm/arm_interface.cpp @@ -185,7 +185,7 @@ void ARM_Interface::Run() { // Notify the debugger and go to sleep if a breakpoint was hit, // or if the thread is unable to continue for any reason. if (True(hr & HaltReason::InstructionBreakpoint) || True(hr & HaltReason::PrefetchAbort)) { - if (!True(hr & HaltReason::InstructionBreakpoint)) { + if (!True(hr & HaltReason::PrefetchAbort)) { RewindBreakpointInstruction(); } if (system.DebuggerEnabled()) { diff --git a/src/core/file_sys/vfs_real.cpp b/src/core/file_sys/vfs_real.cpp index b0515ec05..1c706e4d8 100644 --- a/src/core/file_sys/vfs_real.cpp +++ b/src/core/file_sys/vfs_real.cpp @@ -283,7 +283,8 @@ std::size_t RealVfsFile::GetSize() const { if (size) { return *size; } - return FS::GetSize(path); + auto lk = base.RefreshReference(path, perms, *reference); + return reference->file ? reference->file->GetSize() : 0; } bool RealVfsFile::Resize(std::size_t new_size) { diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp index 1ebc32c1e..94bd656fe 100644 --- a/src/core/hid/emulated_controller.cpp +++ b/src/core/hid/emulated_controller.cpp @@ -1243,10 +1243,12 @@ Common::Input::DriverResult EmulatedController::SetPollingMode( auto& nfc_output_device = output_devices[3]; if (device_index == EmulatedDeviceIndex::LeftIndex) { + controller.left_polling_mode = polling_mode; return left_output_device->SetPollingMode(polling_mode); } if (device_index == EmulatedDeviceIndex::RightIndex) { + controller.right_polling_mode = polling_mode; const auto virtual_nfc_result = nfc_output_device->SetPollingMode(polling_mode); const auto mapped_nfc_result = right_output_device->SetPollingMode(polling_mode); @@ -1261,12 +1263,22 @@ Common::Input::DriverResult EmulatedController::SetPollingMode( return mapped_nfc_result; } + controller.left_polling_mode = polling_mode; + controller.right_polling_mode = polling_mode; left_output_device->SetPollingMode(polling_mode); right_output_device->SetPollingMode(polling_mode); nfc_output_device->SetPollingMode(polling_mode); return Common::Input::DriverResult::Success; } +Common::Input::PollingMode EmulatedController::GetPollingMode( + EmulatedDeviceIndex device_index) const { + if (device_index == EmulatedDeviceIndex::LeftIndex) { + return controller.left_polling_mode; + } + return controller.right_polling_mode; +} + bool EmulatedController::SetCameraFormat( Core::IrSensor::ImageTransferProcessorFormat camera_format) { LOG_INFO(Service_HID, "Set camera format {}", camera_format); diff --git a/src/core/hid/emulated_controller.h b/src/core/hid/emulated_controller.h index d511e5fac..88d77db8d 100644 --- a/src/core/hid/emulated_controller.h +++ b/src/core/hid/emulated_controller.h @@ -143,6 +143,8 @@ struct ControllerStatus { CameraState camera_state{}; RingSensorForce ring_analog_state{}; NfcState nfc_state{}; + Common::Input::PollingMode left_polling_mode{}; + Common::Input::PollingMode right_polling_mode{}; }; enum class ControllerTriggerType { @@ -370,6 +372,12 @@ public: */ Common::Input::DriverResult SetPollingMode(EmulatedDeviceIndex device_index, Common::Input::PollingMode polling_mode); + /** + * Get the current polling mode from a controller + * @param device_index index of the controller to set the polling mode + * @return current polling mode + */ + Common::Input::PollingMode GetPollingMode(EmulatedDeviceIndex device_index) const; /** * Sets the desired camera format to be polled from a controller diff --git a/src/core/hle/service/nfc/common/device.cpp b/src/core/hle/service/nfc/common/device.cpp index 5bf289818..2d633b03f 100644 --- a/src/core/hle/service/nfc/common/device.cpp +++ b/src/core/hle/service/nfc/common/device.cpp @@ -66,10 +66,6 @@ NfcDevice::~NfcDevice() { }; void NfcDevice::NpadUpdate(Core::HID::ControllerTriggerType type) { - if (!is_initalized) { - return; - } - if (type == Core::HID::ControllerTriggerType::Connected) { Initialize(); availability_change_event->Signal(); @@ -77,12 +73,12 @@ void NfcDevice::NpadUpdate(Core::HID::ControllerTriggerType type) { } if (type == Core::HID::ControllerTriggerType::Disconnected) { - device_state = DeviceState::Unavailable; + Finalize(); availability_change_event->Signal(); return; } - if (type != Core::HID::ControllerTriggerType::Nfc) { + if (!is_initalized) { return; } @@ -90,6 +86,17 @@ void NfcDevice::NpadUpdate(Core::HID::ControllerTriggerType type) { return; } + // Ensure nfc mode is always active + if (npad_device->GetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex) == + Common::Input::PollingMode::Active) { + npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex, + Common::Input::PollingMode::NFC); + } + + if (type != Core::HID::ControllerTriggerType::Nfc) { + return; + } + const auto nfc_status = npad_device->GetNfc(); switch (nfc_status.state) { case Common::Input::NfcState::NewAmiibo: @@ -207,11 +214,14 @@ void NfcDevice::Initialize() { } void NfcDevice::Finalize() { - if (device_state == DeviceState::TagMounted) { - Unmount(); - } - if (device_state == DeviceState::SearchingForTag || device_state == DeviceState::TagRemoved) { - StopDetection(); + if (npad_device->IsConnected()) { + if (device_state == DeviceState::TagMounted) { + Unmount(); + } + if (device_state == DeviceState::SearchingForTag || + device_state == DeviceState::TagRemoved) { + StopDetection(); + } } if (device_state != DeviceState::Unavailable) { diff --git a/src/input_common/drivers/mouse.cpp b/src/input_common/drivers/mouse.cpp index dac29c78f..9fb824baf 100644 --- a/src/input_common/drivers/mouse.cpp +++ b/src/input_common/drivers/mouse.cpp @@ -160,8 +160,9 @@ void Mouse::Move(int x, int y, int center_x, int center_y) { last_mouse_change.y += mouse_change.y * y_sensitivity; // Bind the mouse change to [0 <= deadzone_counterweight <= 1.0] - if (last_mouse_change.Length() < deadzone_counterweight) { - last_mouse_change /= last_mouse_change.Length(); + const float length = last_mouse_change.Length(); + if (length < deadzone_counterweight && length != 0.0f) { + last_mouse_change /= length; last_mouse_change *= deadzone_counterweight; } diff --git a/src/input_common/drivers/sdl_driver.cpp b/src/input_common/drivers/sdl_driver.cpp index 9f26392b1..66e3ae9af 100644 --- a/src/input_common/drivers/sdl_driver.cpp +++ b/src/input_common/drivers/sdl_driver.cpp @@ -523,6 +523,8 @@ SDLDriver::SDLDriver(std::string input_engine_) : InputEngine(std::move(input_en } SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_PLAYER_LED, "1"); + // Share the same button mapping with non-Nintendo controllers + SDL_SetHint(SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS, "0"); // Disable hidapi driver for xbox. Already default on Windows, this causes conflict with native // driver on Linux. @@ -800,16 +802,9 @@ ButtonMapping SDLDriver::GetButtonMappingForDevice(const Common::ParamPackage& p // This list is missing ZL/ZR since those are not considered buttons in SDL GameController. // We will add those afterwards - // This list also excludes Screenshot since there's not really a mapping for that ButtonBindings switch_to_sdl_button; - if (SDL_GameControllerGetType(controller) == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO || - SDL_GameControllerGetType(controller) == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_LEFT || - SDL_GameControllerGetType(controller) == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT) { - switch_to_sdl_button = GetNintendoButtonBinding(joystick); - } else { - switch_to_sdl_button = GetDefaultButtonBinding(); - } + switch_to_sdl_button = GetDefaultButtonBinding(joystick); // Add the missing bindings for ZL/ZR static constexpr ZButtonBindings switch_to_sdl_axis{{ @@ -830,32 +825,9 @@ ButtonMapping SDLDriver::GetButtonMappingForDevice(const Common::ParamPackage& p return GetSingleControllerMapping(joystick, switch_to_sdl_button, switch_to_sdl_axis); } -ButtonBindings SDLDriver::GetDefaultButtonBinding() const { - return { - std::pair{Settings::NativeButton::A, SDL_CONTROLLER_BUTTON_B}, - {Settings::NativeButton::B, SDL_CONTROLLER_BUTTON_A}, - {Settings::NativeButton::X, SDL_CONTROLLER_BUTTON_Y}, - {Settings::NativeButton::Y, SDL_CONTROLLER_BUTTON_X}, - {Settings::NativeButton::LStick, SDL_CONTROLLER_BUTTON_LEFTSTICK}, - {Settings::NativeButton::RStick, SDL_CONTROLLER_BUTTON_RIGHTSTICK}, - {Settings::NativeButton::L, SDL_CONTROLLER_BUTTON_LEFTSHOULDER}, - {Settings::NativeButton::R, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER}, - {Settings::NativeButton::Plus, SDL_CONTROLLER_BUTTON_START}, - {Settings::NativeButton::Minus, SDL_CONTROLLER_BUTTON_BACK}, - {Settings::NativeButton::DLeft, SDL_CONTROLLER_BUTTON_DPAD_LEFT}, - {Settings::NativeButton::DUp, SDL_CONTROLLER_BUTTON_DPAD_UP}, - {Settings::NativeButton::DRight, SDL_CONTROLLER_BUTTON_DPAD_RIGHT}, - {Settings::NativeButton::DDown, SDL_CONTROLLER_BUTTON_DPAD_DOWN}, - {Settings::NativeButton::SL, SDL_CONTROLLER_BUTTON_LEFTSHOULDER}, - {Settings::NativeButton::SR, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER}, - {Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE}, - {Settings::NativeButton::Screenshot, SDL_CONTROLLER_BUTTON_MISC1}, - }; -} - -ButtonBindings SDLDriver::GetNintendoButtonBinding( +ButtonBindings SDLDriver::GetDefaultButtonBinding( const std::shared_ptr<SDLJoystick>& joystick) const { - // Default SL/SR mapping for pro controllers + // Default SL/SR mapping for other controllers auto sl_button = SDL_CONTROLLER_BUTTON_LEFTSHOULDER; auto sr_button = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER; @@ -869,10 +841,10 @@ ButtonBindings SDLDriver::GetNintendoButtonBinding( } return { - std::pair{Settings::NativeButton::A, SDL_CONTROLLER_BUTTON_A}, - {Settings::NativeButton::B, SDL_CONTROLLER_BUTTON_B}, - {Settings::NativeButton::X, SDL_CONTROLLER_BUTTON_X}, - {Settings::NativeButton::Y, SDL_CONTROLLER_BUTTON_Y}, + std::pair{Settings::NativeButton::A, SDL_CONTROLLER_BUTTON_B}, + {Settings::NativeButton::B, SDL_CONTROLLER_BUTTON_A}, + {Settings::NativeButton::X, SDL_CONTROLLER_BUTTON_Y}, + {Settings::NativeButton::Y, SDL_CONTROLLER_BUTTON_X}, {Settings::NativeButton::LStick, SDL_CONTROLLER_BUTTON_LEFTSTICK}, {Settings::NativeButton::RStick, SDL_CONTROLLER_BUTTON_RIGHTSTICK}, {Settings::NativeButton::L, SDL_CONTROLLER_BUTTON_LEFTSHOULDER}, diff --git a/src/input_common/drivers/sdl_driver.h b/src/input_common/drivers/sdl_driver.h index ffde169b3..fcba4e3c6 100644 --- a/src/input_common/drivers/sdl_driver.h +++ b/src/input_common/drivers/sdl_driver.h @@ -100,11 +100,8 @@ private: int axis_y, float offset_x, float offset_y) const; - /// Returns the default button bindings list for generic controllers - ButtonBindings GetDefaultButtonBinding() const; - - /// Returns the default button bindings list for nintendo controllers - ButtonBindings GetNintendoButtonBinding(const std::shared_ptr<SDLJoystick>& joystick) const; + /// Returns the default button bindings list + ButtonBindings GetDefaultButtonBinding(const std::shared_ptr<SDLJoystick>& joystick) const; /// Returns the button mappings from a single controller ButtonMapping GetSingleControllerMapping(const std::shared_ptr<SDLJoystick>& joystick, diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index a1457798a..4457b366f 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -599,6 +599,10 @@ void TextureCache<P>::UnmapGPUMemory(size_t as_id, GPUVAddr gpu_addr, size_t siz [&](ImageId id, Image&) { deleted_images.push_back(id); }); for (const ImageId id : deleted_images) { Image& image = slot_images[id]; + if (True(image.flags & ImageFlagBits::CpuModified)) { + continue; + } + image.flags |= ImageFlagBits::CpuModified; if (True(image.flags & ImageFlagBits::Remapped)) { continue; } diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index 421e71e5a..e04852e01 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -485,7 +485,7 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME); } } - if (extensions.extended_dynamic_state2 && (is_radv || is_qualcomm)) { + if (extensions.extended_dynamic_state2 && is_radv) { const u32 version = (properties.properties.driverVersion << 3) >> 3; if (version < VK_MAKE_API_VERSION(0, 22, 3, 1)) { LOG_WARNING( @@ -498,6 +498,20 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME); } } + if (extensions.extended_dynamic_state2 && is_qualcomm) { + const u32 version = (properties.properties.driverVersion << 3) >> 3; + if (version >= VK_MAKE_API_VERSION(0, 0, 676, 0) && + version < VK_MAKE_API_VERSION(0, 0, 680, 0)) { + // Qualcomm Adreno 7xx drivers do not properly support extended_dynamic_state2. + LOG_WARNING(Render_Vulkan, + "Qualcomm Adreno 7xx drivers have broken VK_EXT_extended_dynamic_state2"); + features.extended_dynamic_state2.extendedDynamicState2 = false; + features.extended_dynamic_state2.extendedDynamicState2LogicOp = false; + features.extended_dynamic_state2.extendedDynamicState2PatchControlPoints = false; + extensions.extended_dynamic_state2 = false; + loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME); + } + } if (extensions.extended_dynamic_state3 && is_radv) { LOG_WARNING(Render_Vulkan, "RADV has broken extendedDynamicState3ColorBlendEquation"); features.extended_dynamic_state3.extendedDynamicState3ColorBlendEnable = false; @@ -512,8 +526,7 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR dynamic_state3_enables = false; } } - if (extensions.vertex_input_dynamic_state && (is_radv || is_qualcomm)) { - // Qualcomm S8gen2 drivers do not properly support vertex_input_dynamic_state. + if (extensions.vertex_input_dynamic_state && is_radv) { // TODO(ameerj): Blacklist only offending driver versions // TODO(ameerj): Confirm if RDNA1 is affected const bool is_rdna2 = @@ -526,6 +539,19 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR loaded_extensions.erase(VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME); } } + if (extensions.vertex_input_dynamic_state && is_qualcomm) { + const u32 version = (properties.properties.driverVersion << 3) >> 3; + if (version >= VK_MAKE_API_VERSION(0, 0, 676, 0) && + version < VK_MAKE_API_VERSION(0, 0, 680, 0)) { + // Qualcomm Adreno 7xx drivers do not properly support vertex_input_dynamic_state. + LOG_WARNING( + Render_Vulkan, + "Qualcomm Adreno 7xx drivers have broken VK_EXT_vertex_input_dynamic_state"); + features.vertex_input_dynamic_state.vertexInputDynamicState = false; + extensions.vertex_input_dynamic_state = false; + loaded_extensions.erase(VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME); + } + } sets_per_pool = 64; if (extensions.extended_dynamic_state3 && is_amd_driver && @@ -774,6 +800,17 @@ bool Device::ShouldBoostClocks() const { return validated_driver && !is_steam_deck && !is_debugging; } +bool Device::HasTimelineSemaphore() const { + if (GetDriverID() == VK_DRIVER_ID_QUALCOMM_PROPRIETARY || + GetDriverID() == VK_DRIVER_ID_MESA_TURNIP) { + // Timeline semaphores do not work properly on all Qualcomm drivers. + // They generally work properly with Turnip drivers, but are problematic on some devices + // (e.g. ZTE handsets with Snapdragon 870). + return false; + } + return features.timeline_semaphore.timelineSemaphore; +} + bool Device::GetSuitability(bool requires_swapchain) { // Assume we will be suitable. bool suitable = true; diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index 3ace1fb03..be3ed45ff 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h @@ -528,13 +528,7 @@ public: return extensions.shader_atomic_int64; } - bool HasTimelineSemaphore() const { - if (GetDriverID() == VK_DRIVER_ID_QUALCOMM_PROPRIETARY) { - // Timeline semaphores do not work properly on all Qualcomm drivers. - return false; - } - return features.timeline_semaphore.timelineSemaphore; - } + bool HasTimelineSemaphore() const; /// Returns the minimum supported version of SPIR-V. u32 SupportedSpirvVersion() const { diff --git a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp index a2ef0efa4..42f3ee0b4 100644 --- a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp +++ b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp @@ -221,8 +221,8 @@ vk::Image MemoryAllocator::CreateImage(const VkImageCreateInfo& ci) const { const VmaAllocationCreateInfo alloc_ci = { .flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT, .usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, - .requiredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, - .preferredFlags = 0, + .requiredFlags = 0, + .preferredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, .memoryTypeBits = 0, .pool = VK_NULL_HANDLE, .pUserData = nullptr, diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index fea5eb614..6cd557c29 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -178,6 +178,8 @@ constexpr int default_mouse_hide_timeout = 2500; constexpr int default_mouse_center_timeout = 10; constexpr int default_input_update_timeout = 1; +constexpr size_t CopyBufferSize = 1_MiB; + /** * "Callouts" are one-time instructional messages shown to the user. In the config settings, there * is a bitfield "callout_flags" options, used to track if a message has already been shown to the @@ -454,7 +456,7 @@ GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan // the user through their desktop environment. //: TRANSLATORS: This string is shown to the user to explain why yuzu needs to prevent the //: computer from sleeping - QByteArray wakelock_reason = tr("Running a game").toLatin1(); + QByteArray wakelock_reason = tr("Running a game").toUtf8(); SDL_SetHint(SDL_HINT_SCREENSAVER_INHIBIT_ACTIVITY_NAME, wakelock_reason.data()); // SDL disables the screen saver by default, and setting the hint @@ -2929,10 +2931,10 @@ void GMainWindow::OnMenuInstallToNAND() { int remaining = filenames.size(); - // This would only overflow above 2^43 bytes (8.796 TB) + // This would only overflow above 2^51 bytes (2.252 PB) int total_size = 0; for (const QString& file : files) { - total_size += static_cast<int>(QFile(file).size() / 0x1000); + total_size += static_cast<int>(QFile(file).size() / CopyBufferSize); } if (total_size < 0) { LOG_CRITICAL(Frontend, "Attempting to install too many files, aborting."); @@ -3032,7 +3034,7 @@ InstallResult GMainWindow::InstallNSPXCI(const QString& filename) { return false; } - std::vector<u8> buffer(1_MiB); + std::vector<u8> buffer(CopyBufferSize); for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) { if (install_progress->wasCanceled()) { @@ -3088,7 +3090,7 @@ InstallResult GMainWindow::InstallNCA(const QString& filename) { return false; } - std::array<u8, 0x1000> buffer{}; + std::vector<u8> buffer(CopyBufferSize); for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) { if (install_progress->wasCanceled()) { |