diff options
18 files changed, 1149 insertions, 183 deletions
diff --git a/.github/workflows/android-build.yml b/.github/workflows/android-build.yml new file mode 100644 index 000000000..e639e965a --- /dev/null +++ b/.github/workflows/android-build.yml @@ -0,0 +1,79 @@ +# 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 + 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..b5d338199 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -129,11 +129,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 +152,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/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/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/yuzu/main.cpp b/src/yuzu/main.cpp index fea5eb614..20532416c 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -454,7 +454,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 |