21 Commits

Author SHA1 Message Date
debd4b7b0c nintendo 2023-07-13 10:08:15 -06:00
4c84bce171 input_common: Avoid potential division by zero 2023-07-05 17:42:16 -06:00
d8eb37fbec Merge pull request #10994 from liamwhite/ue4-preferred
vulkan_common: use device local preferred for image memory
2023-07-05 09:23:56 -04:00
ef7d44e243 Merge pull request #11006 from german77/nfc_nfc
service: nfc: Ensure controller is in the correct mode
2023-07-05 09:23:47 -04:00
f71140fbd9 Merge pull request #11012 from gidoly/metroid-fix
Fix regression by unreal engine fix pr #11009
2023-07-05 09:23:34 -04:00
4467fd9993 Merge pull request #11017 from bunnei/fix-turnip-sd870
video_core: vulkan_device: Disable timeline semaphore on Turnip, fix qcom version check.
2023-07-03 23:48:41 -07:00
1462db4694 video_core: vulkan_device: Disable timeline semaphore on Turnip, fix qcom version check. 2023-07-03 19:25:06 -07:00
44af2e32a4 Merge pull request #10964 from bunnei/gpu-remove-qcom-check
video_core: vulkan_device: Fix S8Gen2 dynamic state checks.
2023-07-03 16:59:29 -07:00
3c88547c74 Merge pull request #10943 from t895/stick-modifiers
android: Input overlay updates
2023-07-03 14:44:15 -07:00
1fe003113e Merge pull request #10814 from liushuyu/android-pub
CI: auto-publish Android releases
2023-07-03 14:43:54 -07:00
cef7aaa8ec video_core: vulkan_device: Change to driver version check. 2023-07-03 14:25:06 -07:00
005f0eb083 Merge pull request #11007 from zeltermann/dbus-obey-utf8
Use `toUtf8()` for string passed to DBus
2023-07-03 13:23:44 -04:00
b41006004b android: Reintroduce launch mode as single top 2023-07-03 09:31:02 -06:00
408a9cd50d oops re open 2023-07-03 20:25:23 +09:00
d2b62ae401 Use toUtf8() for string passed to DBus 2023-07-03 14:46:17 +07:00
68f6f2671b android: Version the input overlay
Now within the Input Overlay file, there is a version that will determine when the overlay will be reset. This is intended for breaking changes like the ones we had with the additions of percentage based layouts or the addition of foldable/portrait layouts. This also includes versions for each individual layout so we don't have to reset every layout if only one is broken.

Additionally, this includes new L3/R3 buttons.
2023-07-02 20:19:01 -04:00
ad1946b893 vulkan_common: use device local preferred for image memory 2023-07-01 23:44:57 -04:00
14ea16e499 CI: add auto-publishing steps for Android 2023-06-30 14:26:55 -04:00
22263787e3 CI: add Android build workflow
* Switch OpenJDK runtime to Eclipse Temurin (AdoptOpenJDK has rebranded
to Eclipse Temurin)
* Fetch submodules using full clones instead of shallow clones
2023-06-30 14:24:31 -04:00
ddcd89afd4 video_core: vulkan_device: Scope S8Gen2 checks to just Qualcomm. 2023-06-29 18:41:38 -07:00
dfa040502a video_core: vulkan_device: Fix S8Gen2 dynamic state checks. 2023-06-29 17:37:42 -07:00
25 changed files with 1185 additions and 256 deletions

79
.github/workflows/android-build.yml vendored Normal file
View File

@ -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"]'

218
.github/workflows/android-merge.js vendored Normal file
View File

@ -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;

57
.github/workflows/android-publish.yml vendored Normal file
View File

@ -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);

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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()

View File

@ -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(
ButtonType.BUTTON_A.toString() + "-Y$orientation",
getResourceValue(orientation, 1)
"${Settings.PREF_BUTTON_A}-Y$layout",
getResourceValue(layout, 1)
)
.putFloat(
ButtonType.BUTTON_B.toString() + "-X$orientation",
getResourceValue(orientation, 2)
"${Settings.PREF_BUTTON_B}-X$layout",
getResourceValue(layout, 2)
)
.putFloat(
ButtonType.BUTTON_B.toString() + "-Y$orientation",
getResourceValue(orientation, 3)
"${Settings.PREF_BUTTON_B}-Y$layout",
getResourceValue(layout, 3)
)
.putFloat(
ButtonType.BUTTON_X.toString() + "-X$orientation",
getResourceValue(orientation, 4)
"${Settings.PREF_BUTTON_X}-X$layout",
getResourceValue(layout, 4)
)
.putFloat(
ButtonType.BUTTON_X.toString() + "-Y$orientation",
getResourceValue(orientation, 5)
"${Settings.PREF_BUTTON_X}-Y$layout",
getResourceValue(layout, 5)
)
.putFloat(
ButtonType.BUTTON_Y.toString() + "-X$orientation",
getResourceValue(orientation, 6)
"${Settings.PREF_BUTTON_Y}-X$layout",
getResourceValue(layout, 6)
)
.putFloat(
ButtonType.BUTTON_Y.toString() + "-Y$orientation",
getResourceValue(orientation, 7)
"${Settings.PREF_BUTTON_Y}-Y$layout",
getResourceValue(layout, 7)
)
.putFloat(
ButtonType.TRIGGER_ZL.toString() + "-X$orientation",
getResourceValue(orientation, 8)
"${Settings.PREF_BUTTON_ZL}-X$layout",
getResourceValue(layout, 8)
)
.putFloat(
ButtonType.TRIGGER_ZL.toString() + "-Y$orientation",
getResourceValue(orientation, 9)
"${Settings.PREF_BUTTON_ZL}-Y$layout",
getResourceValue(layout, 9)
)
.putFloat(
ButtonType.TRIGGER_ZR.toString() + "-X$orientation",
getResourceValue(orientation, 10)
"${Settings.PREF_BUTTON_ZR}-X$layout",
getResourceValue(layout, 10)
)
.putFloat(
ButtonType.TRIGGER_ZR.toString() + "-Y$orientation",
getResourceValue(orientation, 11)
"${Settings.PREF_BUTTON_ZR}-Y$layout",
getResourceValue(layout, 11)
)
.putFloat(
ButtonType.DPAD_UP.toString() + "-X$orientation",
getResourceValue(orientation, 12)
"${Settings.PREF_BUTTON_DPAD}-X$layout",
getResourceValue(layout, 12)
)
.putFloat(
ButtonType.DPAD_UP.toString() + "-Y$orientation",
getResourceValue(orientation, 13)
"${Settings.PREF_BUTTON_DPAD}-Y$layout",
getResourceValue(layout, 13)
)
.putFloat(
ButtonType.TRIGGER_L.toString() + "-X$orientation",
getResourceValue(orientation, 14)
"${Settings.PREF_BUTTON_L}-X$layout",
getResourceValue(layout, 14)
)
.putFloat(
ButtonType.TRIGGER_L.toString() + "-Y$orientation",
getResourceValue(orientation, 15)
"${Settings.PREF_BUTTON_L}-Y$layout",
getResourceValue(layout, 15)
)
.putFloat(
ButtonType.TRIGGER_R.toString() + "-X$orientation",
getResourceValue(orientation, 16)
"${Settings.PREF_BUTTON_R}-X$layout",
getResourceValue(layout, 16)
)
.putFloat(
ButtonType.TRIGGER_R.toString() + "-Y$orientation",
getResourceValue(orientation, 17)
"${Settings.PREF_BUTTON_R}-Y$layout",
getResourceValue(layout, 17)
)
.putFloat(
ButtonType.BUTTON_PLUS.toString() + "-X$orientation",
getResourceValue(orientation, 18)
"${Settings.PREF_BUTTON_PLUS}-X$layout",
getResourceValue(layout, 18)
)
.putFloat(
ButtonType.BUTTON_PLUS.toString() + "-Y$orientation",
getResourceValue(orientation, 19)
"${Settings.PREF_BUTTON_PLUS}-Y$layout",
getResourceValue(layout, 19)
)
.putFloat(
ButtonType.BUTTON_MINUS.toString() + "-X$orientation",
getResourceValue(orientation, 20)
"${Settings.PREF_BUTTON_MINUS}-X$layout",
getResourceValue(layout, 20)
)
.putFloat(
ButtonType.BUTTON_MINUS.toString() + "-Y$orientation",
getResourceValue(orientation, 21)
"${Settings.PREF_BUTTON_MINUS}-Y$layout",
getResourceValue(layout, 21)
)
.putFloat(
ButtonType.BUTTON_HOME.toString() + "-X$orientation",
getResourceValue(orientation, 22)
"${Settings.PREF_BUTTON_HOME}-X$layout",
getResourceValue(layout, 22)
)
.putFloat(
ButtonType.BUTTON_HOME.toString() + "-Y$orientation",
getResourceValue(orientation, 23)
"${Settings.PREF_BUTTON_HOME}-Y$layout",
getResourceValue(layout, 23)
)
.putFloat(
ButtonType.BUTTON_CAPTURE.toString() + "-X$orientation",
getResourceValue(orientation, 24)
"${Settings.PREF_BUTTON_SCREENSHOT}-X$layout",
getResourceValue(layout, 24)
)
.putFloat(
ButtonType.BUTTON_CAPTURE.toString() + "-Y$orientation",
getResourceValue(orientation, 25)
"${Settings.PREF_BUTTON_SCREENSHOT}-Y$layout",
getResourceValue(layout, 25)
)
.putFloat(
ButtonType.STICK_R.toString() + "-X$orientation",
getResourceValue(orientation, 26)
"${Settings.PREF_STICK_R}-X$layout",
getResourceValue(layout, 26)
)
.putFloat(
ButtonType.STICK_R.toString() + "-Y$orientation",
getResourceValue(orientation, 27)
"${Settings.PREF_STICK_R}-Y$layout",
getResourceValue(layout, 27)
)
.putFloat(
ButtonType.STICK_L.toString() + "-X$orientation",
getResourceValue(orientation, 28)
"${Settings.PREF_STICK_L}-X$layout",
getResourceValue(layout, 28)
)
.putFloat(
ButtonType.STICK_L.toString() + "-Y$orientation",
getResourceValue(orientation, 29)
"${Settings.PREF_STICK_L}-Y$layout",
getResourceValue(layout, 29)
)
.putFloat(
"${Settings.PREF_BUTTON_STICK_L}-X$layout",
getResourceValue(layout, 30)
)
.putFloat(
"${Settings.PREF_BUTTON_STICK_L}-Y$layout",
getResourceValue(layout, 31)
)
.putFloat(
"${Settings.PREF_BUTTON_STICK_R}-X$layout",
getResourceValue(layout, 32)
)
.putFloat(
"${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

View File

@ -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

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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;
}

View File

@ -172,88 +172,34 @@ void JoyconDriver::InputThread(std::stop_token stop_token) {
}
void JoyconDriver::OnNewData(std::span<u8> buffer) {
const u8 report_mode_byte = buffer[0];
const auto report_mode = static_cast<ReportMode>(buffer[0]);
// Packages can be a little bit inconsistent. Average the delta time to provide a smoother
// motion experience
switch (report_mode) {
case ReportMode::STANDARD_FULL_60HZ:
case ReportMode::NFC_IR_MODE_60HZ:
case ReportMode::SIMPLE_HID_MODE: {
if (report_mode_byte > 0x33 || ((1L << report_mode_byte) & 0xf000a00000000U) == 0) {
// ParceUSBMode();
}
u8 motion_samples = 0;
if (motion_enabled) {
// Packages can be a little bit inconsistent. Average the delta time to provide a smoother
// motion experience
const auto now = std::chrono::steady_clock::now();
const auto new_delta_time = static_cast<u64>(
std::chrono::duration_cast<std::chrono::microseconds>(now - last_update).count());
delta_time = ((delta_time * 8) + (new_delta_time * 2)) / 10;
last_update = now;
joycon_poller->UpdateColor(color);
break;
}
default:
break;
motion_samples = 3;
}
const MotionStatus motion_status{
.is_enabled = motion_enabled,
.delta_time = delta_time,
.gyro_sensitivity = gyro_sensitivity,
.accelerometer_sensitivity = accelerometer_sensitivity,
};
// TODO: Remove this when calibration is properly loaded and not calculated
if (ring_connected && report_mode == ReportMode::STANDARD_FULL_60HZ) {
InputReportActive data{};
memcpy(&data, buffer.data(), sizeof(InputReportActive));
calibration_protocol->GetRingCalibration(ring_calibration, data.ring_input);
if (irs_enabled) {
motion_samples = 2;
}
const RingStatus ring_status{
.is_enabled = ring_connected,
.default_value = ring_calibration.default_value,
.max_value = ring_calibration.max_value,
.min_value = ring_calibration.min_value,
};
const bool is_not_reply_or_state =
report_mode != ReportMode::SUBCMD_REPLY && report_mode != ReportMode::MCU_UPDATE_STATE;
if (irs_protocol->IsEnabled()) {
irs_protocol->RequestImage(buffer);
joycon_poller->UpdateCamera(irs_protocol->GetImage(), irs_protocol->GetIrsFormat());
}
joycon_poller->ReadActiveMode(buffer, motion);
if (nfc_protocol->IsPolling()) {
if (amiibo_detected) {
if (!nfc_protocol->HasAmiibo()) {
joycon_poller->UpdateAmiibo({});
amiibo_detected = false;
return;
}
}
if (!amiibo_detected) {
Joycon::TagInfo tag_info;
const auto result = nfc_protocol->GetTagInfo(tag_info);
if (result == Common::Input::DriverResult::Success) {
joycon_poller->UpdateAmiibo(tag_info);
amiibo_detected = true;
}
}
}
switch (report_mode) {
case ReportMode::STANDARD_FULL_60HZ:
joycon_poller->ReadActiveMode(buffer, motion_status, ring_status);
break;
case ReportMode::NFC_IR_MODE_60HZ:
joycon_poller->ReadNfcIRMode(buffer, motion_status);
break;
case ReportMode::SIMPLE_HID_MODE:
joycon_poller->ReadPassiveMode(buffer);
break;
case ReportMode::SUBCMD_REPLY:
LOG_DEBUG(Input, "Unhandled command reply");
break;
default:
LOG_ERROR(Input, "Report mode not Implemented {}", report_mode);
break;
}
}
Common::Input::DriverResult JoyconDriver::SetPollingMode() {

View File

@ -16,6 +16,13 @@ void JoyconPoller::SetCallbacks(const JoyconCallbacks& callbacks_) {
callbacks = std::move(callbacks_);
}
void JoyconPoller::ReadButtonAndMotionInput(std::span<u8> buffer, const MotionStatus& motion_status,
std::size_t motion_samples, bool is_not_reply_or_state) {
}
void JoyconPoller::ReadActiveMode(std::span<u8> buffer, const MotionStatus& motion_status,
const RingStatus& ring_status) {
InputReportActive data{};

View File

@ -24,6 +24,10 @@ public:
void SetCallbacks(const JoyconCallbacks& callbacks_);
/// Handles data from active packages
void ReadButtonAndMotionInput(std::span<u8> buffer, const MotionStatus& motion_status,
std::size_t motion_samples, bool is_not_reply_or_state);
/// Handles data from passive packages
void ReadPassiveMode(std::span<u8> buffer);

View File

@ -598,6 +598,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;
}

View File

@ -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;

View File

@ -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 {

View File

@ -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,

View File

@ -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