mirror of
https://github.com/yuzu-emu/yuzu-android
synced 2025-06-09 04:20:58 -07:00
Compare commits
3 Commits
master
...
android-17
Author | SHA1 | Date | |
---|---|---|---|
|
d333b5e3b9 | ||
|
8cce3dba47 | ||
|
945dda8614 |
@ -6,12 +6,7 @@
|
|||||||
export NDK_CCACHE="$(which ccache)"
|
export NDK_CCACHE="$(which ccache)"
|
||||||
ccache -s
|
ccache -s
|
||||||
|
|
||||||
BUILD_FLAVOR="mainline"
|
BUILD_FLAVOR=mainline
|
||||||
|
|
||||||
BUILD_TYPE="release"
|
|
||||||
if [ "${GITHUB_REPOSITORY}" == "yuzu-emu/yuzu" ]; then
|
|
||||||
BUILD_TYPE="relWithDebInfo"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ! -z "${ANDROID_KEYSTORE_B64}" ]; then
|
if [ ! -z "${ANDROID_KEYSTORE_B64}" ]; then
|
||||||
export ANDROID_KEYSTORE_FILE="${GITHUB_WORKSPACE}/ks.jks"
|
export ANDROID_KEYSTORE_FILE="${GITHUB_WORKSPACE}/ks.jks"
|
||||||
@ -20,7 +15,7 @@ fi
|
|||||||
|
|
||||||
cd src/android
|
cd src/android
|
||||||
chmod +x ./gradlew
|
chmod +x ./gradlew
|
||||||
./gradlew "assemble${BUILD_FLAVOR}${BUILD_TYPE}" "bundle${BUILD_FLAVOR}${BUILD_TYPE}"
|
./gradlew "assemble${BUILD_FLAVOR}Release" "bundle${BUILD_FLAVOR}Release"
|
||||||
|
|
||||||
ccache -s
|
ccache -s
|
||||||
|
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
#!/bin/bash -ex
|
|
||||||
|
|
||||||
# SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
export NDK_CCACHE="$(which ccache)"
|
|
||||||
ccache -s
|
|
||||||
|
|
||||||
export ANDROID_KEYSTORE_FILE="${GITHUB_WORKSPACE}/ks.jks"
|
|
||||||
base64 --decode <<< "${EA_PLAY_ANDROID_KEYSTORE_B64}" > "${ANDROID_KEYSTORE_FILE}"
|
|
||||||
export ANDROID_KEY_ALIAS="${PLAY_ANDROID_KEY_ALIAS}"
|
|
||||||
export ANDROID_KEYSTORE_PASS="${PLAY_ANDROID_KEYSTORE_PASS}"
|
|
||||||
export SERVICE_ACCOUNT_KEY_PATH="${GITHUB_WORKSPACE}/sa.json"
|
|
||||||
base64 --decode <<< "${EA_SERVICE_ACCOUNT_KEY_B64}" > "${SERVICE_ACCOUNT_KEY_PATH}"
|
|
||||||
./gradlew "publishEaReleaseBundle"
|
|
||||||
|
|
||||||
ccache -s
|
|
||||||
|
|
||||||
if [ ! -z "${ANDROID_KEYSTORE_B64}" ]; then
|
|
||||||
rm "${ANDROID_KEYSTORE_FILE}"
|
|
||||||
fi
|
|
@ -1,21 +0,0 @@
|
|||||||
#!/bin/bash -ex
|
|
||||||
|
|
||||||
# SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
export NDK_CCACHE="$(which ccache)"
|
|
||||||
ccache -s
|
|
||||||
|
|
||||||
export ANDROID_KEYSTORE_FILE="${GITHUB_WORKSPACE}/ks.jks"
|
|
||||||
base64 --decode <<< "${MAINLINE_PLAY_ANDROID_KEYSTORE_B64}" > "${ANDROID_KEYSTORE_FILE}"
|
|
||||||
export ANDROID_KEY_ALIAS="${PLAY_ANDROID_KEY_ALIAS}"
|
|
||||||
export ANDROID_KEYSTORE_PASS="${PLAY_ANDROID_KEYSTORE_PASS}"
|
|
||||||
export SERVICE_ACCOUNT_KEY_PATH="${GITHUB_WORKSPACE}/sa.json"
|
|
||||||
base64 --decode <<< "${MAINLINE_SERVICE_ACCOUNT_KEY_B64}" > "${SERVICE_ACCOUNT_KEY_PATH}"
|
|
||||||
./gradlew "publishMainlineReleaseBundle"
|
|
||||||
|
|
||||||
ccache -s
|
|
||||||
|
|
||||||
if [ ! -z "${ANDROID_KEYSTORE_B64}" ]; then
|
|
||||||
rm "${ANDROID_KEYSTORE_FILE}"
|
|
||||||
fi
|
|
@ -7,16 +7,9 @@
|
|||||||
|
|
||||||
REV_NAME="yuzu-${GITDATE}-${GITREV}"
|
REV_NAME="yuzu-${GITDATE}-${GITREV}"
|
||||||
|
|
||||||
BUILD_FLAVOR="mainline"
|
BUILD_FLAVOR=mainline
|
||||||
|
|
||||||
BUILD_TYPE_LOWER="release"
|
cp src/android/app/build/outputs/apk/"${BUILD_FLAVOR}/release/app-${BUILD_FLAVOR}-release.apk" \
|
||||||
BUILD_TYPE_UPPER="Release"
|
|
||||||
if [ "${GITHUB_REPOSITORY}" == "yuzu-emu/yuzu" ]; then
|
|
||||||
BUILD_TYPE_LOWER="relWithDebInfo"
|
|
||||||
BUILD_TYPE_UPPER="RelWithDebInfo"
|
|
||||||
fi
|
|
||||||
|
|
||||||
cp src/android/app/build/outputs/apk/"${BUILD_FLAVOR}/${BUILD_TYPE_LOWER}/app-${BUILD_FLAVOR}-${BUILD_TYPE_LOWER}.apk" \
|
|
||||||
"artifacts/${REV_NAME}.apk"
|
"artifacts/${REV_NAME}.apk"
|
||||||
cp src/android/app/build/outputs/bundle/"${BUILD_FLAVOR}${BUILD_TYPE_UPPER}"/"app-${BUILD_FLAVOR}-${BUILD_TYPE_LOWER}.aab" \
|
cp src/android/app/build/outputs/bundle/"${BUILD_FLAVOR}Release"/"app-${BUILD_FLAVOR}-release.aab" \
|
||||||
"artifacts/${REV_NAME}.aab"
|
"artifacts/${REV_NAME}.aab"
|
||||||
|
@ -3,35 +3,38 @@
|
|||||||
# SPDX-FileCopyrightText: 2019 yuzu Emulator Project
|
# SPDX-FileCopyrightText: 2019 yuzu Emulator Project
|
||||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
shopt -s nullglob globstar
|
if grep -nrI '\s$' src *.yml *.txt *.md Doxyfile .gitignore .gitmodules .ci* dist/*.desktop \
|
||||||
|
dist/*.svg dist/*.xml; then
|
||||||
if git grep -nrI '\s$' src **/*.yml **/*.txt **/*.md Doxyfile .gitignore .gitmodules .ci* dist/*.desktop dist/*.svg dist/*.xml; then
|
|
||||||
echo Trailing whitespace found, aborting
|
echo Trailing whitespace found, aborting
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Default clang-format points to default 3.5 version one
|
# Default clang-format points to default 3.5 version one
|
||||||
CLANG_FORMAT="${CLANG_FORMAT:-clang-format-15}"
|
CLANG_FORMAT=${CLANG_FORMAT:-clang-format-15}
|
||||||
"$CLANG_FORMAT" --version
|
$CLANG_FORMAT --version
|
||||||
|
|
||||||
|
if [ "$TRAVIS_EVENT_TYPE" = "pull_request" ]; then
|
||||||
|
# Get list of every file modified in this pull request
|
||||||
|
files_to_lint="$(git diff --name-only --diff-filter=ACMRTUXB $TRAVIS_COMMIT_RANGE | grep '^src/[^.]*[.]\(cpp\|h\)$' || true)"
|
||||||
|
else
|
||||||
|
# Check everything for branch pushes
|
||||||
|
files_to_lint="$(find src/ -name '*.cpp' -or -name '*.h')"
|
||||||
|
fi
|
||||||
|
|
||||||
# Turn off tracing for this because it's too verbose
|
# Turn off tracing for this because it's too verbose
|
||||||
set +x
|
set +x
|
||||||
|
|
||||||
# Check everything for branch pushes
|
for f in $files_to_lint; do
|
||||||
FILES_TO_LINT="$(find src/ -name '*.cpp' -or -name '*.h')"
|
d=$(diff -u "$f" <($CLANG_FORMAT "$f") || true)
|
||||||
|
if ! [ -z "$d" ]; then
|
||||||
for f in $FILES_TO_LINT; do
|
echo "!!! $f not compliant to coding style, here is the fix:"
|
||||||
echo "$f"
|
echo "$d"
|
||||||
"$CLANG_FORMAT" -i "$f"
|
fail=1
|
||||||
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
DIFF=$(git -c core.fileMode=false diff)
|
set -x
|
||||||
|
|
||||||
if [ ! -z "$DIFF" ]; then
|
if [ "$fail" = 1 ]; then
|
||||||
echo "!!! Not compliant to coding style, here is the fix:"
|
|
||||||
echo "$DIFF"
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cd src/android
|
|
||||||
./gradlew ktlintCheck
|
|
||||||
|
@ -8,7 +8,17 @@ variables:
|
|||||||
DisplayVersion: $[counter(variables['DisplayPrefix'], 1)]
|
DisplayVersion: $[counter(variables['DisplayPrefix'], 1)]
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
|
- stage: format
|
||||||
|
displayName: 'format'
|
||||||
|
jobs:
|
||||||
|
- job: format
|
||||||
|
displayName: 'clang'
|
||||||
|
pool:
|
||||||
|
vmImage: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- template: ./templates/format-check.yml
|
||||||
- stage: build
|
- stage: build
|
||||||
|
dependsOn: format
|
||||||
displayName: 'build'
|
displayName: 'build'
|
||||||
jobs:
|
jobs:
|
||||||
- job: build
|
- job: build
|
||||||
@ -33,6 +43,7 @@ stages:
|
|||||||
cache: 'true'
|
cache: 'true'
|
||||||
version: $(DisplayVersion)
|
version: $(DisplayVersion)
|
||||||
- stage: build_win
|
- stage: build_win
|
||||||
|
dependsOn: format
|
||||||
displayName: 'build-windows'
|
displayName: 'build-windows'
|
||||||
jobs:
|
jobs:
|
||||||
- job: build
|
- job: build
|
||||||
|
66
.github/workflows/android-ea-play-release.yml
vendored
66
.github/workflows/android-ea-play-release.yml
vendored
@ -1,66 +0,0 @@
|
|||||||
# SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
|
||||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
name: yuzu-android-ea-play-release
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
release-track:
|
|
||||||
description: 'Play store release track (internal/alpha/beta/production)'
|
|
||||||
required: true
|
|
||||||
default: 'alpha'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
android:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: ${{ github.repository == 'yuzu-emu/yuzu' }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
name: Checkout
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
submodules: true
|
|
||||||
token: ${{ secrets.ALT_GITHUB_TOKEN }}
|
|
||||||
- run: npm install execa@5
|
|
||||||
- uses: actions/github-script@v5
|
|
||||||
name: 'Merge and publish Android EA changes'
|
|
||||||
env:
|
|
||||||
ALT_GITHUB_TOKEN: ${{ secrets.ALT_GITHUB_TOKEN }}
|
|
||||||
BUILD_EA: true
|
|
||||||
with:
|
|
||||||
script: |
|
|
||||||
const execa = require("execa");
|
|
||||||
const mergebot = require('./.github/workflows/android-merge.js').mergebot;
|
|
||||||
process.chdir('${{ github.workspace }}');
|
|
||||||
mergebot(github, context, execa);
|
|
||||||
- name: Get tag name
|
|
||||||
run: echo "GIT_TAG_NAME=$(cat tag-name.txt)" >> $GITHUB_ENV
|
|
||||||
- name: Set up JDK 17
|
|
||||||
uses: actions/setup-java@v3
|
|
||||||
with:
|
|
||||||
java-version: '17'
|
|
||||||
distribution: 'temurin'
|
|
||||||
- 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/eabuild.sh
|
|
||||||
env:
|
|
||||||
EA_PLAY_ANDROID_KEYSTORE_B64: ${{ secrets.PLAY_ANDROID_KEYSTORE_B64 }}
|
|
||||||
PLAY_ANDROID_KEY_ALIAS: ${{ secrets.PLAY_ANDROID_KEY_ALIAS }}
|
|
||||||
PLAY_ANDROID_KEYSTORE_PASS: ${{ secrets.PLAY_ANDROID_KEYSTORE_PASS }}
|
|
||||||
EA_SERVICE_ACCOUNT_KEY_B64: ${{ secrets.EA_SERVICE_ACCOUNT_KEY_B64 }}
|
|
||||||
STORE_TRACK: ${{ github.event.inputs.release-track }}
|
|
||||||
AUTO_VERSIONED: true
|
|
||||||
BUILD_EA: true
|
|
||||||
- name: Create release
|
|
||||||
uses: softprops/action-gh-release@v1
|
|
||||||
with:
|
|
||||||
tag_name: ${{ env.EA_TAG_NAME }}
|
|
||||||
name: ${{ env.EA_TAG_NAME }}
|
|
||||||
draft: false
|
|
||||||
prerelease: false
|
|
||||||
repository: yuzu/yuzu-android
|
|
||||||
token: ${{ secrets.ALT_GITHUB_TOKEN }}
|
|
@ -1,59 +0,0 @@
|
|||||||
# SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
|
||||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
name: yuzu-android-mainline-play-release
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
release-tag:
|
|
||||||
description: 'Tag # from yuzu-android that you want to build and publish'
|
|
||||||
required: true
|
|
||||||
default: '200'
|
|
||||||
release-track:
|
|
||||||
description: 'Play store release track (internal/alpha/beta/production)'
|
|
||||||
required: true
|
|
||||||
default: 'alpha'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
android:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: ${{ github.repository == 'yuzu-emu/yuzu' }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
name: Checkout
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
submodules: true
|
|
||||||
token: ${{ secrets.ALT_GITHUB_TOKEN }}
|
|
||||||
- run: npm install execa@5
|
|
||||||
- uses: actions/github-script@v5
|
|
||||||
name: 'Pull mainline tag'
|
|
||||||
env:
|
|
||||||
MAINLINE_TAG: ${{ github.event.inputs.release-tag }}
|
|
||||||
with:
|
|
||||||
script: |
|
|
||||||
const execa = require("execa");
|
|
||||||
const mergebot = require('./.github/workflows/android-merge.js').getMainlineTag;
|
|
||||||
process.chdir('${{ github.workspace }}');
|
|
||||||
mergebot(execa);
|
|
||||||
- name: Set up JDK 17
|
|
||||||
uses: actions/setup-java@v3
|
|
||||||
with:
|
|
||||||
java-version: '17'
|
|
||||||
distribution: 'temurin'
|
|
||||||
- name: Install dependencies
|
|
||||||
run: |
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y ccache apksigner glslang-dev glslang-tools
|
|
||||||
- name: Build
|
|
||||||
run: |
|
|
||||||
echo "GIT_TAG_NAME=android-${{ github.event.inputs.releast-tag }}" >> $GITHUB_ENV
|
|
||||||
./.ci/scripts/android/mainlinebuild.sh
|
|
||||||
env:
|
|
||||||
MAINLINE_PLAY_ANDROID_KEYSTORE_B64: ${{ secrets.PLAY_ANDROID_KEYSTORE_B64 }}
|
|
||||||
PLAY_ANDROID_KEY_ALIAS: ${{ secrets.PLAY_ANDROID_KEY_ALIAS }}
|
|
||||||
PLAY_ANDROID_KEYSTORE_PASS: ${{ secrets.PLAY_ANDROID_KEYSTORE_PASS }}
|
|
||||||
SERVICE_ACCOUNT_KEY_B64: ${{ secrets.MAINLINE_SERVICE_ACCOUNT_KEY_B64 }}
|
|
||||||
STORE_TRACK: ${{ github.event.inputs.release-track }}
|
|
||||||
AUTO_VERSIONED: true
|
|
164
.github/workflows/android-merge.js
vendored
164
.github/workflows/android-merge.js
vendored
@ -6,14 +6,11 @@
|
|||||||
|
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
// which label to check for changes
|
// which label to check for changes
|
||||||
const CHANGE_LABEL_MAINLINE = 'android-merge';
|
const CHANGE_LABEL = 'android-merge';
|
||||||
const CHANGE_LABEL_EA = 'android-ea-merge';
|
|
||||||
// how far back in time should we consider the changes are "recent"? (default: 24 hours)
|
// 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);
|
const DETECTION_TIME_FRAME = (parseInt(process.env.DETECTION_TIME_FRAME)) || (24 * 3600 * 1000);
|
||||||
const BUILD_EA = process.env.BUILD_EA == 'true';
|
|
||||||
const MAINLINE_TAG = process.env.MAINLINE_TAG;
|
|
||||||
|
|
||||||
async function checkBaseChanges(github) {
|
async function checkBaseChanges(github, context) {
|
||||||
// query the commit date of the latest commit on this branch
|
// query the commit date of the latest commit on this branch
|
||||||
const query = `query($owner:String!, $name:String!, $ref:String!) {
|
const query = `query($owner:String!, $name:String!, $ref:String!) {
|
||||||
repository(name:$name, owner:$owner) {
|
repository(name:$name, owner:$owner) {
|
||||||
@ -25,8 +22,8 @@ async function checkBaseChanges(github) {
|
|||||||
}
|
}
|
||||||
}`;
|
}`;
|
||||||
const variables = {
|
const variables = {
|
||||||
owner: 'yuzu-emu',
|
owner: context.repo.owner,
|
||||||
name: 'yuzu',
|
name: context.repo.repo,
|
||||||
ref: 'refs/heads/master',
|
ref: 'refs/heads/master',
|
||||||
};
|
};
|
||||||
const result = await github.graphql(query, variables);
|
const result = await github.graphql(query, variables);
|
||||||
@ -41,9 +38,22 @@ async function checkBaseChanges(github) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkAndroidChanges(github) {
|
async function checkAndroidChanges(github, context) {
|
||||||
if (checkBaseChanges(github)) return true;
|
if (checkBaseChanges(github, context)) return true;
|
||||||
const pulls = getPulls(github, false);
|
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++) {
|
for (let i = 0; i < pulls.length; i++) {
|
||||||
let pull = pulls[i];
|
let pull = pulls[i];
|
||||||
if (new Date() - new Date(pull.headRepository.pushedAt) <= DETECTION_TIME_FRAME) {
|
if (new Date() - new Date(pull.headRepository.pushedAt) <= DETECTION_TIME_FRAME) {
|
||||||
@ -73,21 +83,15 @@ async function tagAndPush(github, owner, repo, execa, commit=false) {
|
|||||||
};
|
};
|
||||||
const tags = await github.graphql(query, variables);
|
const tags = await github.graphql(query, variables);
|
||||||
const tagList = tags.repository.refs.nodes;
|
const tagList = tags.repository.refs.nodes;
|
||||||
let lastTag = 'android-1';
|
const lastTag = tagList[0] ? tagList[0].name : 'dummy-0';
|
||||||
for (let i = 0; i < tagList.length; ++i) {
|
|
||||||
if (tagList[i].name.includes('android-')) {
|
|
||||||
lastTag = tagList[i].name;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const tagNumber = /\w+-(\d+)/.exec(lastTag)[1] | 0;
|
const tagNumber = /\w+-(\d+)/.exec(lastTag)[1] | 0;
|
||||||
const channel = repo.split('-')[1];
|
const channel = repo.split('-')[1];
|
||||||
const newTag = `${channel}-${tagNumber + 1}`;
|
const newTag = `${channel}-${tagNumber + 1}`;
|
||||||
console.log(`New tag: ${newTag}`);
|
console.log(`New tag: ${newTag}`);
|
||||||
if (commit) {
|
if (commit) {
|
||||||
let channelName = channel[0].toUpperCase() + channel.slice(1);
|
let channelName = channel[0].toUpperCase() + channel.slice(1);
|
||||||
console.info(`Committing pending commit as ${channelName} ${tagNumber + 1}`);
|
console.info(`Committing pending commit as ${channelName} #${tagNumber + 1}`);
|
||||||
await execa("git", ['commit', '-m', `${channelName} ${tagNumber + 1}`]);
|
await execa("git", ['commit', '-m', `${channelName} #${tagNumber + 1}`]);
|
||||||
}
|
}
|
||||||
console.info('Pushing tags to GitHub ...');
|
console.info('Pushing tags to GitHub ...');
|
||||||
await execa("git", ['tag', newTag]);
|
await execa("git", ['tag', newTag]);
|
||||||
@ -97,48 +101,6 @@ async function tagAndPush(github, owner, repo, execa, commit=false) {
|
|||||||
console.info('Successfully pushed new changes.');
|
console.info('Successfully pushed new changes.');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function tagAndPushEA(github, owner, repo, execa) {
|
|
||||||
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;
|
|
||||||
let lastTag = 'ea-1';
|
|
||||||
for (let i = 0; i < tagList.length; ++i) {
|
|
||||||
if (tagList[i].name.includes('ea-')) {
|
|
||||||
lastTag = tagList[i].name;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const tagNumber = /\w+-(\d+)/.exec(lastTag)[1] | 0;
|
|
||||||
const newTag = `ea-${tagNumber + 1}`;
|
|
||||||
console.log(`New tag: ${newTag}`);
|
|
||||||
console.info('Pushing tags to GitHub ...');
|
|
||||||
await execa("git", ["remote", "add", "android", "https://github.com/yuzu-emu/yuzu-android.git"]);
|
|
||||||
await execa("git", ["fetch", "android"]);
|
|
||||||
|
|
||||||
await execa("git", ['tag', newTag]);
|
|
||||||
await execa("git", ['push', 'android', `${newTag}`]);
|
|
||||||
|
|
||||||
fs.writeFile('tag-name.txt', newTag, (err) => {
|
|
||||||
if (err) throw 'Could not write tag name to file!'
|
|
||||||
})
|
|
||||||
|
|
||||||
console.info('Successfully pushed new changes.');
|
|
||||||
}
|
|
||||||
|
|
||||||
async function generateReadme(pulls, context, mergeResults, execa) {
|
async function generateReadme(pulls, context, mergeResults, execa) {
|
||||||
let baseUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/`;
|
let baseUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/`;
|
||||||
let output =
|
let output =
|
||||||
@ -195,7 +157,7 @@ async function mergePullRequests(pulls, execa) {
|
|||||||
process1.stdout.pipe(process.stdout);
|
process1.stdout.pipe(process.stdout);
|
||||||
await process1;
|
await process1;
|
||||||
|
|
||||||
const process2 = execa("git", ["commit", "-m", `Merge yuzu-emu#${pr}`]);
|
const process2 = execa("git", ["commit", "-m", `Merge PR ${pr}`]);
|
||||||
process2.stdout.pipe(process.stdout);
|
process2.stdout.pipe(process.stdout);
|
||||||
await process2;
|
await process2;
|
||||||
|
|
||||||
@ -220,27 +182,7 @@ async function mergePullRequests(pulls, execa) {
|
|||||||
return mergeResults;
|
return mergeResults;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function resetBranch(execa) {
|
async function mergebot(github, context, execa) {
|
||||||
console.log("::group::Reset master branch");
|
|
||||||
let hasFailed = false;
|
|
||||||
try {
|
|
||||||
await execa("git", ["remote", "add", "source", "https://github.com/yuzu-emu/yuzu.git"]);
|
|
||||||
await execa("git", ["fetch", "source"]);
|
|
||||||
const process1 = await execa("git", ["rev-parse", "source/master"]);
|
|
||||||
const headCommit = process1.stdout;
|
|
||||||
|
|
||||||
await execa("git", ["reset", "--hard", headCommit]);
|
|
||||||
} catch (err) {
|
|
||||||
console.log(`::error title=Failed to reset master branch`);
|
|
||||||
hasFailed = true;
|
|
||||||
}
|
|
||||||
console.log("::endgroup::");
|
|
||||||
if (hasFailed) {
|
|
||||||
throw 'Failed to reset the master branch. Aborting!';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getPulls(github) {
|
|
||||||
const query = `query ($owner:String!, $name:String!, $label:String!) {
|
const query = `query ($owner:String!, $name:String!, $label:String!) {
|
||||||
repository(name:$name, owner:$owner) {
|
repository(name:$name, owner:$owner) {
|
||||||
pullRequests(labels: [$label], states: OPEN, first: 100) {
|
pullRequests(labels: [$label], states: OPEN, first: 100) {
|
||||||
@ -250,49 +192,13 @@ async function getPulls(github) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}`;
|
}`;
|
||||||
const mainlineVariables = {
|
const variables = {
|
||||||
owner: 'yuzu-emu',
|
owner: context.repo.owner,
|
||||||
name: 'yuzu',
|
name: context.repo.repo,
|
||||||
label: CHANGE_LABEL_MAINLINE,
|
label: CHANGE_LABEL,
|
||||||
};
|
};
|
||||||
const mainlineResult = await github.graphql(query, mainlineVariables);
|
const result = await github.graphql(query, variables);
|
||||||
const pulls = mainlineResult.repository.pullRequests.nodes;
|
const pulls = result.repository.pullRequests.nodes;
|
||||||
if (BUILD_EA) {
|
|
||||||
const eaVariables = {
|
|
||||||
owner: 'yuzu-emu',
|
|
||||||
name: 'yuzu',
|
|
||||||
label: CHANGE_LABEL_EA,
|
|
||||||
};
|
|
||||||
const eaResult = await github.graphql(query, eaVariables);
|
|
||||||
const eaPulls = eaResult.repository.pullRequests.nodes;
|
|
||||||
return pulls.concat(eaPulls);
|
|
||||||
}
|
|
||||||
return pulls;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getMainlineTag(execa) {
|
|
||||||
console.log(`::group::Getting mainline tag android-${MAINLINE_TAG}`);
|
|
||||||
let hasFailed = false;
|
|
||||||
try {
|
|
||||||
await execa("git", ["remote", "add", "mainline", "https://github.com/yuzu-emu/yuzu-android.git"]);
|
|
||||||
await execa("git", ["fetch", "mainline", "--tags"]);
|
|
||||||
await execa("git", ["checkout", `tags/android-${MAINLINE_TAG}`]);
|
|
||||||
await execa("git", ["submodule", "update", "--init", "--recursive"]);
|
|
||||||
} catch (err) {
|
|
||||||
console.log('::error title=Failed pull tag');
|
|
||||||
hasFailed = true;
|
|
||||||
}
|
|
||||||
console.log("::endgroup::");
|
|
||||||
if (hasFailed) {
|
|
||||||
throw 'Failed pull mainline tag. Aborting!';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function mergebot(github, context, execa) {
|
|
||||||
// Reset our local copy of master to what appears on yuzu-emu/yuzu - master
|
|
||||||
await resetBranch(execa);
|
|
||||||
|
|
||||||
const pulls = await getPulls(github);
|
|
||||||
let displayList = [];
|
let displayList = [];
|
||||||
for (let i = 0; i < pulls.length; i++) {
|
for (let i = 0; i < pulls.length; i++) {
|
||||||
let pull = pulls[i];
|
let pull = pulls[i];
|
||||||
@ -302,17 +208,11 @@ async function mergebot(github, context, execa) {
|
|||||||
console.table(displayList);
|
console.table(displayList);
|
||||||
await fetchPullRequests(pulls, "https://github.com/yuzu-emu/yuzu", execa);
|
await fetchPullRequests(pulls, "https://github.com/yuzu-emu/yuzu", execa);
|
||||||
const mergeResults = await mergePullRequests(pulls, execa);
|
const mergeResults = await mergePullRequests(pulls, execa);
|
||||||
|
|
||||||
if (BUILD_EA) {
|
|
||||||
await tagAndPushEA(github, 'yuzu-emu', `yuzu-android`, execa);
|
|
||||||
} else {
|
|
||||||
await generateReadme(pulls, context, mergeResults, execa);
|
await generateReadme(pulls, context, mergeResults, execa);
|
||||||
await tagAndPush(github, 'yuzu-emu', `yuzu-android`, execa, true);
|
await tagAndPush(github, context.repo.owner, `${context.repo.repo}-android`, execa, true);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports.mergebot = mergebot;
|
module.exports.mergebot = mergebot;
|
||||||
module.exports.checkAndroidChanges = checkAndroidChanges;
|
module.exports.checkAndroidChanges = checkAndroidChanges;
|
||||||
module.exports.tagAndPush = tagAndPush;
|
module.exports.tagAndPush = tagAndPush;
|
||||||
module.exports.checkBaseChanges = checkBaseChanges;
|
module.exports.checkBaseChanges = checkBaseChanges;
|
||||||
module.exports.getMainlineTag = getMainlineTag;
|
|
||||||
|
4
.github/workflows/android-publish.yml
vendored
4
.github/workflows/android-publish.yml
vendored
@ -1,4 +1,4 @@
|
|||||||
# SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
# SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
name: yuzu-android-publish
|
name: yuzu-android-publish
|
||||||
@ -33,7 +33,7 @@ jobs:
|
|||||||
script: |
|
script: |
|
||||||
if (context.payload.inputs && context.payload.inputs.android === 'true') return true;
|
if (context.payload.inputs && context.payload.inputs.android === 'true') return true;
|
||||||
const checkAndroidChanges = require('./.github/workflows/android-merge.js').checkAndroidChanges;
|
const checkAndroidChanges = require('./.github/workflows/android-merge.js').checkAndroidChanges;
|
||||||
return checkAndroidChanges(github);
|
return checkAndroidChanges(github, context);
|
||||||
- run: npm install execa@5
|
- run: npm install execa@5
|
||||||
if: ${{ steps.check-changes.outputs.result == 'true' }}
|
if: ${{ steps.check-changes.outputs.result == 'true' }}
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
15
.github/workflows/verify.yml
vendored
15
.github/workflows/verify.yml
vendored
@ -13,15 +13,13 @@ jobs:
|
|||||||
format:
|
format:
|
||||||
name: 'verify format'
|
name: 'verify format'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: yuzuemu/build-environments:linux-clang-format
|
||||||
|
options: -u 1001
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
submodules: false
|
submodules: false
|
||||||
- name: set up JDK 17
|
|
||||||
uses: actions/setup-java@v3
|
|
||||||
with:
|
|
||||||
java-version: '17'
|
|
||||||
distribution: 'temurin'
|
|
||||||
- name: 'Verify Formatting'
|
- name: 'Verify Formatting'
|
||||||
run: bash -ex ./.ci/scripts/format/script.sh
|
run: bash -ex ./.ci/scripts/format/script.sh
|
||||||
build:
|
build:
|
||||||
@ -73,7 +71,7 @@ jobs:
|
|||||||
build-mac:
|
build-mac:
|
||||||
name: 'test build (macos)'
|
name: 'test build (macos)'
|
||||||
needs: format
|
needs: format
|
||||||
runs-on: macos-14
|
runs-on: macos-13
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
@ -81,12 +79,13 @@ jobs:
|
|||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
brew install autoconf automake boost ccache ffmpeg fmt glslang hidapi libtool libusb lz4 ninja nlohmann-json openssl pkg-config qt@5 sdl2 speexdsp zlib zlib zstd
|
# workaround for https://github.com/actions/setup-python/issues/577
|
||||||
|
brew install autoconf automake boost@1.83 ccache ffmpeg fmt glslang hidapi libtool libusb lz4 ninja nlohmann-json openssl pkg-config qt@5 sdl2 speexdsp zlib zlib zstd || brew link --overwrite python@3.12
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
mkdir build
|
mkdir build
|
||||||
cd build
|
cd build
|
||||||
export Qt5_DIR="$(brew --prefix qt@5)/lib/cmake"
|
export Qt5_DIR="/usr/local/opt/qt@5/lib/cmake"
|
||||||
cmake .. -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DYUZU_USE_BUNDLED_VCPKG=OFF -DYUZU_TESTS=OFF -DENABLE_WEB_SERVICE=OFF -DENABLE_LIBUSB=OFF
|
cmake .. -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DYUZU_USE_BUNDLED_VCPKG=OFF -DYUZU_TESTS=OFF -DENABLE_WEB_SERVICE=OFF -DENABLE_LIBUSB=OFF
|
||||||
ninja
|
ninja
|
||||||
build-msvc:
|
build-msvc:
|
||||||
|
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -64,6 +64,3 @@
|
|||||||
[submodule "oaknut"]
|
[submodule "oaknut"]
|
||||||
path = externals/oaknut
|
path = externals/oaknut
|
||||||
url = https://github.com/merryhime/oaknut
|
url = https://github.com/merryhime/oaknut
|
||||||
[submodule "Vulkan-Utility-Libraries"]
|
|
||||||
path = externals/Vulkan-Utility-Libraries
|
|
||||||
url = https://github.com/KhronosGroup/Vulkan-Utility-Libraries.git
|
|
||||||
|
@ -155,7 +155,3 @@ License: MIT
|
|||||||
Files: externals/gamemode/*
|
Files: externals/gamemode/*
|
||||||
Copyright: Copyright 2017-2019 Feral Interactive
|
Copyright: Copyright 2017-2019 Feral Interactive
|
||||||
License: BSD-3-Clause
|
License: BSD-3-Clause
|
||||||
|
|
||||||
Files: src/android/app/debug.keystore
|
|
||||||
Copyright: 2023 yuzu Emulator Project
|
|
||||||
License: GPL-3.0-or-later
|
|
||||||
|
@ -36,8 +36,6 @@ option(YUZU_USE_BUNDLED_FFMPEG "Download/Build bundled FFmpeg" "${WIN32}")
|
|||||||
|
|
||||||
option(YUZU_USE_EXTERNAL_VULKAN_HEADERS "Use Vulkan-Headers from externals" ON)
|
option(YUZU_USE_EXTERNAL_VULKAN_HEADERS "Use Vulkan-Headers from externals" ON)
|
||||||
|
|
||||||
option(YUZU_USE_EXTERNAL_VULKAN_UTILITY_LIBRARIES "Use Vulkan-Utility-Libraries from externals" ON)
|
|
||||||
|
|
||||||
option(YUZU_USE_QT_MULTIMEDIA "Use QtMultimedia for Camera" OFF)
|
option(YUZU_USE_QT_MULTIMEDIA "Use QtMultimedia for Camera" OFF)
|
||||||
|
|
||||||
option(YUZU_USE_QT_WEB_ENGINE "Use QtWebEngine for web applet implementation" OFF)
|
option(YUZU_USE_QT_WEB_ENGINE "Use QtWebEngine for web applet implementation" OFF)
|
||||||
@ -307,11 +305,7 @@ find_package(ZLIB 1.2 REQUIRED)
|
|||||||
find_package(zstd 1.5 REQUIRED)
|
find_package(zstd 1.5 REQUIRED)
|
||||||
|
|
||||||
if (NOT YUZU_USE_EXTERNAL_VULKAN_HEADERS)
|
if (NOT YUZU_USE_EXTERNAL_VULKAN_HEADERS)
|
||||||
find_package(VulkanHeaders 1.3.274 REQUIRED)
|
find_package(Vulkan 1.3.274 REQUIRED)
|
||||||
endif()
|
|
||||||
|
|
||||||
if (NOT YUZU_USE_EXTERNAL_VULKAN_UTILITY_LIBRARIES)
|
|
||||||
find_package(VulkanUtilityLibraries REQUIRED)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (ENABLE_LIBUSB)
|
if (ENABLE_LIBUSB)
|
||||||
@ -322,10 +316,6 @@ if (ARCHITECTURE_x86 OR ARCHITECTURE_x86_64)
|
|||||||
find_package(xbyak 6 CONFIG)
|
find_package(xbyak 6 CONFIG)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (ARCHITECTURE_arm64)
|
|
||||||
find_package(oaknut 2.0.1 CONFIG)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64)
|
if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64)
|
||||||
find_package(dynarmic 6.4.0 CONFIG)
|
find_package(dynarmic 6.4.0 CONFIG)
|
||||||
endif()
|
endif()
|
||||||
|
@ -2,20 +2,18 @@
|
|||||||
#
|
#
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
include(FindPackageHandleStandardArgs)
|
find_path(SimpleIni_INCLUDE_DIR SimpleIni.h)
|
||||||
|
|
||||||
find_package(SimpleIni QUIET CONFIG)
|
include(FindPackageHandleStandardArgs)
|
||||||
if (SimpleIni_CONSIDERED_CONFIGS)
|
find_package_handle_standard_args(SimpleIni
|
||||||
find_package_handle_standard_args(SimpleIni CONFIG_MODE)
|
REQUIRED_VARS SimpleIni_INCLUDE_DIR
|
||||||
else()
|
)
|
||||||
find_package(PkgConfig QUIET)
|
|
||||||
pkg_search_module(SIMPLEINI QUIET IMPORTED_TARGET simpleini)
|
if (SimpleIni_FOUND AND NOT TARGET SimpleIni::SimpleIni)
|
||||||
find_package_handle_standard_args(SimpleIni
|
add_library(SimpleIni::SimpleIni INTERFACE IMPORTED)
|
||||||
REQUIRED_VARS SIMPLEINI_INCLUDEDIR
|
set_target_properties(SimpleIni::SimpleIni PROPERTIES
|
||||||
VERSION_VAR SIMPLEINI_VERSION
|
INTERFACE_INCLUDE_DIRECTORIES "${SimpleIni_INCLUDE_DIR}"
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (SimpleIni_FOUND AND NOT TARGET SimpleIni::SimpleIni)
|
mark_as_advanced(SimpleIni_INCLUDE_DIR)
|
||||||
add_library(SimpleIni::SimpleIni ALIAS PkgConfig::SIMPLEINI)
|
|
||||||
endif()
|
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
| Pull Request | Commit | Title | Author | Merged? |
|
| Pull Request | Commit | Title | Author | Merged? |
|
||||||
|----|----|----|----|----|
|
|----|----|----|----|----|
|
||||||
| [12461](https://github.com/yuzu-emu/yuzu//pull/12461) | [`7464cae24`](https://github.com/yuzu-emu/yuzu//pull/12461/files) | Rework Nvdec and VIC to fix out-of-order videos, and speed up decoding. | [Kelebek1](https://github.com/Kelebek1/) | Yes |
|
| [12466](https://github.com/yuzu-emu/yuzu//pull/12466) | [`ddda76f9b`](https://github.com/yuzu-emu/yuzu//pull/12466/files) | core: track separate heap allocation for linux | [liamwhite](https://github.com/liamwhite/) | Yes |
|
||||||
| [13018](https://github.com/yuzu-emu/yuzu//pull/13018) | [`01cbc638a`](https://github.com/yuzu-emu/yuzu//pull/13018/files) | am: rewrite part 2 | [liamwhite](https://github.com/liamwhite/) | Yes |
|
| [12479](https://github.com/yuzu-emu/yuzu//pull/12479) | [`20e040723`](https://github.com/yuzu-emu/yuzu//pull/12479/files) | video_core: Fix buffer_row_length for linear compressed textures | [GPUCode](https://github.com/GPUCode/) | Yes |
|
||||||
| [13174](https://github.com/yuzu-emu/yuzu//pull/13174) | [`7d1284826`](https://github.com/yuzu-emu/yuzu//pull/13174/files) | glue/time: Remove global variables | [FearlessTobi](https://github.com/FearlessTobi/) | Yes |
|
|
||||||
| [13177](https://github.com/yuzu-emu/yuzu//pull/13177) | [`f5cc94f05`](https://github.com/yuzu-emu/yuzu//pull/13177/files) | vfs: misc performance improvements | [liamwhite](https://github.com/liamwhite/) | Yes |
|
|
||||||
|
|
||||||
|
|
||||||
End of merge log. You can find the original README.md below the break.
|
End of merge log. You can find the original README.md below the break.
|
||||||
|
1
dist/languages/.tx/config
vendored
1
dist/languages/.tx/config
vendored
@ -11,4 +11,3 @@ type = QT
|
|||||||
file_filter = ../../src/android/app/src/main/res/values-<lang>/strings.xml
|
file_filter = ../../src/android/app/src/main/res/values-<lang>/strings.xml
|
||||||
source_file = ../../src/android/app/src/main/res/values/strings.xml
|
source_file = ../../src/android/app/src/main/res/values/strings.xml
|
||||||
type = ANDROID
|
type = ANDROID
|
||||||
lang_map = ja_JP:ja, ko_KR:ko, pt_BR:pt-rBR, pt_PT:pt-rPT, ru_RU:ru, vi_VN:vi, zh_CN:zh-rCN, zh_TW:zh-rTW
|
|
||||||
|
1857
dist/languages/ar.ts
vendored
1857
dist/languages/ar.ts
vendored
File diff suppressed because it is too large
Load Diff
1889
dist/languages/ca.ts
vendored
1889
dist/languages/ca.ts
vendored
File diff suppressed because it is too large
Load Diff
2120
dist/languages/cs.ts
vendored
2120
dist/languages/cs.ts
vendored
File diff suppressed because it is too large
Load Diff
1843
dist/languages/da.ts
vendored
1843
dist/languages/da.ts
vendored
File diff suppressed because it is too large
Load Diff
1898
dist/languages/de.ts
vendored
1898
dist/languages/de.ts
vendored
File diff suppressed because it is too large
Load Diff
1857
dist/languages/el.ts
vendored
1857
dist/languages/el.ts
vendored
File diff suppressed because it is too large
Load Diff
2059
dist/languages/es.ts
vendored
2059
dist/languages/es.ts
vendored
File diff suppressed because it is too large
Load Diff
1939
dist/languages/fr.ts
vendored
1939
dist/languages/fr.ts
vendored
File diff suppressed because it is too large
Load Diff
2112
dist/languages/hu.ts
vendored
2112
dist/languages/hu.ts
vendored
File diff suppressed because it is too large
Load Diff
2314
dist/languages/id.ts
vendored
2314
dist/languages/id.ts
vendored
File diff suppressed because it is too large
Load Diff
1871
dist/languages/it.ts
vendored
1871
dist/languages/it.ts
vendored
File diff suppressed because it is too large
Load Diff
1875
dist/languages/ja_JP.ts
vendored
1875
dist/languages/ja_JP.ts
vendored
File diff suppressed because it is too large
Load Diff
1859
dist/languages/ko_KR.ts
vendored
1859
dist/languages/ko_KR.ts
vendored
File diff suppressed because it is too large
Load Diff
1857
dist/languages/nb.ts
vendored
1857
dist/languages/nb.ts
vendored
File diff suppressed because it is too large
Load Diff
1857
dist/languages/nl.ts
vendored
1857
dist/languages/nl.ts
vendored
File diff suppressed because it is too large
Load Diff
1857
dist/languages/pl.ts
vendored
1857
dist/languages/pl.ts
vendored
File diff suppressed because it is too large
Load Diff
2336
dist/languages/pt_BR.ts
vendored
2336
dist/languages/pt_BR.ts
vendored
File diff suppressed because it is too large
Load Diff
1994
dist/languages/pt_PT.ts
vendored
1994
dist/languages/pt_PT.ts
vendored
File diff suppressed because it is too large
Load Diff
2058
dist/languages/ru_RU.ts
vendored
2058
dist/languages/ru_RU.ts
vendored
File diff suppressed because it is too large
Load Diff
1851
dist/languages/sv.ts
vendored
1851
dist/languages/sv.ts
vendored
File diff suppressed because it is too large
Load Diff
1915
dist/languages/tr_TR.ts
vendored
1915
dist/languages/tr_TR.ts
vendored
File diff suppressed because it is too large
Load Diff
1857
dist/languages/uk.ts
vendored
1857
dist/languages/uk.ts
vendored
File diff suppressed because it is too large
Load Diff
1857
dist/languages/vi.ts
vendored
1857
dist/languages/vi.ts
vendored
File diff suppressed because it is too large
Load Diff
1857
dist/languages/vi_VN.ts
vendored
1857
dist/languages/vi_VN.ts
vendored
File diff suppressed because it is too large
Load Diff
1943
dist/languages/zh_CN.ts
vendored
1943
dist/languages/zh_CN.ts
vendored
File diff suppressed because it is too large
Load Diff
1910
dist/languages/zh_TW.ts
vendored
1910
dist/languages/zh_TW.ts
vendored
File diff suppressed because it is too large
Load Diff
20
externals/CMakeLists.txt
vendored
20
externals/CMakeLists.txt
vendored
@ -14,17 +14,16 @@ set(BUILD_SHARED_LIBS OFF)
|
|||||||
# Skip install rules for all externals
|
# Skip install rules for all externals
|
||||||
set_directory_properties(PROPERTIES EXCLUDE_FROM_ALL ON)
|
set_directory_properties(PROPERTIES EXCLUDE_FROM_ALL ON)
|
||||||
|
|
||||||
# Xbyak (also used by Dynarmic, so needs to be added first)
|
# xbyak
|
||||||
if ((ARCHITECTURE_x86 OR ARCHITECTURE_x86_64) AND NOT TARGET xbyak::xbyak)
|
if ((ARCHITECTURE_x86 OR ARCHITECTURE_x86_64) AND NOT TARGET xbyak::xbyak)
|
||||||
add_subdirectory(xbyak)
|
add_subdirectory(xbyak)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Oaknut (also used by Dynarmic, so needs to be added first)
|
# Dynarmic
|
||||||
if (ARCHITECTURE_arm64 AND NOT TARGET merry::oaknut)
|
if (ARCHITECTURE_arm64 AND NOT TARGET merry::oaknut)
|
||||||
add_subdirectory(oaknut)
|
add_subdirectory(oaknut)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Dynarmic
|
|
||||||
if ((ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64) AND NOT TARGET dynarmic::dynarmic)
|
if ((ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64) AND NOT TARGET dynarmic::dynarmic)
|
||||||
set(DYNARMIC_IGNORE_ASSERTS ON)
|
set(DYNARMIC_IGNORE_ASSERTS ON)
|
||||||
add_subdirectory(dynarmic)
|
add_subdirectory(dynarmic)
|
||||||
@ -155,11 +154,6 @@ if (YUZU_USE_EXTERNAL_VULKAN_HEADERS)
|
|||||||
add_subdirectory(Vulkan-Headers)
|
add_subdirectory(Vulkan-Headers)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Vulkan-Utility-Libraries
|
|
||||||
if (YUZU_USE_EXTERNAL_VULKAN_UTILITY_LIBRARIES)
|
|
||||||
add_subdirectory(Vulkan-Utility-Libraries)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# TZDB (Time Zone Database)
|
# TZDB (Time Zone Database)
|
||||||
add_subdirectory(nx_tzdb)
|
add_subdirectory(nx_tzdb)
|
||||||
|
|
||||||
@ -184,9 +178,6 @@ if (NOT TARGET stb::headers)
|
|||||||
add_library(stb::headers ALIAS stb)
|
add_library(stb::headers ALIAS stb)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_library(tz tz/tz/tz.cpp)
|
|
||||||
target_include_directories(tz PUBLIC ./tz)
|
|
||||||
|
|
||||||
add_library(bc_decoder bc_decoder/bc_decoder.cpp)
|
add_library(bc_decoder bc_decoder/bc_decoder.cpp)
|
||||||
target_include_directories(bc_decoder PUBLIC ./bc_decoder)
|
target_include_directories(bc_decoder PUBLIC ./bc_decoder)
|
||||||
|
|
||||||
@ -314,10 +305,3 @@ endif()
|
|||||||
if (NOT TARGET SimpleIni::SimpleIni)
|
if (NOT TARGET SimpleIni::SimpleIni)
|
||||||
add_subdirectory(simpleini)
|
add_subdirectory(simpleini)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# sse2neon
|
|
||||||
if (ARCHITECTURE_arm64 AND NOT TARGET sse2neon)
|
|
||||||
add_library(sse2neon INTERFACE)
|
|
||||||
target_include_directories(sse2neon INTERFACE sse2neon)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
|
1
externals/Vulkan-Utility-Libraries
vendored
1
externals/Vulkan-Utility-Libraries
vendored
@ -1 +0,0 @@
|
|||||||
Subproject commit 524f8910d0e4a5f2ec5961996b23e5b74b95cb1d
|
|
2
externals/dynarmic
vendored
2
externals/dynarmic
vendored
@ -1 +1 @@
|
|||||||
Subproject commit ba8192d89078af51ae6f97c9352e3683612cdff1
|
Subproject commit 0df09e2f6b61c2d7ad2f2053d4f020a5c33e0378
|
2
externals/nx_tzdb/CMakeLists.txt
vendored
2
externals/nx_tzdb/CMakeLists.txt
vendored
@ -32,7 +32,7 @@ set(NX_TZDB_ARCHIVE "${CMAKE_CURRENT_BINARY_DIR}/${NX_TZDB_VERSION}.zip")
|
|||||||
|
|
||||||
set(NX_TZDB_ROMFS_DIR "${CMAKE_CURRENT_BINARY_DIR}/nx_tzdb")
|
set(NX_TZDB_ROMFS_DIR "${CMAKE_CURRENT_BINARY_DIR}/nx_tzdb")
|
||||||
|
|
||||||
if ((NOT CAN_BUILD_NX_TZDB OR YUZU_DOWNLOAD_TIME_ZONE_DATA) AND NOT EXISTS ${NX_TZDB_ROMFS_DIR})
|
if ((NOT CAN_BUILD_NX_TZDB OR YUZU_DOWNLOAD_TIME_ZONE_DATA) AND NOT EXISTS ${NX_TZDB_ARCHIVE})
|
||||||
set(NX_TZDB_DOWNLOAD_URL "https://github.com/lat9nq/tzdb_to_nx/releases/download/${NX_TZDB_VERSION}/${NX_TZDB_VERSION}.zip")
|
set(NX_TZDB_DOWNLOAD_URL "https://github.com/lat9nq/tzdb_to_nx/releases/download/${NX_TZDB_VERSION}/${NX_TZDB_VERSION}.zip")
|
||||||
|
|
||||||
message(STATUS "Downloading time zone data from ${NX_TZDB_DOWNLOAD_URL}...")
|
message(STATUS "Downloading time zone data from ${NX_TZDB_DOWNLOAD_URL}...")
|
||||||
|
4
externals/nx_tzdb/NxTzdbCreateHeader.cmake
vendored
4
externals/nx_tzdb/NxTzdbCreateHeader.cmake
vendored
@ -11,10 +11,6 @@ execute_process(
|
|||||||
WORKING_DIRECTORY ${ZONE_PATH}
|
WORKING_DIRECTORY ${ZONE_PATH}
|
||||||
OUTPUT_VARIABLE FILE_LIST)
|
OUTPUT_VARIABLE FILE_LIST)
|
||||||
|
|
||||||
if (NOT FILE_LIST)
|
|
||||||
message(FATAL_ERROR "No timezone files found in directory ${ZONE_PATH}, did the download fail?")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
set(DIRECTORY_NAME ${HEADER_NAME})
|
set(DIRECTORY_NAME ${HEADER_NAME})
|
||||||
|
|
||||||
set(FILE_DATA "")
|
set(FILE_DATA "")
|
||||||
|
2
externals/nx_tzdb/tzdb_to_nx
vendored
2
externals/nx_tzdb/tzdb_to_nx
vendored
@ -1 +1 @@
|
|||||||
Subproject commit 97929690234f2b4add36b33657fe3fe09bd57dfd
|
Subproject commit f6680093bca30265c161581fd813d4ddd33f1e3e
|
2
externals/oaknut
vendored
2
externals/oaknut
vendored
@ -1 +1 @@
|
|||||||
Subproject commit 9d091109deb445bc6e9289c6195a282b7c993d49
|
Subproject commit 918bd94f025d6a2de13978468351598997ae3909
|
9282
externals/sse2neon/sse2neon.h
vendored
9282
externals/sse2neon/sse2neon.h
vendored
File diff suppressed because it is too large
Load Diff
1636
externals/tz/tz/tz.cpp
vendored
1636
externals/tz/tz/tz.cpp
vendored
File diff suppressed because it is too large
Load Diff
81
externals/tz/tz/tz.h
vendored
81
externals/tz/tz/tz.h
vendored
@ -1,81 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
|
||||||
// SPDX-FileCopyrightText: 1996 Arthur David Olson
|
|
||||||
// SPDX-License-Identifier: BSD-2-Clause
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <limits>
|
|
||||||
#include <span>
|
|
||||||
#include <array>
|
|
||||||
#include <time.h>
|
|
||||||
|
|
||||||
namespace Tz {
|
|
||||||
using u8 = uint8_t;
|
|
||||||
using s8 = int8_t;
|
|
||||||
using u16 = uint16_t;
|
|
||||||
using s16 = int16_t;
|
|
||||||
using u32 = uint32_t;
|
|
||||||
using s32 = int32_t;
|
|
||||||
using u64 = uint64_t;
|
|
||||||
using s64 = int64_t;
|
|
||||||
|
|
||||||
constexpr size_t TZ_MAX_TIMES = 1000;
|
|
||||||
constexpr size_t TZ_MAX_TYPES = 128;
|
|
||||||
constexpr size_t TZ_MAX_CHARS = 50;
|
|
||||||
constexpr size_t MY_TZNAME_MAX = 255;
|
|
||||||
constexpr size_t TZNAME_MAXIMUM = 255;
|
|
||||||
constexpr size_t TZ_MAX_LEAPS = 50;
|
|
||||||
constexpr s64 TIME_T_MAX = std::numeric_limits<s64>::max();
|
|
||||||
constexpr s64 TIME_T_MIN = std::numeric_limits<s64>::min();
|
|
||||||
constexpr size_t CHARS_EXTRA = 3;
|
|
||||||
constexpr size_t MAX_ZONE_CHARS = std::max(TZ_MAX_CHARS + CHARS_EXTRA, sizeof("UTC"));
|
|
||||||
constexpr size_t MAX_TZNAME_CHARS = 2 * (MY_TZNAME_MAX + 1);
|
|
||||||
|
|
||||||
struct ttinfo {
|
|
||||||
s32 tt_utoff;
|
|
||||||
bool tt_isdst;
|
|
||||||
s32 tt_desigidx;
|
|
||||||
bool tt_ttisstd;
|
|
||||||
bool tt_ttisut;
|
|
||||||
};
|
|
||||||
static_assert(sizeof(ttinfo) == 0x10, "ttinfo has the wrong size!");
|
|
||||||
|
|
||||||
struct Rule {
|
|
||||||
s32 timecnt;
|
|
||||||
s32 typecnt;
|
|
||||||
s32 charcnt;
|
|
||||||
bool goback;
|
|
||||||
bool goahead;
|
|
||||||
std::array <u8, 0x2> padding0;
|
|
||||||
std::array<s64, TZ_MAX_TIMES> ats;
|
|
||||||
std::array<u8, TZ_MAX_TIMES> types;
|
|
||||||
std::array<ttinfo, TZ_MAX_TYPES> ttis;
|
|
||||||
std::array<char, std::max(MAX_ZONE_CHARS, MAX_TZNAME_CHARS)> chars;
|
|
||||||
s32 defaulttype;
|
|
||||||
std::array <u8, 0x12C4> padding1;
|
|
||||||
};
|
|
||||||
static_assert(sizeof(Rule) == 0x4000, "Rule has the wrong size!");
|
|
||||||
|
|
||||||
struct CalendarTimeInternal {
|
|
||||||
s32 tm_sec;
|
|
||||||
s32 tm_min;
|
|
||||||
s32 tm_hour;
|
|
||||||
s32 tm_mday;
|
|
||||||
s32 tm_mon;
|
|
||||||
s32 tm_year;
|
|
||||||
s32 tm_wday;
|
|
||||||
s32 tm_yday;
|
|
||||||
s32 tm_isdst;
|
|
||||||
std::array<char, 16> tm_zone;
|
|
||||||
s32 tm_utoff;
|
|
||||||
s32 time_index;
|
|
||||||
};
|
|
||||||
static_assert(sizeof(CalendarTimeInternal) == 0x3C, "CalendarTimeInternal has the wrong size!");
|
|
||||||
|
|
||||||
s32 ParseTimeZoneBinary(Rule& out_rule, std::span<const u8> binary);
|
|
||||||
|
|
||||||
bool localtime_rz(CalendarTimeInternal* tmp, Rule const* sp, time_t* timep);
|
|
||||||
u32 mktime_tzname(time_t* out_time, Rule const* sp, CalendarTimeInternal* tmp);
|
|
||||||
|
|
||||||
} // namespace Tz
|
|
@ -121,7 +121,6 @@ else()
|
|||||||
-Wno-attributes
|
-Wno-attributes
|
||||||
-Wno-invalid-offsetof
|
-Wno-invalid-offsetof
|
||||||
-Wno-unused-parameter
|
-Wno-unused-parameter
|
||||||
-Wno-missing-field-initializers
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if (CMAKE_CXX_COMPILER_ID MATCHES Clang) # Clang or AppleClang
|
if (CMAKE_CXX_COMPILER_ID MATCHES Clang) # Clang or AppleClang
|
||||||
@ -165,7 +164,6 @@ else()
|
|||||||
|
|
||||||
if (MINGW)
|
if (MINGW)
|
||||||
add_definitions(-DMINGW_HAS_SECURE_API)
|
add_definitions(-DMINGW_HAS_SECURE_API)
|
||||||
add_compile_options("-msse4.1")
|
|
||||||
|
|
||||||
if (MINGW_STATIC_BUILD)
|
if (MINGW_STATIC_BUILD)
|
||||||
add_definitions(-DQT_STATICPLUGIN)
|
add_definitions(-DQT_STATICPLUGIN)
|
||||||
@ -187,7 +185,6 @@ add_subdirectory(common)
|
|||||||
add_subdirectory(core)
|
add_subdirectory(core)
|
||||||
add_subdirectory(audio_core)
|
add_subdirectory(audio_core)
|
||||||
add_subdirectory(video_core)
|
add_subdirectory(video_core)
|
||||||
add_subdirectory(hid_core)
|
|
||||||
add_subdirectory(network)
|
add_subdirectory(network)
|
||||||
add_subdirectory(input_common)
|
add_subdirectory(input_common)
|
||||||
add_subdirectory(frontend_common)
|
add_subdirectory(frontend_common)
|
||||||
|
@ -3,17 +3,16 @@
|
|||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import kotlin.collections.setOf
|
import kotlin.collections.setOf
|
||||||
|
import org.jetbrains.kotlin.konan.properties.Properties
|
||||||
import org.jlleitschuh.gradle.ktlint.reporter.ReporterType
|
import org.jlleitschuh.gradle.ktlint.reporter.ReporterType
|
||||||
import com.github.triplet.gradle.androidpublisher.ReleaseStatus
|
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.application")
|
id("com.android.application")
|
||||||
id("org.jetbrains.kotlin.android")
|
id("org.jetbrains.kotlin.android")
|
||||||
id("kotlin-parcelize")
|
id("kotlin-parcelize")
|
||||||
kotlin("plugin.serialization") version "1.9.20"
|
kotlin("plugin.serialization") version "1.8.21"
|
||||||
id("androidx.navigation.safeargs.kotlin")
|
id("androidx.navigation.safeargs.kotlin")
|
||||||
id("org.jlleitschuh.gradle.ktlint") version "11.4.0"
|
id("org.jlleitschuh.gradle.ktlint") version "11.4.0"
|
||||||
id("com.github.triplet.play") version "3.8.6"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -59,7 +58,15 @@ android {
|
|||||||
targetSdk = 34
|
targetSdk = 34
|
||||||
versionName = getGitVersion()
|
versionName = getGitVersion()
|
||||||
|
|
||||||
versionCode = if (System.getenv("AUTO_VERSIONED") == "true") {
|
// If you want to use autoVersion for the versionCode, create a property in local.properties
|
||||||
|
// named "autoVersioned" and set it to "true"
|
||||||
|
val properties = Properties()
|
||||||
|
val versionProperty = try {
|
||||||
|
properties.load(project.rootProject.file("local.properties").inputStream())
|
||||||
|
properties.getProperty("autoVersioned") ?: ""
|
||||||
|
} catch (e: Exception) { "" }
|
||||||
|
|
||||||
|
versionCode = if (versionProperty == "true") {
|
||||||
autoVersion
|
autoVersion
|
||||||
} else {
|
} else {
|
||||||
1
|
1
|
||||||
@ -75,8 +82,8 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val keystoreFile = System.getenv("ANDROID_KEYSTORE_FILE")
|
val keystoreFile = System.getenv("ANDROID_KEYSTORE_FILE")
|
||||||
signingConfigs {
|
|
||||||
if (keystoreFile != null) {
|
if (keystoreFile != null) {
|
||||||
|
signingConfigs {
|
||||||
create("release") {
|
create("release") {
|
||||||
storeFile = file(keystoreFile)
|
storeFile = file(keystoreFile)
|
||||||
storePassword = System.getenv("ANDROID_KEYSTORE_PASS")
|
storePassword = System.getenv("ANDROID_KEYSTORE_PASS")
|
||||||
@ -84,12 +91,6 @@ android {
|
|||||||
keyPassword = System.getenv("ANDROID_KEYSTORE_PASS")
|
keyPassword = System.getenv("ANDROID_KEYSTORE_PASS")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
create("default") {
|
|
||||||
storeFile = file("$projectDir/debug.keystore")
|
|
||||||
storePassword = "android"
|
|
||||||
keyAlias = "androiddebugkey"
|
|
||||||
keyPassword = "android"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Define build types, which are orthogonal to product flavors.
|
// Define build types, which are orthogonal to product flavors.
|
||||||
@ -100,7 +101,7 @@ android {
|
|||||||
signingConfig = if (keystoreFile != null) {
|
signingConfig = if (keystoreFile != null) {
|
||||||
signingConfigs.getByName("release")
|
signingConfigs.getByName("release")
|
||||||
} else {
|
} else {
|
||||||
signingConfigs.getByName("default")
|
signingConfigs.getByName("debug")
|
||||||
}
|
}
|
||||||
|
|
||||||
resValue("string", "app_name_suffixed", "yuzu")
|
resValue("string", "app_name_suffixed", "yuzu")
|
||||||
@ -117,7 +118,7 @@ android {
|
|||||||
register("relWithDebInfo") {
|
register("relWithDebInfo") {
|
||||||
isDefault = true
|
isDefault = true
|
||||||
resValue("string", "app_name_suffixed", "yuzu Debug Release")
|
resValue("string", "app_name_suffixed", "yuzu Debug Release")
|
||||||
signingConfig = signingConfigs.getByName("default")
|
signingConfig = signingConfigs.getByName("debug")
|
||||||
isMinifyEnabled = true
|
isMinifyEnabled = true
|
||||||
isDebuggable = true
|
isDebuggable = true
|
||||||
proguardFiles(
|
proguardFiles(
|
||||||
@ -132,7 +133,6 @@ android {
|
|||||||
// Signed by debug key disallowing distribution on Play Store.
|
// Signed by debug key disallowing distribution on Play Store.
|
||||||
// Attaches 'debug' suffix to version and package name, allowing installation alongside the release build.
|
// Attaches 'debug' suffix to version and package name, allowing installation alongside the release build.
|
||||||
debug {
|
debug {
|
||||||
signingConfig = signingConfigs.getByName("default")
|
|
||||||
resValue("string", "app_name_suffixed", "yuzu Debug")
|
resValue("string", "app_name_suffixed", "yuzu Debug")
|
||||||
isDebuggable = true
|
isDebuggable = true
|
||||||
isJniDebuggable = true
|
isJniDebuggable = true
|
||||||
@ -188,15 +188,8 @@ tasks.create<Delete>("ktlintReset") {
|
|||||||
delete(File(buildDir.path + File.separator + "intermediates/ktLint"))
|
delete(File(buildDir.path + File.separator + "intermediates/ktLint"))
|
||||||
}
|
}
|
||||||
|
|
||||||
val showFormatHelp = {
|
|
||||||
logger.lifecycle(
|
|
||||||
"If this check fails, please try running \"gradlew ktlintFormat\" for automatic " +
|
|
||||||
"codestyle fixes"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
tasks.getByPath("ktlintKotlinScriptCheck").doFirst { showFormatHelp.invoke() }
|
|
||||||
tasks.getByPath("ktlintMainSourceSetCheck").doFirst { showFormatHelp.invoke() }
|
|
||||||
tasks.getByPath("loadKtlintReporters").dependsOn("ktlintReset")
|
tasks.getByPath("loadKtlintReporters").dependsOn("ktlintReset")
|
||||||
|
tasks.getByPath("preBuild").dependsOn("ktlintCheck")
|
||||||
|
|
||||||
ktlint {
|
ktlint {
|
||||||
version.set("0.47.1")
|
version.set("0.47.1")
|
||||||
@ -214,15 +207,6 @@ ktlint {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
play {
|
|
||||||
val keyPath = System.getenv("SERVICE_ACCOUNT_KEY_PATH")
|
|
||||||
if (keyPath != null) {
|
|
||||||
serviceAccountCredentials.set(File(keyPath))
|
|
||||||
}
|
|
||||||
track.set(System.getenv("STORE_TRACK") ?: "internal")
|
|
||||||
releaseStatus.set(ReleaseStatus.COMPLETED)
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("androidx.core:core-ktx:1.12.0")
|
implementation("androidx.core:core-ktx:1.12.0")
|
||||||
implementation("androidx.appcompat:appcompat:1.6.1")
|
implementation("androidx.appcompat:appcompat:1.6.1")
|
||||||
@ -244,39 +228,71 @@ dependencies {
|
|||||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
|
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun runGitCommand(command: List<String>): String {
|
fun getGitVersion(): String {
|
||||||
return try {
|
var versionName = "0.0"
|
||||||
ProcessBuilder(command)
|
|
||||||
|
try {
|
||||||
|
versionName = ProcessBuilder("git", "describe", "--always", "--long")
|
||||||
.directory(project.rootDir)
|
.directory(project.rootDir)
|
||||||
.redirectOutput(ProcessBuilder.Redirect.PIPE)
|
.redirectOutput(ProcessBuilder.Redirect.PIPE)
|
||||||
.redirectError(ProcessBuilder.Redirect.PIPE)
|
.redirectError(ProcessBuilder.Redirect.PIPE)
|
||||||
.start().inputStream.bufferedReader().use { it.readText() }
|
.start().inputStream.bufferedReader().use { it.readText() }
|
||||||
.trim()
|
.trim()
|
||||||
|
.replace(Regex("(-0)?-[^-]+$"), "")
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logger.error("Cannot find git")
|
logger.error("Cannot find git, defaulting to dummy version number")
|
||||||
""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (System.getenv("GITHUB_ACTIONS") != null) {
|
||||||
|
val gitTag = System.getenv("GIT_TAG_NAME")
|
||||||
|
versionName = gitTag ?: versionName
|
||||||
|
}
|
||||||
|
|
||||||
|
return versionName
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getGitVersion(): String {
|
fun getGitHash(): String {
|
||||||
val gitVersion = runGitCommand(
|
try {
|
||||||
listOf(
|
val processBuilder = ProcessBuilder("git", "rev-parse", "--short", "HEAD")
|
||||||
"git",
|
processBuilder.directory(project.rootDir)
|
||||||
"describe",
|
val process = processBuilder.start()
|
||||||
"--always",
|
val inputStream = process.inputStream
|
||||||
"--long"
|
val errorStream = process.errorStream
|
||||||
)
|
process.waitFor()
|
||||||
).replace(Regex("(-0)?-[^-]+$"), "")
|
|
||||||
val versionName = if (System.getenv("GITHUB_ACTIONS") != null) {
|
return if (process.exitValue() == 0) {
|
||||||
System.getenv("GIT_TAG_NAME") ?: gitVersion
|
inputStream.bufferedReader()
|
||||||
|
.use { it.readText().trim() } // return the value of gitHash
|
||||||
} else {
|
} else {
|
||||||
gitVersion
|
val errorMessage = errorStream.bufferedReader().use { it.readText().trim() }
|
||||||
|
logger.error("Error running git command: $errorMessage")
|
||||||
|
"dummy-hash" // return a dummy hash value in case of an error
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logger.error("$e: Cannot find git, defaulting to dummy build hash")
|
||||||
|
return "dummy-hash" // return a dummy hash value in case of an error
|
||||||
}
|
}
|
||||||
return versionName.ifEmpty { "0.0" }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getGitHash(): String =
|
fun getBranch(): String {
|
||||||
runGitCommand(listOf("git", "rev-parse", "--short", "HEAD")).ifEmpty { "dummy-hash" }
|
try {
|
||||||
|
val processBuilder = ProcessBuilder("git", "rev-parse", "--abbrev-ref", "HEAD")
|
||||||
|
processBuilder.directory(project.rootDir)
|
||||||
|
val process = processBuilder.start()
|
||||||
|
val inputStream = process.inputStream
|
||||||
|
val errorStream = process.errorStream
|
||||||
|
process.waitFor()
|
||||||
|
|
||||||
fun getBranch(): String =
|
return if (process.exitValue() == 0) {
|
||||||
runGitCommand(listOf("git", "rev-parse", "--abbrev-ref", "HEAD")).ifEmpty { "dummy-hash" }
|
inputStream.bufferedReader()
|
||||||
|
.use { it.readText().trim() } // return the value of gitHash
|
||||||
|
} else {
|
||||||
|
val errorMessage = errorStream.bufferedReader().use { it.readText().trim() }
|
||||||
|
logger.error("Error running git command: $errorMessage")
|
||||||
|
"dummy-hash" // return a dummy hash value in case of an error
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logger.error("$e: Cannot find git, defaulting to dummy build hash")
|
||||||
|
return "dummy-hash" // return a dummy hash value in case of an error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Binary file not shown.
@ -12,9 +12,10 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
<uses-feature android:name="android.hardware.vulkan.version" android:version="0x401000" android:required="true" />
|
<uses-feature android:name="android.hardware.vulkan.version" android:version="0x401000" android:required="true" />
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
||||||
<uses-permission android:name="android.permission.NFC" />
|
<uses-permission android:name="android.permission.NFC" />
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
<uses-permission android:name="android.permission.VIBRATE" />
|
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name="org.yuzu.yuzu_emu.YuzuApplication"
|
android:name="org.yuzu.yuzu_emu.YuzuApplication"
|
||||||
@ -30,9 +31,6 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
android:dataExtractionRules="@xml/data_extraction_rules_api_31"
|
android:dataExtractionRules="@xml/data_extraction_rules_api_31"
|
||||||
android:enableOnBackInvokedCallback="true">
|
android:enableOnBackInvokedCallback="true">
|
||||||
|
|
||||||
<meta-data android:name="android.game_mode_config"
|
|
||||||
android:resource="@xml/game_mode_config" />
|
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="org.yuzu.yuzu_emu.ui.main.MainActivity"
|
android:name="org.yuzu.yuzu_emu.ui.main.MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
@ -79,6 +77,10 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
android:resource="@xml/nfc_tech_filter" />
|
android:resource="@xml/nfc_tech_filter" />
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<service android:name="org.yuzu.yuzu_emu.utils.ForegroundService" android:foregroundServiceType="specialUse">
|
||||||
|
<property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE" android:value="Keep emulation running in background"/>
|
||||||
|
</service>
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name=".features.DocumentProvider"
|
android:name=".features.DocumentProvider"
|
||||||
android:authorities="${applicationId}.user"
|
android:authorities="${applicationId}.user"
|
||||||
|
@ -3,30 +3,58 @@
|
|||||||
|
|
||||||
package org.yuzu.yuzu_emu
|
package org.yuzu.yuzu_emu
|
||||||
|
|
||||||
|
import android.app.Dialog
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.os.Bundle
|
||||||
import android.text.Html
|
import android.text.Html
|
||||||
import android.text.method.LinkMovementMethod
|
import android.text.method.LinkMovementMethod
|
||||||
import android.view.Surface
|
import android.view.Surface
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.annotation.Keep
|
import androidx.annotation.Keep
|
||||||
|
import androidx.fragment.app.DialogFragment
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
import org.yuzu.yuzu_emu.activities.EmulationActivity
|
import org.yuzu.yuzu_emu.activities.EmulationActivity
|
||||||
import org.yuzu.yuzu_emu.fragments.CoreErrorDialogFragment
|
|
||||||
import org.yuzu.yuzu_emu.utils.DocumentsTree
|
import org.yuzu.yuzu_emu.utils.DocumentsTree
|
||||||
import org.yuzu.yuzu_emu.utils.FileUtil
|
import org.yuzu.yuzu_emu.utils.FileUtil
|
||||||
import org.yuzu.yuzu_emu.utils.Log
|
import org.yuzu.yuzu_emu.utils.Log
|
||||||
import org.yuzu.yuzu_emu.model.InstallResult
|
import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable
|
||||||
import org.yuzu.yuzu_emu.model.Patch
|
|
||||||
import org.yuzu.yuzu_emu.model.GameVerificationResult
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class which contains methods that interact
|
* Class which contains methods that interact
|
||||||
* with the native side of the Yuzu code.
|
* with the native side of the Yuzu code.
|
||||||
*/
|
*/
|
||||||
object NativeLibrary {
|
object NativeLibrary {
|
||||||
|
/**
|
||||||
|
* Default controller id for each device
|
||||||
|
*/
|
||||||
|
const val Player1Device = 0
|
||||||
|
const val Player2Device = 1
|
||||||
|
const val Player3Device = 2
|
||||||
|
const val Player4Device = 3
|
||||||
|
const val Player5Device = 4
|
||||||
|
const val Player6Device = 5
|
||||||
|
const val Player7Device = 6
|
||||||
|
const val Player8Device = 7
|
||||||
|
const val ConsoleDevice = 8
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controller type for each device
|
||||||
|
*/
|
||||||
|
const val ProController = 3
|
||||||
|
const val Handheld = 4
|
||||||
|
const val JoyconDual = 5
|
||||||
|
const val JoyconLeft = 6
|
||||||
|
const val JoyconRight = 7
|
||||||
|
const val GameCube = 8
|
||||||
|
const val Pokeball = 9
|
||||||
|
const val NES = 10
|
||||||
|
const val SNES = 11
|
||||||
|
const val N64 = 12
|
||||||
|
const val SegaGenesis = 13
|
||||||
|
|
||||||
@JvmField
|
@JvmField
|
||||||
var sEmulationActivity = WeakReference<EmulationActivity?>(null)
|
var sEmulationActivity = WeakReference<EmulationActivity?>(null)
|
||||||
|
|
||||||
@ -96,17 +124,120 @@ object NativeLibrary {
|
|||||||
FileUtil.getFilename(Uri.parse(path))
|
FileUtil.getFilename(Uri.parse(path))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if pro controller isn't available and handheld is
|
||||||
|
*/
|
||||||
|
external fun isHandheldOnly(): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes controller type for a specific device.
|
||||||
|
*
|
||||||
|
* @param Device The input descriptor of the gamepad.
|
||||||
|
* @param Type The NpadStyleIndex of the gamepad.
|
||||||
|
*/
|
||||||
|
external fun setDeviceType(Device: Int, Type: Int): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles event when a gamepad is connected.
|
||||||
|
*
|
||||||
|
* @param Device The input descriptor of the gamepad.
|
||||||
|
*/
|
||||||
|
external fun onGamePadConnectEvent(Device: Int): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles event when a gamepad is disconnected.
|
||||||
|
*
|
||||||
|
* @param Device The input descriptor of the gamepad.
|
||||||
|
*/
|
||||||
|
external fun onGamePadDisconnectEvent(Device: Int): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles button press events for a gamepad.
|
||||||
|
*
|
||||||
|
* @param Device The input descriptor of the gamepad.
|
||||||
|
* @param Button Key code identifying which button was pressed.
|
||||||
|
* @param Action Mask identifying which action is happening (button pressed down, or button released).
|
||||||
|
* @return If we handled the button press.
|
||||||
|
*/
|
||||||
|
external fun onGamePadButtonEvent(Device: Int, Button: Int, Action: Int): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles joystick movement events.
|
||||||
|
*
|
||||||
|
* @param Device The device ID of the gamepad.
|
||||||
|
* @param Axis The axis ID
|
||||||
|
* @param x_axis The value of the x-axis represented by the given ID.
|
||||||
|
* @param y_axis The value of the y-axis represented by the given ID.
|
||||||
|
*/
|
||||||
|
external fun onGamePadJoystickEvent(
|
||||||
|
Device: Int,
|
||||||
|
Axis: Int,
|
||||||
|
x_axis: Float,
|
||||||
|
y_axis: Float
|
||||||
|
): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles motion events.
|
||||||
|
*
|
||||||
|
* @param delta_timestamp The finger id corresponding to this event
|
||||||
|
* @param gyro_x,gyro_y,gyro_z The value of the accelerometer sensor.
|
||||||
|
* @param accel_x,accel_y,accel_z The value of the y-axis
|
||||||
|
*/
|
||||||
|
external fun onGamePadMotionEvent(
|
||||||
|
Device: Int,
|
||||||
|
delta_timestamp: Long,
|
||||||
|
gyro_x: Float,
|
||||||
|
gyro_y: Float,
|
||||||
|
gyro_z: Float,
|
||||||
|
accel_x: Float,
|
||||||
|
accel_y: Float,
|
||||||
|
accel_z: Float
|
||||||
|
): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signals and load a nfc tag
|
||||||
|
*
|
||||||
|
* @param data Byte array containing all the data from a nfc tag
|
||||||
|
*/
|
||||||
|
external fun onReadNfcTag(data: ByteArray?): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes current loaded nfc tag
|
||||||
|
*/
|
||||||
|
external fun onRemoveNfcTag(): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles touch press events.
|
||||||
|
*
|
||||||
|
* @param finger_id The finger id corresponding to this event
|
||||||
|
* @param x_axis The value of the x-axis.
|
||||||
|
* @param y_axis The value of the y-axis.
|
||||||
|
*/
|
||||||
|
external fun onTouchPressed(finger_id: Int, x_axis: Float, y_axis: Float)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles touch movement.
|
||||||
|
*
|
||||||
|
* @param x_axis The value of the instantaneous x-axis.
|
||||||
|
* @param y_axis The value of the instantaneous y-axis.
|
||||||
|
*/
|
||||||
|
external fun onTouchMoved(finger_id: Int, x_axis: Float, y_axis: Float)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles touch release events.
|
||||||
|
*
|
||||||
|
* @param finger_id The finger id corresponding to this event
|
||||||
|
*/
|
||||||
|
external fun onTouchReleased(finger_id: Int)
|
||||||
|
|
||||||
external fun setAppDirectory(directory: String)
|
external fun setAppDirectory(directory: String)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Installs a nsp or xci file to nand
|
* Installs a nsp or xci file to nand
|
||||||
* @param filename String representation of file uri
|
* @param filename String representation of file uri
|
||||||
* @return int representation of [InstallResult]
|
* @param extension Lowercase string representation of file extension without "."
|
||||||
*/
|
*/
|
||||||
external fun installFileToNand(
|
external fun installFileToNand(filename: String, extension: String): Int
|
||||||
filename: String,
|
|
||||||
callback: (max: Long, progress: Long) -> Boolean
|
|
||||||
): Int
|
|
||||||
|
|
||||||
external fun doesUpdateMatchProgram(programId: String, updatePath: String): Boolean
|
external fun doesUpdateMatchProgram(programId: String, updatePath: String): Boolean
|
||||||
|
|
||||||
@ -124,7 +255,7 @@ object NativeLibrary {
|
|||||||
/**
|
/**
|
||||||
* Begins emulation.
|
* Begins emulation.
|
||||||
*/
|
*/
|
||||||
external fun run(path: String?, programIndex: Int, frontendInitiated: Boolean)
|
external fun run(path: String?)
|
||||||
|
|
||||||
// Surface Handling
|
// Surface Handling
|
||||||
external fun surfaceChanged(surf: Surface?)
|
external fun surfaceChanged(surf: Surface?)
|
||||||
@ -166,11 +297,6 @@ object NativeLibrary {
|
|||||||
*/
|
*/
|
||||||
external fun getCpuBackend(): String
|
external fun getCpuBackend(): String
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the current GPU Driver.
|
|
||||||
*/
|
|
||||||
external fun getGpuDriver(): String
|
|
||||||
|
|
||||||
external fun applySettings()
|
external fun applySettings()
|
||||||
|
|
||||||
external fun logSettings()
|
external fun logSettings()
|
||||||
@ -181,13 +307,46 @@ object NativeLibrary {
|
|||||||
ErrorUnknown
|
ErrorUnknown
|
||||||
}
|
}
|
||||||
|
|
||||||
var coreErrorAlertResult = false
|
private var coreErrorAlertResult = false
|
||||||
val coreErrorAlertLock = Object()
|
private val coreErrorAlertLock = Object()
|
||||||
|
|
||||||
|
class CoreErrorDialogFragment : DialogFragment() {
|
||||||
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
|
val title = requireArguments().serializable<String>("title")
|
||||||
|
val message = requireArguments().serializable<String>("message")
|
||||||
|
|
||||||
|
return MaterialAlertDialogBuilder(requireActivity())
|
||||||
|
.setTitle(title)
|
||||||
|
.setMessage(message)
|
||||||
|
.setPositiveButton(R.string.continue_button, null)
|
||||||
|
.setNegativeButton(R.string.abort_button) { _: DialogInterface?, _: Int ->
|
||||||
|
coreErrorAlertResult = false
|
||||||
|
synchronized(coreErrorAlertLock) { coreErrorAlertLock.notify() }
|
||||||
|
}
|
||||||
|
.create()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDismiss(dialog: DialogInterface) {
|
||||||
|
coreErrorAlertResult = true
|
||||||
|
synchronized(coreErrorAlertLock) { coreErrorAlertLock.notify() }
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun newInstance(title: String?, message: String?): CoreErrorDialogFragment {
|
||||||
|
val frag = CoreErrorDialogFragment()
|
||||||
|
val args = Bundle()
|
||||||
|
args.putString("title", title)
|
||||||
|
args.putString("message", message)
|
||||||
|
frag.arguments = args
|
||||||
|
return frag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun onCoreErrorImpl(title: String, message: String) {
|
private fun onCoreErrorImpl(title: String, message: String) {
|
||||||
val emulationActivity = sEmulationActivity.get()
|
val emulationActivity = sEmulationActivity.get()
|
||||||
if (emulationActivity == null) {
|
if (emulationActivity == null) {
|
||||||
Log.error("[NativeLibrary] EmulationActivity not present")
|
error("[NativeLibrary] EmulationActivity not present")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -203,7 +362,7 @@ object NativeLibrary {
|
|||||||
fun onCoreError(error: CoreError?, details: String): Boolean {
|
fun onCoreError(error: CoreError?, details: String): Boolean {
|
||||||
val emulationActivity = sEmulationActivity.get()
|
val emulationActivity = sEmulationActivity.get()
|
||||||
if (emulationActivity == null) {
|
if (emulationActivity == null) {
|
||||||
Log.error("[NativeLibrary] EmulationActivity not present")
|
error("[NativeLibrary] EmulationActivity not present")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -234,7 +393,7 @@ object NativeLibrary {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Show the AlertDialog on the main thread.
|
// Show the AlertDialog on the main thread.
|
||||||
emulationActivity.runOnUiThread { onCoreErrorImpl(title, message) }
|
emulationActivity.runOnUiThread(Runnable { onCoreErrorImpl(title, message) })
|
||||||
|
|
||||||
// Wait for the lock to notify that it is complete.
|
// Wait for the lock to notify that it is complete.
|
||||||
synchronized(coreErrorAlertLock) { coreErrorAlertLock.wait() }
|
synchronized(coreErrorAlertLock) { coreErrorAlertLock.wait() }
|
||||||
@ -319,12 +478,6 @@ object NativeLibrary {
|
|||||||
sEmulationActivity.get()!!.onEmulationStopped(status)
|
sEmulationActivity.get()!!.onEmulationStopped(status)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Keep
|
|
||||||
@JvmStatic
|
|
||||||
fun onProgramChanged(programIndex: Int) {
|
|
||||||
sEmulationActivity.get()!!.onProgramChanged(programIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logs the Yuzu version, Android version and, CPU.
|
* Logs the Yuzu version, Android version and, CPU.
|
||||||
*/
|
*/
|
||||||
@ -382,49 +535,9 @@ object NativeLibrary {
|
|||||||
*
|
*
|
||||||
* @param path Path to game file. Can be a [Uri].
|
* @param path Path to game file. Can be a [Uri].
|
||||||
* @param programId String representation of a game's program ID
|
* @param programId String representation of a game's program ID
|
||||||
* @return Array of available patches
|
* @return Array of pairs where the first value is the name of an addon and the second is the version
|
||||||
*/
|
*/
|
||||||
external fun getPatchesForFile(path: String, programId: String): Array<Patch>?
|
external fun getAddonsForFile(path: String, programId: String): Array<Pair<String, String>>?
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes an update for a given [programId]
|
|
||||||
* @param programId String representation of a game's program ID
|
|
||||||
*/
|
|
||||||
external fun removeUpdate(programId: String)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes all DLC for a [programId]
|
|
||||||
* @param programId String representation of a game's program ID
|
|
||||||
*/
|
|
||||||
external fun removeDLC(programId: String)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes a mod installed for a given [programId]
|
|
||||||
* @param programId String representation of a game's program ID
|
|
||||||
* @param name The name of a mod as given by [getPatchesForFile]. This corresponds with the name
|
|
||||||
* of the mod's directory in a game's load folder.
|
|
||||||
*/
|
|
||||||
external fun removeMod(programId: String, name: String)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verifies all installed content
|
|
||||||
* @param callback UI callback for verification progress. Return true in the callback to cancel.
|
|
||||||
* @return Array of content that failed verification. Successful if empty.
|
|
||||||
*/
|
|
||||||
external fun verifyInstalledContents(
|
|
||||||
callback: (max: Long, progress: Long) -> Boolean
|
|
||||||
): Array<String>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verifies the contents of a game
|
|
||||||
* @param path String path to a game
|
|
||||||
* @param callback UI callback for verification progress. Return true in the callback to cancel.
|
|
||||||
* @return Int that is meant to be converted to a [GameVerificationResult]
|
|
||||||
*/
|
|
||||||
external fun verifyGameContents(
|
|
||||||
path: String,
|
|
||||||
callback: (max: Long, progress: Long) -> Boolean
|
|
||||||
): Int
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the save location for a specific game
|
* Gets the save location for a specific game
|
||||||
@ -434,15 +547,6 @@ object NativeLibrary {
|
|||||||
*/
|
*/
|
||||||
external fun getSavePath(programId: String): String
|
external fun getSavePath(programId: String): String
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the root save directory for the default profile as either
|
|
||||||
* /user/save/account/<user id raw string> or /user/save/000...000/<user id>
|
|
||||||
*
|
|
||||||
* @param future If true, returns the /user/save/account/... directory
|
|
||||||
* @return Save data path that may not exist yet
|
|
||||||
*/
|
|
||||||
external fun getDefaultProfileSaveDataRoot(future: Boolean): String
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a file to the manual filesystem provider in our EmulationSession instance
|
* Adds a file to the manual filesystem provider in our EmulationSession instance
|
||||||
* @param path Path to the file we're adding. Can be a string representation of a [Uri] or
|
* @param path Path to the file we're adding. Can be a string representation of a [Uri] or
|
||||||
@ -456,7 +560,55 @@ object NativeLibrary {
|
|||||||
external fun clearFilesystemProvider()
|
external fun clearFilesystemProvider()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if all necessary keys are present for decryption
|
* Button type for use in onTouchEvent
|
||||||
*/
|
*/
|
||||||
external fun areKeysPresent(): Boolean
|
object ButtonType {
|
||||||
|
const val BUTTON_A = 0
|
||||||
|
const val BUTTON_B = 1
|
||||||
|
const val BUTTON_X = 2
|
||||||
|
const val BUTTON_Y = 3
|
||||||
|
const val STICK_L = 4
|
||||||
|
const val STICK_R = 5
|
||||||
|
const val TRIGGER_L = 6
|
||||||
|
const val TRIGGER_R = 7
|
||||||
|
const val TRIGGER_ZL = 8
|
||||||
|
const val TRIGGER_ZR = 9
|
||||||
|
const val BUTTON_PLUS = 10
|
||||||
|
const val BUTTON_MINUS = 11
|
||||||
|
const val DPAD_LEFT = 12
|
||||||
|
const val DPAD_UP = 13
|
||||||
|
const val DPAD_RIGHT = 14
|
||||||
|
const val DPAD_DOWN = 15
|
||||||
|
const val BUTTON_SL = 16
|
||||||
|
const val BUTTON_SR = 17
|
||||||
|
const val BUTTON_HOME = 18
|
||||||
|
const val BUTTON_CAPTURE = 19
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stick type for use in onTouchEvent
|
||||||
|
*/
|
||||||
|
object StickType {
|
||||||
|
const val STICK_L = 0
|
||||||
|
const val STICK_R = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Button states
|
||||||
|
*/
|
||||||
|
object ButtonState {
|
||||||
|
const val RELEASED = 0
|
||||||
|
const val PRESSED = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Result from installFileToNand
|
||||||
|
*/
|
||||||
|
object InstallFileToNandResult {
|
||||||
|
const val Success = 0
|
||||||
|
const val SuccessFileOverwritten = 1
|
||||||
|
const val Error = 2
|
||||||
|
const val ErrorBaseGame = 3
|
||||||
|
const val ErrorFilenameExtension = 4
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@ import android.app.Application
|
|||||||
import android.app.NotificationChannel
|
import android.app.NotificationChannel
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import org.yuzu.yuzu_emu.features.input.NativeInput
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
|
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
|
||||||
import org.yuzu.yuzu_emu.utils.DocumentsTree
|
import org.yuzu.yuzu_emu.utils.DocumentsTree
|
||||||
@ -18,6 +17,17 @@ fun Context.getPublicFilesDir(): File = getExternalFilesDir(null) ?: filesDir
|
|||||||
|
|
||||||
class YuzuApplication : Application() {
|
class YuzuApplication : Application() {
|
||||||
private fun createNotificationChannels() {
|
private fun createNotificationChannels() {
|
||||||
|
val emulationChannel = NotificationChannel(
|
||||||
|
getString(R.string.emulation_notification_channel_id),
|
||||||
|
getString(R.string.emulation_notification_channel_name),
|
||||||
|
NotificationManager.IMPORTANCE_LOW
|
||||||
|
)
|
||||||
|
emulationChannel.description = getString(
|
||||||
|
R.string.emulation_notification_channel_description
|
||||||
|
)
|
||||||
|
emulationChannel.setSound(null, null)
|
||||||
|
emulationChannel.vibrationPattern = null
|
||||||
|
|
||||||
val noticeChannel = NotificationChannel(
|
val noticeChannel = NotificationChannel(
|
||||||
getString(R.string.notice_notification_channel_id),
|
getString(R.string.notice_notification_channel_id),
|
||||||
getString(R.string.notice_notification_channel_name),
|
getString(R.string.notice_notification_channel_name),
|
||||||
@ -29,6 +39,7 @@ class YuzuApplication : Application() {
|
|||||||
// Register the channel with the system; you can't change the importance
|
// Register the channel with the system; you can't change the importance
|
||||||
// or other notification behaviors after this
|
// or other notification behaviors after this
|
||||||
val notificationManager = getSystemService(NotificationManager::class.java)
|
val notificationManager = getSystemService(NotificationManager::class.java)
|
||||||
|
notificationManager.createNotificationChannel(emulationChannel)
|
||||||
notificationManager.createNotificationChannel(noticeChannel)
|
notificationManager.createNotificationChannel(noticeChannel)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,7 +49,6 @@ class YuzuApplication : Application() {
|
|||||||
documentsTree = DocumentsTree()
|
documentsTree = DocumentsTree()
|
||||||
DirectoryInitialization.start()
|
DirectoryInitialization.start()
|
||||||
GpuDriverHelper.initializeDriverParameters()
|
GpuDriverHelper.initializeDriverParameters()
|
||||||
NativeInput.reloadInputDevices()
|
|
||||||
NativeLibrary.logDeviceInfo()
|
NativeLibrary.logDeviceInfo()
|
||||||
Log.logDeviceInfo()
|
Log.logDeviceInfo()
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
package org.yuzu.yuzu_emu.activities
|
package org.yuzu.yuzu_emu.activities
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.Activity
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.app.PictureInPictureParams
|
import android.app.PictureInPictureParams
|
||||||
import android.app.RemoteAction
|
import android.app.RemoteAction
|
||||||
@ -39,18 +40,16 @@ import org.yuzu.yuzu_emu.NativeLibrary
|
|||||||
import org.yuzu.yuzu_emu.R
|
import org.yuzu.yuzu_emu.R
|
||||||
import org.yuzu.yuzu_emu.YuzuApplication
|
import org.yuzu.yuzu_emu.YuzuApplication
|
||||||
import org.yuzu.yuzu_emu.databinding.ActivityEmulationBinding
|
import org.yuzu.yuzu_emu.databinding.ActivityEmulationBinding
|
||||||
import org.yuzu.yuzu_emu.features.input.NativeInput
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
|
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||||
import org.yuzu.yuzu_emu.model.EmulationViewModel
|
import org.yuzu.yuzu_emu.model.EmulationViewModel
|
||||||
import org.yuzu.yuzu_emu.model.Game
|
import org.yuzu.yuzu_emu.model.Game
|
||||||
|
import org.yuzu.yuzu_emu.utils.ForegroundService
|
||||||
import org.yuzu.yuzu_emu.utils.InputHandler
|
import org.yuzu.yuzu_emu.utils.InputHandler
|
||||||
import org.yuzu.yuzu_emu.utils.Log
|
import org.yuzu.yuzu_emu.utils.Log
|
||||||
import org.yuzu.yuzu_emu.utils.MemoryUtil
|
import org.yuzu.yuzu_emu.utils.MemoryUtil
|
||||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
|
||||||
import org.yuzu.yuzu_emu.utils.NfcReader
|
import org.yuzu.yuzu_emu.utils.NfcReader
|
||||||
import org.yuzu.yuzu_emu.utils.ParamPackage
|
|
||||||
import org.yuzu.yuzu_emu.utils.ThemeHelper
|
import org.yuzu.yuzu_emu.utils.ThemeHelper
|
||||||
import java.text.NumberFormat
|
import java.text.NumberFormat
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
@ -66,6 +65,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||||||
private var motionTimestamp: Long = 0
|
private var motionTimestamp: Long = 0
|
||||||
private var flipMotionOrientation: Boolean = false
|
private var flipMotionOrientation: Boolean = false
|
||||||
|
|
||||||
|
private var controllerIds = InputHandler.getGameControllerIds()
|
||||||
|
|
||||||
private val actionPause = "ACTION_EMULATOR_PAUSE"
|
private val actionPause = "ACTION_EMULATOR_PAUSE"
|
||||||
private val actionPlay = "ACTION_EMULATOR_PLAY"
|
private val actionPlay = "ACTION_EMULATOR_PLAY"
|
||||||
private val actionMute = "ACTION_EMULATOR_MUTE"
|
private val actionMute = "ACTION_EMULATOR_MUTE"
|
||||||
@ -73,39 +74,18 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||||||
|
|
||||||
private val emulationViewModel: EmulationViewModel by viewModels()
|
private val emulationViewModel: EmulationViewModel by viewModels()
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
stopForegroundService(this)
|
||||||
|
emulationViewModel.clear()
|
||||||
|
super.onDestroy()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
Log.gameLaunched = true
|
Log.gameLaunched = true
|
||||||
ThemeHelper.setTheme(this)
|
ThemeHelper.setTheme(this)
|
||||||
|
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
InputHandler.updateControllerData()
|
|
||||||
val players = NativeConfig.getInputSettings(true)
|
|
||||||
var hasConfiguredControllers = false
|
|
||||||
players.forEach {
|
|
||||||
if (it.hasMapping()) {
|
|
||||||
hasConfiguredControllers = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!hasConfiguredControllers && InputHandler.androidControllers.isNotEmpty()) {
|
|
||||||
var params: ParamPackage? = null
|
|
||||||
for (controller in InputHandler.registeredControllers) {
|
|
||||||
if (controller.get("port", -1) == 0) {
|
|
||||||
params = controller
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params != null) {
|
|
||||||
NativeInput.updateMappingsWithDefault(
|
|
||||||
0,
|
|
||||||
params,
|
|
||||||
params.get("display", getString(R.string.unknown))
|
|
||||||
)
|
|
||||||
NativeConfig.saveGlobalConfig()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
binding = ActivityEmulationBinding.inflate(layoutInflater)
|
binding = ActivityEmulationBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
@ -123,6 +103,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||||||
nfcReader = NfcReader(this)
|
nfcReader = NfcReader(this)
|
||||||
nfcReader.initialize()
|
nfcReader.initialize()
|
||||||
|
|
||||||
|
InputHandler.initialize()
|
||||||
|
|
||||||
val preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
|
val preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
|
||||||
if (!preferences.getBoolean(Settings.PREF_MEMORY_WARNING_SHOWN, false)) {
|
if (!preferences.getBoolean(Settings.PREF_MEMORY_WARNING_SHOWN, false)) {
|
||||||
if (MemoryUtil.isLessThan(MemoryUtil.REQUIRED_MEMORY, MemoryUtil.totalMemory)) {
|
if (MemoryUtil.isLessThan(MemoryUtil.REQUIRED_MEMORY, MemoryUtil.totalMemory)) {
|
||||||
@ -144,6 +126,10 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||||||
.apply()
|
.apply()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Start a foreground service to prevent the app from getting killed in the background
|
||||||
|
val startIntent = Intent(this, ForegroundService::class.java)
|
||||||
|
startForegroundService(startIntent)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
|
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
|
||||||
@ -173,7 +159,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||||||
super.onResume()
|
super.onResume()
|
||||||
nfcReader.startScanning()
|
nfcReader.startScanning()
|
||||||
startMotionSensorListener()
|
startMotionSensorListener()
|
||||||
InputHandler.updateControllerData()
|
InputHandler.updateControllerIds()
|
||||||
|
|
||||||
buildPictureInPictureParams()
|
buildPictureInPictureParams()
|
||||||
}
|
}
|
||||||
@ -198,7 +184,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||||||
super.onNewIntent(intent)
|
super.onNewIntent(intent)
|
||||||
setIntent(intent)
|
setIntent(intent)
|
||||||
nfcReader.onNewIntent(intent)
|
nfcReader.onNewIntent(intent)
|
||||||
InputHandler.updateControllerData()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
||||||
@ -208,10 +193,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||||||
return super.dispatchKeyEvent(event)
|
return super.dispatchKeyEvent(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (emulationViewModel.drawerOpen.value) {
|
|
||||||
return super.dispatchKeyEvent(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
return InputHandler.dispatchKeyEvent(event)
|
return InputHandler.dispatchKeyEvent(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,10 +203,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||||||
return super.dispatchGenericMotionEvent(event)
|
return super.dispatchGenericMotionEvent(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (emulationViewModel.drawerOpen.value) {
|
|
||||||
return super.dispatchGenericMotionEvent(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't attempt to do anything if we are disconnecting a device.
|
// Don't attempt to do anything if we are disconnecting a device.
|
||||||
if (event.actionMasked == MotionEvent.ACTION_CANCEL) {
|
if (event.actionMasked == MotionEvent.ACTION_CANCEL) {
|
||||||
return true
|
return true
|
||||||
@ -271,8 +248,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||||||
}
|
}
|
||||||
val deltaTimestamp = (event.timestamp - motionTimestamp) / 1000
|
val deltaTimestamp = (event.timestamp - motionTimestamp) / 1000
|
||||||
motionTimestamp = event.timestamp
|
motionTimestamp = event.timestamp
|
||||||
NativeInput.onDeviceMotionEvent(
|
NativeLibrary.onGamePadMotionEvent(
|
||||||
NativeInput.Player1Device,
|
NativeLibrary.Player1Device,
|
||||||
deltaTimestamp,
|
deltaTimestamp,
|
||||||
gyro[0],
|
gyro[0],
|
||||||
gyro[1],
|
gyro[1],
|
||||||
@ -281,8 +258,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||||||
accel[1],
|
accel[1],
|
||||||
accel[2]
|
accel[2]
|
||||||
)
|
)
|
||||||
NativeInput.onDeviceMotionEvent(
|
NativeLibrary.onGamePadMotionEvent(
|
||||||
NativeInput.ConsoleDevice,
|
NativeLibrary.ConsoleDevice,
|
||||||
deltaTimestamp,
|
deltaTimestamp,
|
||||||
gyro[0],
|
gyro[0],
|
||||||
gyro[1],
|
gyro[1],
|
||||||
@ -461,14 +438,9 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun onEmulationStopped(status: Int) {
|
fun onEmulationStopped(status: Int) {
|
||||||
if (status == 0 && emulationViewModel.programChanged.value == -1) {
|
if (status == 0) {
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
emulationViewModel.setEmulationStopped(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onProgramChanged(programIndex: Int) {
|
|
||||||
emulationViewModel.setProgramChanged(programIndex)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startMotionSensorListener() {
|
private fun startMotionSensorListener() {
|
||||||
@ -497,6 +469,12 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||||||
activity.startActivity(launcher)
|
activity.startActivity(launcher)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun stopForegroundService(activity: Activity) {
|
||||||
|
val startIntent = Intent(activity, ForegroundService::class.java)
|
||||||
|
startIntent.action = ForegroundService.ACTION_STOP
|
||||||
|
activity.startForegroundService(startIntent)
|
||||||
|
}
|
||||||
|
|
||||||
private fun areCoordinatesOutside(view: View?, x: Float, y: Float): Boolean {
|
private fun areCoordinatesOutside(view: View?, x: Float, y: Float): Boolean {
|
||||||
if (view == null) {
|
if (view == null) {
|
||||||
return true
|
return true
|
||||||
|
@ -1,38 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.adapters
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import androidx.recyclerview.widget.AsyncDifferConfig
|
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
|
||||||
import androidx.recyclerview.widget.ListAdapter
|
|
||||||
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generic adapter that implements an [AsyncDifferConfig] and covers some of the basic boilerplate
|
|
||||||
* code used in every [RecyclerView].
|
|
||||||
* Type assigned to [Model] must inherit from [Object] in order to be compared properly.
|
|
||||||
* @param exact Decides whether each item will be compared by reference or by their contents
|
|
||||||
*/
|
|
||||||
abstract class AbstractDiffAdapter<Model : Any, Holder : AbstractViewHolder<Model>>(
|
|
||||||
exact: Boolean = true
|
|
||||||
) : ListAdapter<Model, Holder>(AsyncDifferConfig.Builder(DiffCallback<Model>(exact)).build()) {
|
|
||||||
override fun onBindViewHolder(holder: Holder, position: Int) =
|
|
||||||
holder.bind(currentList[position])
|
|
||||||
|
|
||||||
private class DiffCallback<Model>(val exact: Boolean) : DiffUtil.ItemCallback<Model>() {
|
|
||||||
override fun areItemsTheSame(oldItem: Model & Any, newItem: Model & Any): Boolean {
|
|
||||||
if (exact) {
|
|
||||||
return oldItem === newItem
|
|
||||||
}
|
|
||||||
return oldItem == newItem
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("DiffUtilEquals")
|
|
||||||
override fun areContentsTheSame(oldItem: Model & Any, newItem: Model & Any): Boolean {
|
|
||||||
return oldItem == newItem
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,98 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.adapters
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generic list class meant to take care of basic lists
|
|
||||||
* @param currentList The list to show initially
|
|
||||||
*/
|
|
||||||
abstract class AbstractListAdapter<Model : Any, Holder : AbstractViewHolder<Model>>(
|
|
||||||
open var currentList: List<Model>
|
|
||||||
) : RecyclerView.Adapter<Holder>() {
|
|
||||||
override fun onBindViewHolder(holder: Holder, position: Int) =
|
|
||||||
holder.bind(currentList[position])
|
|
||||||
|
|
||||||
override fun getItemCount(): Int = currentList.size
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds an item to [currentList] and notifies the underlying adapter of the change. If no parameter
|
|
||||||
* is passed in for position, [item] is added to the end of the list. Invokes [callback] last.
|
|
||||||
* @param item The item to add to the list
|
|
||||||
* @param position Index where [item] will be added
|
|
||||||
* @param callback Lambda that's called at the end of the list changes and has the added list
|
|
||||||
* position passed in as a parameter
|
|
||||||
*/
|
|
||||||
open fun addItem(item: Model, position: Int = -1, callback: ((position: Int) -> Unit)? = null) {
|
|
||||||
val newList = currentList.toMutableList()
|
|
||||||
val positionToUpdate: Int
|
|
||||||
if (position == -1) {
|
|
||||||
newList.add(item)
|
|
||||||
currentList = newList
|
|
||||||
positionToUpdate = currentList.size - 1
|
|
||||||
} else {
|
|
||||||
newList.add(position, item)
|
|
||||||
currentList = newList
|
|
||||||
positionToUpdate = position
|
|
||||||
}
|
|
||||||
onItemAdded(positionToUpdate, callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fun onItemAdded(position: Int, callback: ((Int) -> Unit)? = null) {
|
|
||||||
notifyItemInserted(position)
|
|
||||||
callback?.invoke(position)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Replaces the [item] at [position] in the [currentList] and notifies the underlying adapter
|
|
||||||
* of the change. Invokes [callback] last.
|
|
||||||
* @param item New list item
|
|
||||||
* @param position Index where [item] will replace the existing list item
|
|
||||||
* @param callback Lambda that's called at the end of the list changes and has the changed list
|
|
||||||
* position passed in as a parameter
|
|
||||||
*/
|
|
||||||
fun changeItem(item: Model, position: Int, callback: ((position: Int) -> Unit)? = null) {
|
|
||||||
val newList = currentList.toMutableList()
|
|
||||||
newList[position] = item
|
|
||||||
currentList = newList
|
|
||||||
onItemChanged(position, callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fun onItemChanged(position: Int, callback: ((Int) -> Unit)? = null) {
|
|
||||||
notifyItemChanged(position)
|
|
||||||
callback?.invoke(position)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes the list item at [position] in [currentList] and notifies the underlying adapter
|
|
||||||
* of the change. Invokes [callback] last.
|
|
||||||
* @param position Index where the list item will be removed
|
|
||||||
* @param callback Lambda that's called at the end of the list changes and has the removed list
|
|
||||||
* position passed in as a parameter
|
|
||||||
*/
|
|
||||||
fun removeItem(position: Int, callback: ((position: Int) -> Unit)? = null) {
|
|
||||||
val newList = currentList.toMutableList()
|
|
||||||
newList.removeAt(position)
|
|
||||||
currentList = newList
|
|
||||||
onItemRemoved(position, callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fun onItemRemoved(position: Int, callback: ((Int) -> Unit)? = null) {
|
|
||||||
notifyItemRemoved(position)
|
|
||||||
callback?.invoke(position)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Replaces [currentList] with [newList] and notifies the underlying adapter of the change.
|
|
||||||
* @param newList The new list to replace [currentList]
|
|
||||||
*/
|
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
|
||||||
open fun replaceList(newList: List<Model>) {
|
|
||||||
currentList = newList
|
|
||||||
notifyDataSetChanged()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,105 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.adapters
|
|
||||||
|
|
||||||
import org.yuzu.yuzu_emu.model.SelectableItem
|
|
||||||
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generic list class meant to take care of single selection UI updates
|
|
||||||
* @param currentList The list to show initially
|
|
||||||
* @param defaultSelection The default selection to use if no list items are selected by
|
|
||||||
* [SelectableItem.selected] or if the currently selected item is removed from the list
|
|
||||||
*/
|
|
||||||
abstract class AbstractSingleSelectionList<
|
|
||||||
Model : SelectableItem,
|
|
||||||
Holder : AbstractViewHolder<Model>
|
|
||||||
>(
|
|
||||||
final override var currentList: List<Model>,
|
|
||||||
private val defaultSelection: DefaultSelection = DefaultSelection.Start
|
|
||||||
) : AbstractListAdapter<Model, Holder>(currentList) {
|
|
||||||
var selectedItem = getDefaultSelection()
|
|
||||||
|
|
||||||
init {
|
|
||||||
findSelectedItem()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Changes the selection state of the [SelectableItem] that was selected and the previously selected
|
|
||||||
* item and notifies the underlying adapter of the change for those items. Invokes [callback] last.
|
|
||||||
* Does nothing if [position] is the same as the currently selected item.
|
|
||||||
* @param position Index of the item that was selected
|
|
||||||
* @param callback Lambda that's called at the end of the list changes and has the selected list
|
|
||||||
* position passed in as a parameter
|
|
||||||
*/
|
|
||||||
fun selectItem(position: Int, callback: ((position: Int) -> Unit)? = null) {
|
|
||||||
if (position == selectedItem) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val previouslySelectedItem = selectedItem
|
|
||||||
selectedItem = position
|
|
||||||
if (currentList.indices.contains(selectedItem)) {
|
|
||||||
currentList[selectedItem].onSelectionStateChanged(true)
|
|
||||||
}
|
|
||||||
if (currentList.indices.contains(previouslySelectedItem)) {
|
|
||||||
currentList[previouslySelectedItem].onSelectionStateChanged(false)
|
|
||||||
}
|
|
||||||
onItemChanged(previouslySelectedItem)
|
|
||||||
onItemChanged(selectedItem)
|
|
||||||
callback?.invoke(position)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes a given item from the list and notifies the underlying adapter of the change. If the
|
|
||||||
* currently selected item was the item that was removed, the item at the position provided
|
|
||||||
* by [defaultSelection] will be made the new selection. Invokes [callback] last.
|
|
||||||
* @param position Index of the item that was removed
|
|
||||||
* @param callback Lambda that's called at the end of the list changes and has the removed and
|
|
||||||
* selected list positions passed in as parameters
|
|
||||||
*/
|
|
||||||
fun removeSelectableItem(
|
|
||||||
position: Int,
|
|
||||||
callback: ((removedPosition: Int, selectedPosition: Int) -> Unit)?
|
|
||||||
) {
|
|
||||||
removeItem(position)
|
|
||||||
if (position == selectedItem) {
|
|
||||||
selectedItem = getDefaultSelection()
|
|
||||||
currentList[selectedItem].onSelectionStateChanged(true)
|
|
||||||
onItemChanged(selectedItem)
|
|
||||||
} else if (position < selectedItem) {
|
|
||||||
selectedItem--
|
|
||||||
}
|
|
||||||
callback?.invoke(position, selectedItem)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun addItem(item: Model, position: Int, callback: ((Int) -> Unit)?) {
|
|
||||||
super.addItem(item, position, callback)
|
|
||||||
if (position <= selectedItem && position != -1) {
|
|
||||||
selectedItem++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun replaceList(newList: List<Model>) {
|
|
||||||
super.replaceList(newList)
|
|
||||||
findSelectedItem()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun findSelectedItem() {
|
|
||||||
for (i in currentList.indices) {
|
|
||||||
if (currentList[i].selected) {
|
|
||||||
selectedItem = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getDefaultSelection(): Int =
|
|
||||||
when (defaultSelection) {
|
|
||||||
DefaultSelection.Start -> currentList.indices.first
|
|
||||||
DefaultSelection.End -> currentList.indices.last
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class DefaultSelection { Start, End }
|
|
||||||
}
|
|
@ -5,33 +5,48 @@ package org.yuzu.yuzu_emu.adapters
|
|||||||
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.AsyncDifferConfig
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import org.yuzu.yuzu_emu.databinding.ListItemAddonBinding
|
import org.yuzu.yuzu_emu.databinding.ListItemAddonBinding
|
||||||
import org.yuzu.yuzu_emu.model.Patch
|
import org.yuzu.yuzu_emu.model.Addon
|
||||||
import org.yuzu.yuzu_emu.model.AddonViewModel
|
|
||||||
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
|
||||||
|
|
||||||
class AddonAdapter(val addonViewModel: AddonViewModel) :
|
class AddonAdapter : ListAdapter<Addon, AddonAdapter.AddonViewHolder>(
|
||||||
AbstractDiffAdapter<Patch, AddonAdapter.AddonViewHolder>() {
|
AsyncDifferConfig.Builder(DiffCallback()).build()
|
||||||
|
) {
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AddonViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AddonViewHolder {
|
||||||
ListItemAddonBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
ListItemAddonBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
.also { return AddonViewHolder(it) }
|
.also { return AddonViewHolder(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int = currentList.size
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: AddonViewHolder, position: Int) =
|
||||||
|
holder.bind(currentList[position])
|
||||||
|
|
||||||
inner class AddonViewHolder(val binding: ListItemAddonBinding) :
|
inner class AddonViewHolder(val binding: ListItemAddonBinding) :
|
||||||
AbstractViewHolder<Patch>(binding) {
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
override fun bind(model: Patch) {
|
fun bind(addon: Addon) {
|
||||||
binding.root.setOnClickListener {
|
binding.root.setOnClickListener {
|
||||||
binding.addonCheckbox.isChecked = !binding.addonCheckbox.isChecked
|
binding.addonSwitch.isChecked = !binding.addonSwitch.isChecked
|
||||||
}
|
}
|
||||||
binding.title.text = model.name
|
binding.title.text = addon.title
|
||||||
binding.version.text = model.version
|
binding.version.text = addon.version
|
||||||
binding.addonCheckbox.setOnCheckedChangeListener { _, checked ->
|
binding.addonSwitch.setOnCheckedChangeListener { _, checked ->
|
||||||
model.enabled = checked
|
addon.enabled = checked
|
||||||
}
|
}
|
||||||
binding.addonCheckbox.isChecked = model.enabled
|
binding.addonSwitch.isChecked = addon.enabled
|
||||||
binding.buttonDelete.setOnClickListener {
|
|
||||||
addonViewModel.setAddonToDelete(model)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class DiffCallback : DiffUtil.ItemCallback<Addon>() {
|
||||||
|
override fun areItemsTheSame(oldItem: Addon, newItem: Addon): Boolean {
|
||||||
|
return oldItem == newItem
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun areContentsTheSame(oldItem: Addon, newItem: Addon): Boolean {
|
||||||
|
return oldItem == newItem
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,11 +4,13 @@
|
|||||||
package org.yuzu.yuzu_emu.adapters
|
package org.yuzu.yuzu_emu.adapters
|
||||||
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.core.content.res.ResourcesCompat
|
import androidx.core.content.res.ResourcesCompat
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
import androidx.navigation.findNavController
|
import androidx.navigation.findNavController
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import org.yuzu.yuzu_emu.HomeNavigationDirections
|
import org.yuzu.yuzu_emu.HomeNavigationDirections
|
||||||
import org.yuzu.yuzu_emu.NativeLibrary
|
import org.yuzu.yuzu_emu.NativeLibrary
|
||||||
import org.yuzu.yuzu_emu.R
|
import org.yuzu.yuzu_emu.R
|
||||||
@ -17,39 +19,31 @@ import org.yuzu.yuzu_emu.databinding.CardSimpleOutlinedBinding
|
|||||||
import org.yuzu.yuzu_emu.model.Applet
|
import org.yuzu.yuzu_emu.model.Applet
|
||||||
import org.yuzu.yuzu_emu.model.AppletInfo
|
import org.yuzu.yuzu_emu.model.AppletInfo
|
||||||
import org.yuzu.yuzu_emu.model.Game
|
import org.yuzu.yuzu_emu.model.Game
|
||||||
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
|
||||||
|
|
||||||
class AppletAdapter(val activity: FragmentActivity, applets: List<Applet>) :
|
class AppletAdapter(val activity: FragmentActivity, var applets: List<Applet>) :
|
||||||
AbstractListAdapter<Applet, AppletAdapter.AppletViewHolder>(applets) {
|
RecyclerView.Adapter<AppletAdapter.AppletViewHolder>(),
|
||||||
|
View.OnClickListener {
|
||||||
|
|
||||||
override fun onCreateViewHolder(
|
override fun onCreateViewHolder(
|
||||||
parent: ViewGroup,
|
parent: ViewGroup,
|
||||||
viewType: Int
|
viewType: Int
|
||||||
): AppletAdapter.AppletViewHolder {
|
): AppletAdapter.AppletViewHolder {
|
||||||
CardSimpleOutlinedBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
CardSimpleOutlinedBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
|
.apply { root.setOnClickListener(this@AppletAdapter) }
|
||||||
.also { return AppletViewHolder(it) }
|
.also { return AppletViewHolder(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class AppletViewHolder(val binding: CardSimpleOutlinedBinding) :
|
override fun onBindViewHolder(holder: AppletViewHolder, position: Int) =
|
||||||
AbstractViewHolder<Applet>(binding) {
|
holder.bind(applets[position])
|
||||||
override fun bind(model: Applet) {
|
|
||||||
binding.title.setText(model.titleId)
|
|
||||||
binding.description.setText(model.descriptionId)
|
|
||||||
binding.icon.setImageDrawable(
|
|
||||||
ResourcesCompat.getDrawable(
|
|
||||||
binding.icon.context.resources,
|
|
||||||
model.iconId,
|
|
||||||
binding.icon.context.theme
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
binding.root.setOnClickListener { onClick(model) }
|
override fun getItemCount(): Int = applets.size
|
||||||
}
|
|
||||||
|
|
||||||
fun onClick(applet: Applet) {
|
override fun onClick(view: View) {
|
||||||
|
val applet = (view.tag as AppletViewHolder).applet
|
||||||
val appletPath = NativeLibrary.getAppletLaunchPath(applet.appletInfo.entryId)
|
val appletPath = NativeLibrary.getAppletLaunchPath(applet.appletInfo.entryId)
|
||||||
if (appletPath.isEmpty()) {
|
if (appletPath.isEmpty()) {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
binding.root.context,
|
YuzuApplication.appContext,
|
||||||
R.string.applets_error_applet,
|
R.string.applets_error_applet,
|
||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_SHORT
|
||||||
).show()
|
).show()
|
||||||
@ -57,7 +51,7 @@ class AppletAdapter(val activity: FragmentActivity, applets: List<Applet>) :
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (applet.appletInfo == AppletInfo.Cabinet) {
|
if (applet.appletInfo == AppletInfo.Cabinet) {
|
||||||
binding.root.findNavController()
|
view.findNavController()
|
||||||
.navigate(R.id.action_appletLauncherFragment_to_cabinetLauncherDialogFragment)
|
.navigate(R.id.action_appletLauncherFragment_to_cabinetLauncherDialogFragment)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -68,7 +62,29 @@ class AppletAdapter(val activity: FragmentActivity, applets: List<Applet>) :
|
|||||||
path = appletPath
|
path = appletPath
|
||||||
)
|
)
|
||||||
val action = HomeNavigationDirections.actionGlobalEmulationActivity(appletGame)
|
val action = HomeNavigationDirections.actionGlobalEmulationActivity(appletGame)
|
||||||
binding.root.findNavController().navigate(action)
|
view.findNavController().navigate(action)
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class AppletViewHolder(val binding: CardSimpleOutlinedBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
|
lateinit var applet: Applet
|
||||||
|
|
||||||
|
init {
|
||||||
|
itemView.tag = this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bind(applet: Applet) {
|
||||||
|
this.applet = applet
|
||||||
|
|
||||||
|
binding.title.setText(applet.titleId)
|
||||||
|
binding.description.setText(applet.descriptionId)
|
||||||
|
binding.icon.setImageDrawable(
|
||||||
|
ResourcesCompat.getDrawable(
|
||||||
|
binding.icon.context.resources,
|
||||||
|
applet.iconId,
|
||||||
|
binding.icon.context.theme
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,10 +4,12 @@
|
|||||||
package org.yuzu.yuzu_emu.adapters
|
package org.yuzu.yuzu_emu.adapters
|
||||||
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.core.content.res.ResourcesCompat
|
import androidx.core.content.res.ResourcesCompat
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import org.yuzu.yuzu_emu.HomeNavigationDirections
|
import org.yuzu.yuzu_emu.HomeNavigationDirections
|
||||||
import org.yuzu.yuzu_emu.NativeLibrary
|
import org.yuzu.yuzu_emu.NativeLibrary
|
||||||
import org.yuzu.yuzu_emu.R
|
import org.yuzu.yuzu_emu.R
|
||||||
@ -17,34 +19,25 @@ import org.yuzu.yuzu_emu.model.CabinetMode
|
|||||||
import org.yuzu.yuzu_emu.adapters.CabinetLauncherDialogAdapter.CabinetModeViewHolder
|
import org.yuzu.yuzu_emu.adapters.CabinetLauncherDialogAdapter.CabinetModeViewHolder
|
||||||
import org.yuzu.yuzu_emu.model.AppletInfo
|
import org.yuzu.yuzu_emu.model.AppletInfo
|
||||||
import org.yuzu.yuzu_emu.model.Game
|
import org.yuzu.yuzu_emu.model.Game
|
||||||
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
|
||||||
|
|
||||||
class CabinetLauncherDialogAdapter(val fragment: Fragment) :
|
class CabinetLauncherDialogAdapter(val fragment: Fragment) :
|
||||||
AbstractListAdapter<CabinetMode, CabinetModeViewHolder>(
|
RecyclerView.Adapter<CabinetModeViewHolder>(),
|
||||||
CabinetMode.values().copyOfRange(1, CabinetMode.entries.size).toList()
|
View.OnClickListener {
|
||||||
) {
|
private val cabinetModes = CabinetMode.values().copyOfRange(1, CabinetMode.values().size)
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CabinetModeViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CabinetModeViewHolder {
|
||||||
DialogListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
DialogListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
|
.apply { root.setOnClickListener(this@CabinetLauncherDialogAdapter) }
|
||||||
.also { return CabinetModeViewHolder(it) }
|
.also { return CabinetModeViewHolder(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class CabinetModeViewHolder(val binding: DialogListItemBinding) :
|
override fun getItemCount(): Int = cabinetModes.size
|
||||||
AbstractViewHolder<CabinetMode>(binding) {
|
|
||||||
override fun bind(model: CabinetMode) {
|
|
||||||
binding.icon.setImageDrawable(
|
|
||||||
ResourcesCompat.getDrawable(
|
|
||||||
binding.icon.context.resources,
|
|
||||||
model.iconId,
|
|
||||||
binding.icon.context.theme
|
|
||||||
)
|
|
||||||
)
|
|
||||||
binding.title.setText(model.titleId)
|
|
||||||
|
|
||||||
binding.root.setOnClickListener { onClick(model) }
|
override fun onBindViewHolder(holder: CabinetModeViewHolder, position: Int) =
|
||||||
}
|
holder.bind(cabinetModes[position])
|
||||||
|
|
||||||
private fun onClick(mode: CabinetMode) {
|
override fun onClick(view: View) {
|
||||||
|
val mode = (view.tag as CabinetModeViewHolder).cabinetMode
|
||||||
val appletPath = NativeLibrary.getAppletLaunchPath(AppletInfo.Cabinet.entryId)
|
val appletPath = NativeLibrary.getAppletLaunchPath(AppletInfo.Cabinet.entryId)
|
||||||
NativeLibrary.setCurrentAppletId(AppletInfo.Cabinet.appletId)
|
NativeLibrary.setCurrentAppletId(AppletInfo.Cabinet.appletId)
|
||||||
NativeLibrary.setCabinetMode(mode.id)
|
NativeLibrary.setCabinetMode(mode.id)
|
||||||
@ -55,5 +48,25 @@ class CabinetLauncherDialogAdapter(val fragment: Fragment) :
|
|||||||
val action = HomeNavigationDirections.actionGlobalEmulationActivity(appletGame)
|
val action = HomeNavigationDirections.actionGlobalEmulationActivity(appletGame)
|
||||||
fragment.findNavController().navigate(action)
|
fragment.findNavController().navigate(action)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inner class CabinetModeViewHolder(val binding: DialogListItemBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
|
lateinit var cabinetMode: CabinetMode
|
||||||
|
|
||||||
|
init {
|
||||||
|
itemView.tag = this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bind(cabinetMode: CabinetMode) {
|
||||||
|
this.cabinetMode = cabinetMode
|
||||||
|
binding.icon.setImageDrawable(
|
||||||
|
ResourcesCompat.getDrawable(
|
||||||
|
binding.icon.context.resources,
|
||||||
|
cabinetMode.iconId,
|
||||||
|
binding.icon.context.theme
|
||||||
|
)
|
||||||
|
)
|
||||||
|
binding.title.setText(cabinetMode.titleId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,57 +3,115 @@
|
|||||||
|
|
||||||
package org.yuzu.yuzu_emu.adapters
|
package org.yuzu.yuzu_emu.adapters
|
||||||
|
|
||||||
|
import android.text.TextUtils
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.AsyncDifferConfig
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import org.yuzu.yuzu_emu.R
|
import org.yuzu.yuzu_emu.R
|
||||||
import org.yuzu.yuzu_emu.databinding.CardDriverOptionBinding
|
import org.yuzu.yuzu_emu.databinding.CardDriverOptionBinding
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.StringSetting
|
|
||||||
import org.yuzu.yuzu_emu.model.Driver
|
|
||||||
import org.yuzu.yuzu_emu.model.DriverViewModel
|
import org.yuzu.yuzu_emu.model.DriverViewModel
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.marquee
|
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
import org.yuzu.yuzu_emu.utils.GpuDriverMetadata
|
||||||
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
|
||||||
|
|
||||||
class DriverAdapter(private val driverViewModel: DriverViewModel) :
|
class DriverAdapter(private val driverViewModel: DriverViewModel) :
|
||||||
AbstractSingleSelectionList<Driver, DriverAdapter.DriverViewHolder>(
|
ListAdapter<Pair<String, GpuDriverMetadata>, DriverAdapter.DriverViewHolder>(
|
||||||
driverViewModel.driverList.value
|
AsyncDifferConfig.Builder(DiffCallback()).build()
|
||||||
) {
|
) {
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DriverViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DriverViewHolder {
|
||||||
|
val binding =
|
||||||
CardDriverOptionBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
CardDriverOptionBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
.also { return DriverViewHolder(it) }
|
return DriverViewHolder(binding)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int = currentList.size
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: DriverViewHolder, position: Int) =
|
||||||
|
holder.bind(currentList[position])
|
||||||
|
|
||||||
|
private fun onSelectDriver(position: Int) {
|
||||||
|
driverViewModel.setSelectedDriverIndex(position)
|
||||||
|
notifyItemChanged(driverViewModel.previouslySelectedDriver)
|
||||||
|
notifyItemChanged(driverViewModel.selectedDriver)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onDeleteDriver(driverData: Pair<String, GpuDriverMetadata>, position: Int) {
|
||||||
|
if (driverViewModel.selectedDriver > position) {
|
||||||
|
driverViewModel.setSelectedDriverIndex(driverViewModel.selectedDriver - 1)
|
||||||
|
}
|
||||||
|
if (GpuDriverHelper.customDriverSettingData == driverData.second) {
|
||||||
|
driverViewModel.setSelectedDriverIndex(0)
|
||||||
|
}
|
||||||
|
driverViewModel.driversToDelete.add(driverData.first)
|
||||||
|
driverViewModel.removeDriver(driverData)
|
||||||
|
notifyItemRemoved(position)
|
||||||
|
notifyItemChanged(driverViewModel.selectedDriver)
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class DriverViewHolder(val binding: CardDriverOptionBinding) :
|
inner class DriverViewHolder(val binding: CardDriverOptionBinding) :
|
||||||
AbstractViewHolder<Driver>(binding) {
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
override fun bind(model: Driver) {
|
private lateinit var driverData: Pair<String, GpuDriverMetadata>
|
||||||
|
|
||||||
|
fun bind(driverData: Pair<String, GpuDriverMetadata>) {
|
||||||
|
this.driverData = driverData
|
||||||
|
val driver = driverData.second
|
||||||
|
|
||||||
binding.apply {
|
binding.apply {
|
||||||
radioButton.isChecked = model.selected
|
radioButton.isChecked = driverViewModel.selectedDriver == bindingAdapterPosition
|
||||||
root.setOnClickListener {
|
root.setOnClickListener {
|
||||||
selectItem(bindingAdapterPosition) {
|
onSelectDriver(bindingAdapterPosition)
|
||||||
driverViewModel.onDriverSelected(it)
|
|
||||||
driverViewModel.showClearButton(!StringSetting.DRIVER_PATH.global)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
buttonDelete.setOnClickListener {
|
buttonDelete.setOnClickListener {
|
||||||
removeSelectableItem(
|
onDeleteDriver(driverData, bindingAdapterPosition)
|
||||||
bindingAdapterPosition
|
|
||||||
) { removedPosition: Int, selectedPosition: Int ->
|
|
||||||
driverViewModel.onDriverRemoved(removedPosition, selectedPosition)
|
|
||||||
driverViewModel.showClearButton(!StringSetting.DRIVER_PATH.global)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delay marquee by 3s
|
// Delay marquee by 3s
|
||||||
title.marquee()
|
title.postDelayed(
|
||||||
version.marquee()
|
{
|
||||||
description.marquee()
|
title.isSelected = true
|
||||||
title.text = model.title
|
title.ellipsize = TextUtils.TruncateAt.MARQUEE
|
||||||
version.text = model.version
|
version.isSelected = true
|
||||||
description.text = model.description
|
version.ellipsize = TextUtils.TruncateAt.MARQUEE
|
||||||
buttonDelete.setVisible(
|
description.isSelected = true
|
||||||
model.title != binding.root.context.getString(R.string.system_gpu_driver)
|
description.ellipsize = TextUtils.TruncateAt.MARQUEE
|
||||||
|
},
|
||||||
|
3000
|
||||||
)
|
)
|
||||||
|
if (driver.name == null) {
|
||||||
|
title.setText(R.string.system_gpu_driver)
|
||||||
|
description.text = ""
|
||||||
|
version.text = ""
|
||||||
|
version.visibility = View.GONE
|
||||||
|
description.visibility = View.GONE
|
||||||
|
buttonDelete.visibility = View.GONE
|
||||||
|
} else {
|
||||||
|
title.text = driver.name
|
||||||
|
version.text = driver.version
|
||||||
|
description.text = driver.description
|
||||||
|
version.visibility = View.VISIBLE
|
||||||
|
description.visibility = View.VISIBLE
|
||||||
|
buttonDelete.visibility = View.VISIBLE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DiffCallback : DiffUtil.ItemCallback<Pair<String, GpuDriverMetadata>>() {
|
||||||
|
override fun areItemsTheSame(
|
||||||
|
oldItem: Pair<String, GpuDriverMetadata>,
|
||||||
|
newItem: Pair<String, GpuDriverMetadata>
|
||||||
|
): Boolean {
|
||||||
|
return oldItem.first == newItem.first
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun areContentsTheSame(
|
||||||
|
oldItem: Pair<String, GpuDriverMetadata>,
|
||||||
|
newItem: Pair<String, GpuDriverMetadata>
|
||||||
|
): Boolean {
|
||||||
|
return oldItem.second == newItem.second
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,18 +4,23 @@
|
|||||||
package org.yuzu.yuzu_emu.adapters
|
package org.yuzu.yuzu_emu.adapters
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.text.TextUtils
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
|
import androidx.recyclerview.widget.AsyncDifferConfig
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import org.yuzu.yuzu_emu.databinding.CardFolderBinding
|
import org.yuzu.yuzu_emu.databinding.CardFolderBinding
|
||||||
import org.yuzu.yuzu_emu.fragments.GameFolderPropertiesDialogFragment
|
import org.yuzu.yuzu_emu.fragments.GameFolderPropertiesDialogFragment
|
||||||
import org.yuzu.yuzu_emu.model.GameDir
|
import org.yuzu.yuzu_emu.model.GameDir
|
||||||
import org.yuzu.yuzu_emu.model.GamesViewModel
|
import org.yuzu.yuzu_emu.model.GamesViewModel
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.marquee
|
|
||||||
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
|
||||||
|
|
||||||
class FolderAdapter(val activity: FragmentActivity, val gamesViewModel: GamesViewModel) :
|
class FolderAdapter(val activity: FragmentActivity, val gamesViewModel: GamesViewModel) :
|
||||||
AbstractDiffAdapter<GameDir, FolderAdapter.FolderViewHolder>() {
|
ListAdapter<GameDir, FolderAdapter.FolderViewHolder>(
|
||||||
|
AsyncDifferConfig.Builder(DiffCallback()).build()
|
||||||
|
) {
|
||||||
override fun onCreateViewHolder(
|
override fun onCreateViewHolder(
|
||||||
parent: ViewGroup,
|
parent: ViewGroup,
|
||||||
viewType: Int
|
viewType: Int
|
||||||
@ -24,15 +29,28 @@ class FolderAdapter(val activity: FragmentActivity, val gamesViewModel: GamesVie
|
|||||||
.also { return FolderViewHolder(it) }
|
.also { return FolderViewHolder(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: FolderAdapter.FolderViewHolder, position: Int) =
|
||||||
|
holder.bind(currentList[position])
|
||||||
|
|
||||||
inner class FolderViewHolder(val binding: CardFolderBinding) :
|
inner class FolderViewHolder(val binding: CardFolderBinding) :
|
||||||
AbstractViewHolder<GameDir>(binding) {
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
override fun bind(model: GameDir) {
|
private lateinit var gameDir: GameDir
|
||||||
|
|
||||||
|
fun bind(gameDir: GameDir) {
|
||||||
|
this.gameDir = gameDir
|
||||||
|
|
||||||
binding.apply {
|
binding.apply {
|
||||||
path.text = Uri.parse(model.uriString).path
|
path.text = Uri.parse(gameDir.uriString).path
|
||||||
path.marquee()
|
path.postDelayed(
|
||||||
|
{
|
||||||
|
path.isSelected = true
|
||||||
|
path.ellipsize = TextUtils.TruncateAt.MARQUEE
|
||||||
|
},
|
||||||
|
3000
|
||||||
|
)
|
||||||
|
|
||||||
buttonEdit.setOnClickListener {
|
buttonEdit.setOnClickListener {
|
||||||
GameFolderPropertiesDialogFragment.newInstance(model)
|
GameFolderPropertiesDialogFragment.newInstance(this@FolderViewHolder.gameDir)
|
||||||
.show(
|
.show(
|
||||||
activity.supportFragmentManager,
|
activity.supportFragmentManager,
|
||||||
GameFolderPropertiesDialogFragment.TAG
|
GameFolderPropertiesDialogFragment.TAG
|
||||||
@ -40,9 +58,19 @@ class FolderAdapter(val activity: FragmentActivity, val gamesViewModel: GamesVie
|
|||||||
}
|
}
|
||||||
|
|
||||||
buttonDelete.setOnClickListener {
|
buttonDelete.setOnClickListener {
|
||||||
gamesViewModel.removeFolder(model)
|
gamesViewModel.removeFolder(this@FolderViewHolder.gameDir)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class DiffCallback : DiffUtil.ItemCallback<GameDir>() {
|
||||||
|
override fun areItemsTheSame(oldItem: GameDir, newItem: GameDir): Boolean {
|
||||||
|
return oldItem == newItem
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun areContentsTheSame(oldItem: GameDir, newItem: GameDir): Boolean {
|
||||||
|
return oldItem == newItem
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,56 +3,75 @@
|
|||||||
|
|
||||||
package org.yuzu.yuzu_emu.adapters
|
package org.yuzu.yuzu_emu.adapters
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.drawable.LayerDrawable
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.text.TextUtils
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.content.pm.ShortcutInfoCompat
|
import androidx.core.content.pm.ShortcutInfoCompat
|
||||||
import androidx.core.content.pm.ShortcutManagerCompat
|
import androidx.core.content.pm.ShortcutManagerCompat
|
||||||
|
import androidx.core.content.res.ResourcesCompat
|
||||||
|
import androidx.core.graphics.drawable.IconCompat
|
||||||
|
import androidx.core.graphics.drawable.toBitmap
|
||||||
|
import androidx.core.graphics.drawable.toDrawable
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.navigation.findNavController
|
import androidx.navigation.findNavController
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
|
import androidx.recyclerview.widget.AsyncDifferConfig
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.yuzu.yuzu_emu.HomeNavigationDirections
|
import org.yuzu.yuzu_emu.HomeNavigationDirections
|
||||||
import org.yuzu.yuzu_emu.R
|
import org.yuzu.yuzu_emu.R
|
||||||
import org.yuzu.yuzu_emu.YuzuApplication
|
import org.yuzu.yuzu_emu.YuzuApplication
|
||||||
|
import org.yuzu.yuzu_emu.activities.EmulationActivity
|
||||||
|
import org.yuzu.yuzu_emu.adapters.GameAdapter.GameViewHolder
|
||||||
import org.yuzu.yuzu_emu.databinding.CardGameBinding
|
import org.yuzu.yuzu_emu.databinding.CardGameBinding
|
||||||
import org.yuzu.yuzu_emu.model.Game
|
import org.yuzu.yuzu_emu.model.Game
|
||||||
import org.yuzu.yuzu_emu.model.GamesViewModel
|
import org.yuzu.yuzu_emu.model.GamesViewModel
|
||||||
import org.yuzu.yuzu_emu.utils.GameIconUtils
|
import org.yuzu.yuzu_emu.utils.GameIconUtils
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.marquee
|
|
||||||
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
|
||||||
|
|
||||||
class GameAdapter(private val activity: AppCompatActivity) :
|
class GameAdapter(private val activity: AppCompatActivity) :
|
||||||
AbstractDiffAdapter<Game, GameAdapter.GameViewHolder>(exact = false) {
|
ListAdapter<Game, GameViewHolder>(AsyncDifferConfig.Builder(DiffCallback()).build()),
|
||||||
|
View.OnClickListener,
|
||||||
|
View.OnLongClickListener {
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GameViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GameViewHolder {
|
||||||
CardGameBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
// Create a new view.
|
||||||
.also { return GameViewHolder(it) }
|
val binding = CardGameBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
|
binding.cardGame.setOnClickListener(this)
|
||||||
|
binding.cardGame.setOnLongClickListener(this)
|
||||||
|
|
||||||
|
// Use that view to create a ViewHolder.
|
||||||
|
return GameViewHolder(binding)
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class GameViewHolder(val binding: CardGameBinding) :
|
override fun onBindViewHolder(holder: GameViewHolder, position: Int) =
|
||||||
AbstractViewHolder<Game>(binding) {
|
holder.bind(currentList[position])
|
||||||
override fun bind(model: Game) {
|
|
||||||
binding.imageGameScreen.scaleType = ImageView.ScaleType.CENTER_CROP
|
|
||||||
GameIconUtils.loadGameIcon(model, binding.imageGameScreen)
|
|
||||||
|
|
||||||
binding.textGameTitle.text = model.title.replace("[\\t\\n\\r]+".toRegex(), " ")
|
override fun getItemCount(): Int = currentList.size
|
||||||
|
|
||||||
binding.textGameTitle.marquee()
|
/**
|
||||||
binding.cardGame.setOnClickListener { onClick(model) }
|
* Launches the game that was clicked on.
|
||||||
binding.cardGame.setOnLongClickListener { onLongClick(model) }
|
*
|
||||||
}
|
* @param view The card representing the game the user wants to play.
|
||||||
|
*/
|
||||||
|
override fun onClick(view: View) {
|
||||||
|
val holder = view.tag as GameViewHolder
|
||||||
|
|
||||||
fun onClick(game: Game) {
|
|
||||||
val gameExists = DocumentFile.fromSingleUri(
|
val gameExists = DocumentFile.fromSingleUri(
|
||||||
YuzuApplication.appContext,
|
YuzuApplication.appContext,
|
||||||
Uri.parse(game.path)
|
Uri.parse(holder.game.path)
|
||||||
)?.exists() == true
|
)?.exists() == true
|
||||||
if (!gameExists) {
|
if (!gameExists) {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
@ -65,35 +84,92 @@ class GameAdapter(private val activity: AppCompatActivity) :
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val preferences =
|
val preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
|
||||||
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
|
|
||||||
preferences.edit()
|
preferences.edit()
|
||||||
.putLong(
|
.putLong(
|
||||||
game.keyLastPlayedTime,
|
holder.game.keyLastPlayedTime,
|
||||||
System.currentTimeMillis()
|
System.currentTimeMillis()
|
||||||
)
|
)
|
||||||
.apply()
|
.apply()
|
||||||
|
|
||||||
|
val openIntent = Intent(YuzuApplication.appContext, EmulationActivity::class.java).apply {
|
||||||
|
action = Intent.ACTION_VIEW
|
||||||
|
data = Uri.parse(holder.game.path)
|
||||||
|
}
|
||||||
|
|
||||||
activity.lifecycleScope.launch {
|
activity.lifecycleScope.launch {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
|
val layerDrawable = ResourcesCompat.getDrawable(
|
||||||
|
YuzuApplication.appContext.resources,
|
||||||
|
R.drawable.shortcut,
|
||||||
|
null
|
||||||
|
) as LayerDrawable
|
||||||
|
layerDrawable.setDrawableByLayerId(
|
||||||
|
R.id.shortcut_foreground,
|
||||||
|
GameIconUtils.getGameIcon(activity, holder.game)
|
||||||
|
.toDrawable(YuzuApplication.appContext.resources)
|
||||||
|
)
|
||||||
|
val inset = YuzuApplication.appContext.resources
|
||||||
|
.getDimensionPixelSize(R.dimen.icon_inset)
|
||||||
|
layerDrawable.setLayerInset(1, inset, inset, inset, inset)
|
||||||
val shortcut =
|
val shortcut =
|
||||||
ShortcutInfoCompat.Builder(YuzuApplication.appContext, game.path)
|
ShortcutInfoCompat.Builder(YuzuApplication.appContext, holder.game.path)
|
||||||
.setShortLabel(game.title)
|
.setShortLabel(holder.game.title)
|
||||||
.setIcon(GameIconUtils.getShortcutIcon(activity, game))
|
.setIcon(
|
||||||
.setIntent(game.launchIntent)
|
IconCompat.createWithAdaptiveBitmap(
|
||||||
|
layerDrawable.toBitmap(config = Bitmap.Config.ARGB_8888)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.setIntent(openIntent)
|
||||||
.build()
|
.build()
|
||||||
ShortcutManagerCompat.pushDynamicShortcut(YuzuApplication.appContext, shortcut)
|
ShortcutManagerCompat.pushDynamicShortcut(YuzuApplication.appContext, shortcut)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val action = HomeNavigationDirections.actionGlobalEmulationActivity(game, true)
|
val action = HomeNavigationDirections.actionGlobalEmulationActivity(holder.game, true)
|
||||||
binding.root.findNavController().navigate(action)
|
view.findNavController().navigate(action)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onLongClick(game: Game): Boolean {
|
override fun onLongClick(view: View): Boolean {
|
||||||
val action = HomeNavigationDirections.actionGlobalPerGamePropertiesFragment(game)
|
val holder = view.tag as GameViewHolder
|
||||||
binding.root.findNavController().navigate(action)
|
val action = HomeNavigationDirections.actionGlobalPerGamePropertiesFragment(holder.game)
|
||||||
|
view.findNavController().navigate(action)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inner class GameViewHolder(val binding: CardGameBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
|
lateinit var game: Game
|
||||||
|
|
||||||
|
init {
|
||||||
|
binding.cardGame.tag = this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bind(game: Game) {
|
||||||
|
this.game = game
|
||||||
|
|
||||||
|
binding.imageGameScreen.scaleType = ImageView.ScaleType.CENTER_CROP
|
||||||
|
GameIconUtils.loadGameIcon(game, binding.imageGameScreen)
|
||||||
|
|
||||||
|
binding.textGameTitle.text = game.title.replace("[\\t\\n\\r]+".toRegex(), " ")
|
||||||
|
|
||||||
|
binding.textGameTitle.postDelayed(
|
||||||
|
{
|
||||||
|
binding.textGameTitle.ellipsize = TextUtils.TruncateAt.MARQUEE
|
||||||
|
binding.textGameTitle.isSelected = true
|
||||||
|
},
|
||||||
|
3000
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DiffCallback : DiffUtil.ItemCallback<Game>() {
|
||||||
|
override fun areItemsTheSame(oldItem: Game, newItem: Game): Boolean {
|
||||||
|
return oldItem == newItem
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun areContentsTheSame(oldItem: Game, newItem: Game): Boolean {
|
||||||
|
return oldItem == newItem
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,28 +3,32 @@
|
|||||||
|
|
||||||
package org.yuzu.yuzu_emu.adapters
|
package org.yuzu.yuzu_emu.adapters
|
||||||
|
|
||||||
|
import android.text.TextUtils
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.core.content.res.ResourcesCompat
|
import androidx.core.content.res.ResourcesCompat
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.yuzu.yuzu_emu.databinding.CardInstallableIconBinding
|
import org.yuzu.yuzu_emu.databinding.CardInstallableIconBinding
|
||||||
import org.yuzu.yuzu_emu.databinding.CardSimpleOutlinedBinding
|
import org.yuzu.yuzu_emu.databinding.CardSimpleOutlinedBinding
|
||||||
import org.yuzu.yuzu_emu.model.GameProperty
|
import org.yuzu.yuzu_emu.model.GameProperty
|
||||||
import org.yuzu.yuzu_emu.model.InstallableProperty
|
import org.yuzu.yuzu_emu.model.InstallableProperty
|
||||||
import org.yuzu.yuzu_emu.model.SubmenuProperty
|
import org.yuzu.yuzu_emu.model.SubmenuProperty
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.marquee
|
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
|
||||||
import org.yuzu.yuzu_emu.utils.collect
|
|
||||||
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
|
||||||
|
|
||||||
class GamePropertiesAdapter(
|
class GamePropertiesAdapter(
|
||||||
private val viewLifecycle: LifecycleOwner,
|
private val viewLifecycle: LifecycleOwner,
|
||||||
private var properties: List<GameProperty>
|
private var properties: List<GameProperty>
|
||||||
) : AbstractListAdapter<GameProperty, AbstractViewHolder<GameProperty>>(properties) {
|
) :
|
||||||
|
RecyclerView.Adapter<GamePropertiesAdapter.GamePropertyViewHolder>() {
|
||||||
override fun onCreateViewHolder(
|
override fun onCreateViewHolder(
|
||||||
parent: ViewGroup,
|
parent: ViewGroup,
|
||||||
viewType: Int
|
viewType: Int
|
||||||
): AbstractViewHolder<GameProperty> {
|
): GamePropertyViewHolder {
|
||||||
val inflater = LayoutInflater.from(parent.context)
|
val inflater = LayoutInflater.from(parent.context)
|
||||||
return when (viewType) {
|
return when (viewType) {
|
||||||
PropertyType.Submenu.ordinal -> {
|
PropertyType.Submenu.ordinal -> {
|
||||||
@ -47,6 +51,11 @@ class GamePropertiesAdapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int = properties.size
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: GamePropertyViewHolder, position: Int) =
|
||||||
|
holder.bind(properties[position])
|
||||||
|
|
||||||
override fun getItemViewType(position: Int): Int {
|
override fun getItemViewType(position: Int): Int {
|
||||||
return when (properties[position]) {
|
return when (properties[position]) {
|
||||||
is SubmenuProperty -> PropertyType.Submenu.ordinal
|
is SubmenuProperty -> PropertyType.Submenu.ordinal
|
||||||
@ -54,10 +63,14 @@ class GamePropertiesAdapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sealed class GamePropertyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||||
|
abstract fun bind(property: GameProperty)
|
||||||
|
}
|
||||||
|
|
||||||
inner class SubmenuPropertyViewHolder(val binding: CardSimpleOutlinedBinding) :
|
inner class SubmenuPropertyViewHolder(val binding: CardSimpleOutlinedBinding) :
|
||||||
AbstractViewHolder<GameProperty>(binding) {
|
GamePropertyViewHolder(binding.root) {
|
||||||
override fun bind(model: GameProperty) {
|
override fun bind(property: GameProperty) {
|
||||||
val submenuProperty = model as SubmenuProperty
|
val submenuProperty = property as SubmenuProperty
|
||||||
|
|
||||||
binding.root.setOnClickListener {
|
binding.root.setOnClickListener {
|
||||||
submenuProperty.action.invoke()
|
submenuProperty.action.invoke()
|
||||||
@ -73,23 +86,31 @@ class GamePropertiesAdapter(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
binding.details.marquee()
|
binding.details.postDelayed({
|
||||||
|
binding.details.isSelected = true
|
||||||
|
binding.details.ellipsize = TextUtils.TruncateAt.MARQUEE
|
||||||
|
}, 3000)
|
||||||
|
|
||||||
if (submenuProperty.details != null) {
|
if (submenuProperty.details != null) {
|
||||||
binding.details.setVisible(true)
|
binding.details.visibility = View.VISIBLE
|
||||||
binding.details.text = submenuProperty.details.invoke()
|
binding.details.text = submenuProperty.details.invoke()
|
||||||
} else if (submenuProperty.detailsFlow != null) {
|
} else if (submenuProperty.detailsFlow != null) {
|
||||||
binding.details.setVisible(true)
|
binding.details.visibility = View.VISIBLE
|
||||||
submenuProperty.detailsFlow.collect(viewLifecycle) { binding.details.text = it }
|
viewLifecycle.lifecycleScope.launch {
|
||||||
|
viewLifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||||
|
submenuProperty.detailsFlow.collect { binding.details.text = it }
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
binding.details.setVisible(false)
|
binding.details.visibility = View.GONE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class InstallablePropertyViewHolder(val binding: CardInstallableIconBinding) :
|
inner class InstallablePropertyViewHolder(val binding: CardInstallableIconBinding) :
|
||||||
AbstractViewHolder<GameProperty>(binding) {
|
GamePropertyViewHolder(binding.root) {
|
||||||
override fun bind(model: GameProperty) {
|
override fun bind(property: GameProperty) {
|
||||||
val installableProperty = model as InstallableProperty
|
val installableProperty = property as InstallableProperty
|
||||||
|
|
||||||
binding.title.setText(installableProperty.titleId)
|
binding.title.setText(installableProperty.titleId)
|
||||||
binding.description.setText(installableProperty.descriptionId)
|
binding.description.setText(installableProperty.descriptionId)
|
||||||
@ -101,10 +122,14 @@ class GamePropertiesAdapter(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
binding.buttonInstall.setVisible(installableProperty.install != null)
|
if (installableProperty.install != null) {
|
||||||
binding.buttonInstall.setOnClickListener { installableProperty.install?.invoke() }
|
binding.buttonInstall.visibility = View.VISIBLE
|
||||||
binding.buttonExport.setVisible(installableProperty.export != null)
|
binding.buttonInstall.setOnClickListener { installableProperty.install.invoke() }
|
||||||
binding.buttonExport.setOnClickListener { installableProperty.export?.invoke() }
|
}
|
||||||
|
if (installableProperty.export != null) {
|
||||||
|
binding.buttonExport.visibility = View.VISIBLE
|
||||||
|
binding.buttonExport.setOnClickListener { installableProperty.export.invoke() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,45 +3,80 @@
|
|||||||
|
|
||||||
package org.yuzu.yuzu_emu.adapters
|
package org.yuzu.yuzu_emu.adapters
|
||||||
|
|
||||||
|
import android.text.TextUtils
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.content.res.ResourcesCompat
|
import androidx.core.content.res.ResourcesCompat
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.yuzu.yuzu_emu.R
|
import org.yuzu.yuzu_emu.R
|
||||||
import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding
|
import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding
|
||||||
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
|
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
|
||||||
import org.yuzu.yuzu_emu.model.HomeSetting
|
import org.yuzu.yuzu_emu.model.HomeSetting
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.marquee
|
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
|
||||||
import org.yuzu.yuzu_emu.utils.collect
|
|
||||||
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
|
||||||
|
|
||||||
class HomeSettingAdapter(
|
class HomeSettingAdapter(
|
||||||
private val activity: AppCompatActivity,
|
private val activity: AppCompatActivity,
|
||||||
private val viewLifecycle: LifecycleOwner,
|
private val viewLifecycle: LifecycleOwner,
|
||||||
options: List<HomeSetting>
|
var options: List<HomeSetting>
|
||||||
) : AbstractListAdapter<HomeSetting, HomeSettingAdapter.HomeOptionViewHolder>(options) {
|
) :
|
||||||
|
RecyclerView.Adapter<HomeSettingAdapter.HomeOptionViewHolder>(),
|
||||||
|
View.OnClickListener {
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HomeOptionViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HomeOptionViewHolder {
|
||||||
|
val binding =
|
||||||
CardHomeOptionBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
CardHomeOptionBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
.also { return HomeOptionViewHolder(it) }
|
binding.root.setOnClickListener(this)
|
||||||
|
return HomeOptionViewHolder(binding)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int {
|
||||||
|
return options.size
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: HomeOptionViewHolder, position: Int) {
|
||||||
|
holder.bind(options[position])
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onClick(view: View) {
|
||||||
|
val holder = view.tag as HomeOptionViewHolder
|
||||||
|
if (holder.option.isEnabled.invoke()) {
|
||||||
|
holder.option.onClick.invoke()
|
||||||
|
} else {
|
||||||
|
MessageDialogFragment.newInstance(
|
||||||
|
activity,
|
||||||
|
titleId = holder.option.disabledTitleId,
|
||||||
|
descriptionId = holder.option.disabledMessageId
|
||||||
|
).show(activity.supportFragmentManager, MessageDialogFragment.TAG)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class HomeOptionViewHolder(val binding: CardHomeOptionBinding) :
|
inner class HomeOptionViewHolder(val binding: CardHomeOptionBinding) :
|
||||||
AbstractViewHolder<HomeSetting>(binding) {
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
override fun bind(model: HomeSetting) {
|
lateinit var option: HomeSetting
|
||||||
binding.optionTitle.text = activity.resources.getString(model.titleId)
|
|
||||||
binding.optionDescription.text = activity.resources.getString(model.descriptionId)
|
init {
|
||||||
|
itemView.tag = this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bind(option: HomeSetting) {
|
||||||
|
this.option = option
|
||||||
|
binding.optionTitle.text = activity.resources.getString(option.titleId)
|
||||||
|
binding.optionDescription.text = activity.resources.getString(option.descriptionId)
|
||||||
binding.optionIcon.setImageDrawable(
|
binding.optionIcon.setImageDrawable(
|
||||||
ResourcesCompat.getDrawable(
|
ResourcesCompat.getDrawable(
|
||||||
activity.resources,
|
activity.resources,
|
||||||
model.iconId,
|
option.iconId,
|
||||||
activity.theme
|
activity.theme
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
when (model.titleId) {
|
when (option.titleId) {
|
||||||
R.string.get_early_access ->
|
R.string.get_early_access ->
|
||||||
binding.optionLayout.background =
|
binding.optionLayout.background =
|
||||||
ContextCompat.getDrawable(
|
ContextCompat.getDrawable(
|
||||||
@ -50,34 +85,30 @@ class HomeSettingAdapter(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!model.isEnabled.invoke()) {
|
if (!option.isEnabled.invoke()) {
|
||||||
binding.optionTitle.alpha = 0.5f
|
binding.optionTitle.alpha = 0.5f
|
||||||
binding.optionDescription.alpha = 0.5f
|
binding.optionDescription.alpha = 0.5f
|
||||||
binding.optionIcon.alpha = 0.5f
|
binding.optionIcon.alpha = 0.5f
|
||||||
}
|
}
|
||||||
|
|
||||||
model.details.collect(viewLifecycle) { updateOptionDetails(it) }
|
viewLifecycle.lifecycleScope.launch {
|
||||||
binding.optionDetail.marquee()
|
viewLifecycle.repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||||
|
option.details.collect { updateOptionDetails(it) }
|
||||||
binding.root.setOnClickListener { onClick(model) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onClick(model: HomeSetting) {
|
|
||||||
if (model.isEnabled.invoke()) {
|
|
||||||
model.onClick.invoke()
|
|
||||||
} else {
|
|
||||||
MessageDialogFragment.newInstance(
|
|
||||||
activity,
|
|
||||||
titleId = model.disabledTitleId,
|
|
||||||
descriptionId = model.disabledMessageId
|
|
||||||
).show(activity.supportFragmentManager, MessageDialogFragment.TAG)
|
|
||||||
}
|
}
|
||||||
|
binding.optionDetail.postDelayed(
|
||||||
|
{
|
||||||
|
binding.optionDetail.ellipsize = TextUtils.TruncateAt.MARQUEE
|
||||||
|
binding.optionDetail.isSelected = true
|
||||||
|
},
|
||||||
|
3000
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateOptionDetails(detailString: String) {
|
private fun updateOptionDetails(detailString: String) {
|
||||||
if (detailString.isNotEmpty()) {
|
if (detailString.isNotEmpty()) {
|
||||||
binding.optionDetail.text = detailString
|
binding.optionDetail.text = detailString
|
||||||
binding.optionDetail.setVisible(true)
|
binding.optionDetail.visibility = View.VISIBLE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,32 +4,46 @@
|
|||||||
package org.yuzu.yuzu_emu.adapters
|
package org.yuzu.yuzu_emu.adapters
|
||||||
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import org.yuzu.yuzu_emu.databinding.CardInstallableBinding
|
import org.yuzu.yuzu_emu.databinding.CardInstallableBinding
|
||||||
import org.yuzu.yuzu_emu.model.Installable
|
import org.yuzu.yuzu_emu.model.Installable
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
|
||||||
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
|
||||||
|
|
||||||
class InstallableAdapter(installables: List<Installable>) :
|
class InstallableAdapter(private val installables: List<Installable>) :
|
||||||
AbstractListAdapter<Installable, InstallableAdapter.InstallableViewHolder>(installables) {
|
RecyclerView.Adapter<InstallableAdapter.InstallableViewHolder>() {
|
||||||
override fun onCreateViewHolder(
|
override fun onCreateViewHolder(
|
||||||
parent: ViewGroup,
|
parent: ViewGroup,
|
||||||
viewType: Int
|
viewType: Int
|
||||||
): InstallableAdapter.InstallableViewHolder {
|
): InstallableAdapter.InstallableViewHolder {
|
||||||
|
val binding =
|
||||||
CardInstallableBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
CardInstallableBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
.also { return InstallableViewHolder(it) }
|
return InstallableViewHolder(binding)
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class InstallableViewHolder(val binding: CardInstallableBinding) :
|
override fun getItemCount(): Int = installables.size
|
||||||
AbstractViewHolder<Installable>(binding) {
|
|
||||||
override fun bind(model: Installable) {
|
|
||||||
binding.title.setText(model.titleId)
|
|
||||||
binding.description.setText(model.descriptionId)
|
|
||||||
|
|
||||||
binding.buttonInstall.setVisible(model.install != null)
|
override fun onBindViewHolder(holder: InstallableAdapter.InstallableViewHolder, position: Int) =
|
||||||
binding.buttonInstall.setOnClickListener { model.install?.invoke() }
|
holder.bind(installables[position])
|
||||||
binding.buttonExport.setVisible(model.export != null)
|
|
||||||
binding.buttonExport.setOnClickListener { model.export?.invoke() }
|
inner class InstallableViewHolder(val binding: CardInstallableBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
|
lateinit var installable: Installable
|
||||||
|
|
||||||
|
fun bind(installable: Installable) {
|
||||||
|
this.installable = installable
|
||||||
|
|
||||||
|
binding.title.setText(installable.titleId)
|
||||||
|
binding.description.setText(installable.descriptionId)
|
||||||
|
|
||||||
|
if (installable.install != null) {
|
||||||
|
binding.buttonInstall.visibility = View.VISIBLE
|
||||||
|
binding.buttonInstall.setOnClickListener { installable.install.invoke() }
|
||||||
|
}
|
||||||
|
if (installable.export != null) {
|
||||||
|
binding.buttonExport.visibility = View.VISIBLE
|
||||||
|
binding.buttonExport.setOnClickListener { installable.export.invoke() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,36 +4,52 @@
|
|||||||
package org.yuzu.yuzu_emu.adapters
|
package org.yuzu.yuzu_emu.adapters
|
||||||
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
||||||
|
import org.yuzu.yuzu_emu.YuzuApplication
|
||||||
import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
|
import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
|
||||||
import org.yuzu.yuzu_emu.fragments.LicenseBottomSheetDialogFragment
|
import org.yuzu.yuzu_emu.fragments.LicenseBottomSheetDialogFragment
|
||||||
import org.yuzu.yuzu_emu.model.License
|
import org.yuzu.yuzu_emu.model.License
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
|
||||||
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
|
||||||
|
|
||||||
class LicenseAdapter(private val activity: AppCompatActivity, licenses: List<License>) :
|
class LicenseAdapter(private val activity: AppCompatActivity, var licenses: List<License>) :
|
||||||
AbstractListAdapter<License, LicenseAdapter.LicenseViewHolder>(licenses) {
|
RecyclerView.Adapter<LicenseAdapter.LicenseViewHolder>(),
|
||||||
|
View.OnClickListener {
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LicenseViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LicenseViewHolder {
|
||||||
|
val binding =
|
||||||
ListItemSettingBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
ListItemSettingBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
.also { return LicenseViewHolder(it) }
|
binding.root.setOnClickListener(this)
|
||||||
|
return LicenseViewHolder(binding)
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class LicenseViewHolder(val binding: ListItemSettingBinding) :
|
override fun getItemCount(): Int = licenses.size
|
||||||
AbstractViewHolder<License>(binding) {
|
|
||||||
override fun bind(model: License) {
|
|
||||||
binding.apply {
|
|
||||||
textSettingName.text = root.context.getString(model.titleId)
|
|
||||||
textSettingDescription.text = root.context.getString(model.descriptionId)
|
|
||||||
textSettingValue.setVisible(false)
|
|
||||||
|
|
||||||
root.setOnClickListener { onClick(model) }
|
override fun onBindViewHolder(holder: LicenseViewHolder, position: Int) {
|
||||||
}
|
holder.bind(licenses[position])
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onClick(license: License) {
|
override fun onClick(view: View) {
|
||||||
|
val license = (view.tag as LicenseViewHolder).license
|
||||||
LicenseBottomSheetDialogFragment.newInstance(license)
|
LicenseBottomSheetDialogFragment.newInstance(license)
|
||||||
.show(activity.supportFragmentManager, LicenseBottomSheetDialogFragment.TAG)
|
.show(activity.supportFragmentManager, LicenseBottomSheetDialogFragment.TAG)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inner class LicenseViewHolder(val binding: ListItemSettingBinding) : ViewHolder(binding.root) {
|
||||||
|
lateinit var license: License
|
||||||
|
|
||||||
|
init {
|
||||||
|
itemView.tag = this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bind(license: License) {
|
||||||
|
this.license = license
|
||||||
|
|
||||||
|
val context = YuzuApplication.appContext
|
||||||
|
binding.textSettingName.text = context.getString(license.titleId)
|
||||||
|
binding.textSettingDescription.text = context.getString(license.descriptionId)
|
||||||
|
binding.textSettingValue.visibility = View.GONE
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,10 +5,12 @@ package org.yuzu.yuzu_emu.adapters
|
|||||||
|
|
||||||
import android.text.Html
|
import android.text.Html
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.content.res.ResourcesCompat
|
import androidx.core.content.res.ResourcesCompat
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.google.android.material.button.MaterialButton
|
import com.google.android.material.button.MaterialButton
|
||||||
import org.yuzu.yuzu_emu.databinding.PageSetupBinding
|
import org.yuzu.yuzu_emu.databinding.PageSetupBinding
|
||||||
import org.yuzu.yuzu_emu.model.HomeViewModel
|
import org.yuzu.yuzu_emu.model.HomeViewModel
|
||||||
@ -16,52 +18,63 @@ import org.yuzu.yuzu_emu.model.SetupCallback
|
|||||||
import org.yuzu.yuzu_emu.model.SetupPage
|
import org.yuzu.yuzu_emu.model.SetupPage
|
||||||
import org.yuzu.yuzu_emu.model.StepState
|
import org.yuzu.yuzu_emu.model.StepState
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils
|
import org.yuzu.yuzu_emu.utils.ViewUtils
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
|
||||||
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
|
||||||
|
|
||||||
class SetupAdapter(val activity: AppCompatActivity, pages: List<SetupPage>) :
|
class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>) :
|
||||||
AbstractListAdapter<SetupPage, SetupAdapter.SetupPageViewHolder>(pages) {
|
RecyclerView.Adapter<SetupAdapter.SetupPageViewHolder>() {
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SetupPageViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SetupPageViewHolder {
|
||||||
PageSetupBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
val binding = PageSetupBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
.also { return SetupPageViewHolder(it) }
|
return SetupPageViewHolder(binding)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int = pages.size
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: SetupPageViewHolder, position: Int) =
|
||||||
|
holder.bind(pages[position])
|
||||||
|
|
||||||
inner class SetupPageViewHolder(val binding: PageSetupBinding) :
|
inner class SetupPageViewHolder(val binding: PageSetupBinding) :
|
||||||
AbstractViewHolder<SetupPage>(binding), SetupCallback {
|
RecyclerView.ViewHolder(binding.root), SetupCallback {
|
||||||
override fun bind(model: SetupPage) {
|
lateinit var page: SetupPage
|
||||||
if (model.stepCompleted.invoke() == StepState.COMPLETE) {
|
|
||||||
binding.buttonAction.setVisible(visible = false, gone = false)
|
init {
|
||||||
binding.textConfirmation.setVisible(true)
|
itemView.tag = this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bind(page: SetupPage) {
|
||||||
|
this.page = page
|
||||||
|
|
||||||
|
if (page.stepCompleted.invoke() == StepState.COMPLETE) {
|
||||||
|
binding.buttonAction.visibility = View.INVISIBLE
|
||||||
|
binding.textConfirmation.visibility = View.VISIBLE
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.icon.setImageDrawable(
|
binding.icon.setImageDrawable(
|
||||||
ResourcesCompat.getDrawable(
|
ResourcesCompat.getDrawable(
|
||||||
activity.resources,
|
activity.resources,
|
||||||
model.iconId,
|
page.iconId,
|
||||||
activity.theme
|
activity.theme
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
binding.textTitle.text = activity.resources.getString(model.titleId)
|
binding.textTitle.text = activity.resources.getString(page.titleId)
|
||||||
binding.textDescription.text =
|
binding.textDescription.text =
|
||||||
Html.fromHtml(activity.resources.getString(model.descriptionId), 0)
|
Html.fromHtml(activity.resources.getString(page.descriptionId), 0)
|
||||||
|
|
||||||
binding.buttonAction.apply {
|
binding.buttonAction.apply {
|
||||||
text = activity.resources.getString(model.buttonTextId)
|
text = activity.resources.getString(page.buttonTextId)
|
||||||
if (model.buttonIconId != 0) {
|
if (page.buttonIconId != 0) {
|
||||||
icon = ResourcesCompat.getDrawable(
|
icon = ResourcesCompat.getDrawable(
|
||||||
activity.resources,
|
activity.resources,
|
||||||
model.buttonIconId,
|
page.buttonIconId,
|
||||||
activity.theme
|
activity.theme
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
iconGravity =
|
iconGravity =
|
||||||
if (model.leftAlignedIcon) {
|
if (page.leftAlignedIcon) {
|
||||||
MaterialButton.ICON_GRAVITY_START
|
MaterialButton.ICON_GRAVITY_START
|
||||||
} else {
|
} else {
|
||||||
MaterialButton.ICON_GRAVITY_END
|
MaterialButton.ICON_GRAVITY_END
|
||||||
}
|
}
|
||||||
setOnClickListener {
|
setOnClickListener {
|
||||||
model.buttonAction.invoke(this@SetupPageViewHolder)
|
page.buttonAction.invoke(this@SetupPageViewHolder)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,416 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.input
|
|
||||||
|
|
||||||
import org.yuzu.yuzu_emu.features.input.model.NativeButton
|
|
||||||
import org.yuzu.yuzu_emu.features.input.model.NativeAnalog
|
|
||||||
import org.yuzu.yuzu_emu.features.input.model.InputType
|
|
||||||
import org.yuzu.yuzu_emu.features.input.model.ButtonName
|
|
||||||
import org.yuzu.yuzu_emu.features.input.model.NpadStyleIndex
|
|
||||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
|
||||||
import org.yuzu.yuzu_emu.utils.ParamPackage
|
|
||||||
import android.view.InputDevice
|
|
||||||
|
|
||||||
object NativeInput {
|
|
||||||
/**
|
|
||||||
* Default controller id for each device
|
|
||||||
*/
|
|
||||||
const val Player1Device = 0
|
|
||||||
const val Player2Device = 1
|
|
||||||
const val Player3Device = 2
|
|
||||||
const val Player4Device = 3
|
|
||||||
const val Player5Device = 4
|
|
||||||
const val Player6Device = 5
|
|
||||||
const val Player7Device = 6
|
|
||||||
const val Player8Device = 7
|
|
||||||
const val ConsoleDevice = 8
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Button states
|
|
||||||
*/
|
|
||||||
object ButtonState {
|
|
||||||
const val RELEASED = 0
|
|
||||||
const val PRESSED = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if pro controller isn't available and handheld is.
|
|
||||||
* Intended to check where the input overlay should direct its inputs.
|
|
||||||
*/
|
|
||||||
external fun isHandheldOnly(): Boolean
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles button press events for a gamepad.
|
|
||||||
* @param guid 32 character hexadecimal string consisting of the controller's PID+VID.
|
|
||||||
* @param port Port determined by controller connection order.
|
|
||||||
* @param buttonId The Android Keycode corresponding to this event.
|
|
||||||
* @param action Mask identifying which action is happening (button pressed down, or button released).
|
|
||||||
*/
|
|
||||||
external fun onGamePadButtonEvent(
|
|
||||||
guid: String,
|
|
||||||
port: Int,
|
|
||||||
buttonId: Int,
|
|
||||||
action: Int
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles axis movement events.
|
|
||||||
* @param guid 32 character hexadecimal string consisting of the controller's PID+VID.
|
|
||||||
* @param port Port determined by controller connection order.
|
|
||||||
* @param axis The axis ID.
|
|
||||||
* @param value Value along the given axis.
|
|
||||||
*/
|
|
||||||
external fun onGamePadAxisEvent(guid: String, port: Int, axis: Int, value: Float)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles motion events.
|
|
||||||
* @param guid 32 character hexadecimal string consisting of the controller's PID+VID.
|
|
||||||
* @param port Port determined by controller connection order.
|
|
||||||
* @param deltaTimestamp The finger id corresponding to this event.
|
|
||||||
* @param xGyro The value of the x-axis for the gyroscope.
|
|
||||||
* @param yGyro The value of the y-axis for the gyroscope.
|
|
||||||
* @param zGyro The value of the z-axis for the gyroscope.
|
|
||||||
* @param xAccel The value of the x-axis for the accelerometer.
|
|
||||||
* @param yAccel The value of the y-axis for the accelerometer.
|
|
||||||
* @param zAccel The value of the z-axis for the accelerometer.
|
|
||||||
*/
|
|
||||||
external fun onGamePadMotionEvent(
|
|
||||||
guid: String,
|
|
||||||
port: Int,
|
|
||||||
deltaTimestamp: Long,
|
|
||||||
xGyro: Float,
|
|
||||||
yGyro: Float,
|
|
||||||
zGyro: Float,
|
|
||||||
xAccel: Float,
|
|
||||||
yAccel: Float,
|
|
||||||
zAccel: Float
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Signals and load a nfc tag
|
|
||||||
* @param data Byte array containing all the data from a nfc tag.
|
|
||||||
*/
|
|
||||||
external fun onReadNfcTag(data: ByteArray?)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes current loaded nfc tag.
|
|
||||||
*/
|
|
||||||
external fun onRemoveNfcTag()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles touch press events.
|
|
||||||
* @param fingerId The finger id corresponding to this event.
|
|
||||||
* @param xAxis The value of the x-axis on the touchscreen.
|
|
||||||
* @param yAxis The value of the y-axis on the touchscreen.
|
|
||||||
*/
|
|
||||||
external fun onTouchPressed(fingerId: Int, xAxis: Float, yAxis: Float)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles touch movement.
|
|
||||||
* @param fingerId The finger id corresponding to this event.
|
|
||||||
* @param xAxis The value of the x-axis on the touchscreen.
|
|
||||||
* @param yAxis The value of the y-axis on the touchscreen.
|
|
||||||
*/
|
|
||||||
external fun onTouchMoved(fingerId: Int, xAxis: Float, yAxis: Float)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles touch release events.
|
|
||||||
* @param fingerId The finger id corresponding to this event
|
|
||||||
*/
|
|
||||||
external fun onTouchReleased(fingerId: Int)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends a button input to the global virtual controllers.
|
|
||||||
* @param port Port determined by controller connection order.
|
|
||||||
* @param button The [NativeButton] corresponding to this event.
|
|
||||||
* @param action Mask identifying which action is happening (button pressed down, or button released).
|
|
||||||
*/
|
|
||||||
fun onOverlayButtonEvent(port: Int, button: NativeButton, action: Int) =
|
|
||||||
onOverlayButtonEventImpl(port, button.int, action)
|
|
||||||
|
|
||||||
private external fun onOverlayButtonEventImpl(port: Int, buttonId: Int, action: Int)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends a joystick input to the global virtual controllers.
|
|
||||||
* @param port Port determined by controller connection order.
|
|
||||||
* @param stick The [NativeAnalog] corresponding to this event.
|
|
||||||
* @param xAxis Value along the X axis.
|
|
||||||
* @param yAxis Value along the Y axis.
|
|
||||||
*/
|
|
||||||
fun onOverlayJoystickEvent(port: Int, stick: NativeAnalog, xAxis: Float, yAxis: Float) =
|
|
||||||
onOverlayJoystickEventImpl(port, stick.int, xAxis, yAxis)
|
|
||||||
|
|
||||||
private external fun onOverlayJoystickEventImpl(
|
|
||||||
port: Int,
|
|
||||||
stickId: Int,
|
|
||||||
xAxis: Float,
|
|
||||||
yAxis: Float
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles motion events for the global virtual controllers.
|
|
||||||
* @param port Port determined by controller connection order
|
|
||||||
* @param deltaTimestamp The finger id corresponding to this event.
|
|
||||||
* @param xGyro The value of the x-axis for the gyroscope.
|
|
||||||
* @param yGyro The value of the y-axis for the gyroscope.
|
|
||||||
* @param zGyro The value of the z-axis for the gyroscope.
|
|
||||||
* @param xAccel The value of the x-axis for the accelerometer.
|
|
||||||
* @param yAccel The value of the y-axis for the accelerometer.
|
|
||||||
* @param zAccel The value of the z-axis for the accelerometer.
|
|
||||||
*/
|
|
||||||
external fun onDeviceMotionEvent(
|
|
||||||
port: Int,
|
|
||||||
deltaTimestamp: Long,
|
|
||||||
xGyro: Float,
|
|
||||||
yGyro: Float,
|
|
||||||
zGyro: Float,
|
|
||||||
xAccel: Float,
|
|
||||||
yAccel: Float,
|
|
||||||
zAccel: Float
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reloads all input devices from the currently loaded Settings::values.players into HID Core
|
|
||||||
*/
|
|
||||||
external fun reloadInputDevices()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers a controller to be used with mapping
|
|
||||||
* @param device An [InputDevice] or the input overlay wrapped with [YuzuInputDevice]
|
|
||||||
*/
|
|
||||||
external fun registerController(device: YuzuInputDevice)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the names of input devices that have been registered with the input subsystem via [registerController]
|
|
||||||
*/
|
|
||||||
external fun getInputDevices(): Array<String>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads all input profiles from disk. Must be called before creating a profile picker.
|
|
||||||
*/
|
|
||||||
external fun loadInputProfiles()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the names of each available input profile.
|
|
||||||
*/
|
|
||||||
external fun getInputProfileNames(): Array<String>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the user-provided name for an input profile is valid.
|
|
||||||
* @param name User-provided name for an input profile.
|
|
||||||
* @return Whether [name] is valid or not.
|
|
||||||
*/
|
|
||||||
external fun isProfileNameValid(name: String): Boolean
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new input profile.
|
|
||||||
* @param name The new profile's name.
|
|
||||||
* @param playerIndex Index of the player that's currently being edited. Used to write the profile
|
|
||||||
* name to this player's config.
|
|
||||||
* @return Whether creating the profile was successful or not.
|
|
||||||
*/
|
|
||||||
external fun createProfile(name: String, playerIndex: Int): Boolean
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes an input profile.
|
|
||||||
* @param name Name of the profile to delete.
|
|
||||||
* @param playerIndex Index of the player that's currently being edited. Used to remove the profile
|
|
||||||
* name from this player's config if they have it loaded.
|
|
||||||
* @return Whether deleting this profile was successful or not.
|
|
||||||
*/
|
|
||||||
external fun deleteProfile(name: String, playerIndex: Int): Boolean
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads an input profile.
|
|
||||||
* @param name Name of the input profile to load.
|
|
||||||
* @param playerIndex Index of the player that will have this profile loaded.
|
|
||||||
* @return Whether loading this profile was successful or not.
|
|
||||||
*/
|
|
||||||
external fun loadProfile(name: String, playerIndex: Int): Boolean
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves an input profile.
|
|
||||||
* @param name Name of the profile to save.
|
|
||||||
* @param playerIndex Index of the player that's currently being edited. Used to write the profile
|
|
||||||
* name to this player's config.
|
|
||||||
* @return Whether saving the profile was successful or not.
|
|
||||||
*/
|
|
||||||
external fun saveProfile(name: String, playerIndex: Int): Boolean
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Intended to be used immediately before a call to [NativeConfig.saveControlPlayerValues]
|
|
||||||
* Must be used while per-game config is loaded.
|
|
||||||
*/
|
|
||||||
external fun loadPerGameConfiguration(
|
|
||||||
playerIndex: Int,
|
|
||||||
selectedIndex: Int,
|
|
||||||
selectedProfileName: String
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tells the input subsystem to start listening for inputs to map.
|
|
||||||
* @param type Type of input to map as shown by the int property in each [InputType].
|
|
||||||
*/
|
|
||||||
external fun beginMapping(type: Int)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets an input's [ParamPackage] as a serialized string. Used for input verification before mapping.
|
|
||||||
* Must be run after [beginMapping] and before [stopMapping].
|
|
||||||
*/
|
|
||||||
external fun getNextInput(): String
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tells the input subsystem to stop listening for inputs to map.
|
|
||||||
*/
|
|
||||||
external fun stopMapping()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates a controller's mappings with auto-mapping params.
|
|
||||||
* @param playerIndex Index of the player to auto-map.
|
|
||||||
* @param deviceParams [ParamPackage] representing the device to auto-map as received
|
|
||||||
* from [getInputDevices].
|
|
||||||
* @param displayName Name of the device to auto-map as received from the "display" param in [deviceParams].
|
|
||||||
* Intended to be a way to provide a default name for a controller if the "display" param is empty.
|
|
||||||
*/
|
|
||||||
fun updateMappingsWithDefault(
|
|
||||||
playerIndex: Int,
|
|
||||||
deviceParams: ParamPackage,
|
|
||||||
displayName: String
|
|
||||||
) = updateMappingsWithDefaultImpl(playerIndex, deviceParams.serialize(), displayName)
|
|
||||||
|
|
||||||
private external fun updateMappingsWithDefaultImpl(
|
|
||||||
playerIndex: Int,
|
|
||||||
deviceParams: String,
|
|
||||||
displayName: String
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the params for a specific button.
|
|
||||||
* @param playerIndex Index of the player to get params from.
|
|
||||||
* @param button The [NativeButton] to get params for.
|
|
||||||
* @return A [ParamPackage] representing a player's specific button.
|
|
||||||
*/
|
|
||||||
fun getButtonParam(playerIndex: Int, button: NativeButton): ParamPackage =
|
|
||||||
ParamPackage(getButtonParamImpl(playerIndex, button.int))
|
|
||||||
|
|
||||||
private external fun getButtonParamImpl(playerIndex: Int, buttonId: Int): String
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the params for a specific button.
|
|
||||||
* @param playerIndex Index of the player to set params for.
|
|
||||||
* @param button The [NativeButton] to set params for.
|
|
||||||
* @param param A [ParamPackage] to set.
|
|
||||||
*/
|
|
||||||
fun setButtonParam(playerIndex: Int, button: NativeButton, param: ParamPackage) =
|
|
||||||
setButtonParamImpl(playerIndex, button.int, param.serialize())
|
|
||||||
|
|
||||||
private external fun setButtonParamImpl(playerIndex: Int, buttonId: Int, param: String)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the params for a specific stick.
|
|
||||||
* @param playerIndex Index of the player to get params from.
|
|
||||||
* @param stick The [NativeAnalog] to get params for.
|
|
||||||
* @return A [ParamPackage] representing a player's specific stick.
|
|
||||||
*/
|
|
||||||
fun getStickParam(playerIndex: Int, stick: NativeAnalog): ParamPackage =
|
|
||||||
ParamPackage(getStickParamImpl(playerIndex, stick.int))
|
|
||||||
|
|
||||||
private external fun getStickParamImpl(playerIndex: Int, stickId: Int): String
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the params for a specific stick.
|
|
||||||
* @param playerIndex Index of the player to set params for.
|
|
||||||
* @param stick The [NativeAnalog] to set params for.
|
|
||||||
* @param param A [ParamPackage] to set.
|
|
||||||
*/
|
|
||||||
fun setStickParam(playerIndex: Int, stick: NativeAnalog, param: ParamPackage) =
|
|
||||||
setStickParamImpl(playerIndex, stick.int, param.serialize())
|
|
||||||
|
|
||||||
private external fun setStickParamImpl(playerIndex: Int, stickId: Int, param: String)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the int representation of a [ButtonName]. Tells you what to show as the mapped input for
|
|
||||||
* a button/analog/other.
|
|
||||||
* @param param A [ParamPackage] that represents a specific button's params.
|
|
||||||
* @return The [ButtonName] for [param].
|
|
||||||
*/
|
|
||||||
fun getButtonName(param: ParamPackage): ButtonName =
|
|
||||||
ButtonName.from(getButtonNameImpl(param.serialize()))
|
|
||||||
|
|
||||||
private external fun getButtonNameImpl(param: String): Int
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets each supported [NpadStyleIndex] for a given player.
|
|
||||||
* @param playerIndex Index of the player to get supported indexes for.
|
|
||||||
* @return List of each supported [NpadStyleIndex].
|
|
||||||
*/
|
|
||||||
fun getSupportedStyleTags(playerIndex: Int): List<NpadStyleIndex> =
|
|
||||||
getSupportedStyleTagsImpl(playerIndex).map { NpadStyleIndex.from(it) }
|
|
||||||
|
|
||||||
private external fun getSupportedStyleTagsImpl(playerIndex: Int): IntArray
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the [NpadStyleIndex] for a given player.
|
|
||||||
* @param playerIndex Index of the player to get an [NpadStyleIndex] from.
|
|
||||||
* @return The [NpadStyleIndex] for a given player.
|
|
||||||
*/
|
|
||||||
fun getStyleIndex(playerIndex: Int): NpadStyleIndex =
|
|
||||||
NpadStyleIndex.from(getStyleIndexImpl(playerIndex))
|
|
||||||
|
|
||||||
private external fun getStyleIndexImpl(playerIndex: Int): Int
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the [NpadStyleIndex] for a given player.
|
|
||||||
* @param playerIndex Index of the player to change.
|
|
||||||
* @param style The new style to set.
|
|
||||||
*/
|
|
||||||
fun setStyleIndex(playerIndex: Int, style: NpadStyleIndex) =
|
|
||||||
setStyleIndexImpl(playerIndex, style.int)
|
|
||||||
|
|
||||||
private external fun setStyleIndexImpl(playerIndex: Int, styleIndex: Int)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if a device is a controller.
|
|
||||||
* @param params [ParamPackage] for an input device retrieved from [getInputDevices]
|
|
||||||
* @return Whether the device is a controller or not.
|
|
||||||
*/
|
|
||||||
fun isController(params: ParamPackage): Boolean = isControllerImpl(params.serialize())
|
|
||||||
|
|
||||||
private external fun isControllerImpl(params: String): Boolean
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if a controller is connected
|
|
||||||
* @param playerIndex Index of the player to check.
|
|
||||||
* @return Whether the player is connected or not.
|
|
||||||
*/
|
|
||||||
external fun getIsConnected(playerIndex: Int): Boolean
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Connects/disconnects a controller and ensures that connection order stays in-tact.
|
|
||||||
* @param playerIndex Index of the player to connect/disconnect.
|
|
||||||
* @param connected Whether to connect or disconnect this controller.
|
|
||||||
*/
|
|
||||||
fun connectControllers(playerIndex: Int, connected: Boolean = true) {
|
|
||||||
val connectedControllers = mutableListOf<Boolean>().apply {
|
|
||||||
if (connected) {
|
|
||||||
for (i in 0 until 8) {
|
|
||||||
add(i <= playerIndex)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (i in 0 until 8) {
|
|
||||||
add(i < playerIndex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
connectControllersImpl(connectedControllers.toBooleanArray())
|
|
||||||
}
|
|
||||||
|
|
||||||
private external fun connectControllersImpl(connected: BooleanArray)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resets all of the button and analog mappings for a player.
|
|
||||||
* @param playerIndex Index of the player that will have its mappings reset.
|
|
||||||
*/
|
|
||||||
external fun resetControllerMappings(playerIndex: Int)
|
|
||||||
}
|
|
@ -1,93 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.input
|
|
||||||
|
|
||||||
import android.view.InputDevice
|
|
||||||
import androidx.annotation.Keep
|
|
||||||
import org.yuzu.yuzu_emu.YuzuApplication
|
|
||||||
import org.yuzu.yuzu_emu.R
|
|
||||||
import org.yuzu.yuzu_emu.utils.InputHandler.getGUID
|
|
||||||
|
|
||||||
@Keep
|
|
||||||
interface YuzuInputDevice {
|
|
||||||
fun getName(): String
|
|
||||||
|
|
||||||
fun getGUID(): String
|
|
||||||
|
|
||||||
fun getPort(): Int
|
|
||||||
|
|
||||||
fun getSupportsVibration(): Boolean
|
|
||||||
|
|
||||||
fun vibrate(intensity: Float)
|
|
||||||
|
|
||||||
fun getAxes(): Array<Int> = arrayOf()
|
|
||||||
fun hasKeys(keys: IntArray): BooleanArray = BooleanArray(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
class YuzuPhysicalDevice(
|
|
||||||
private val device: InputDevice,
|
|
||||||
private val port: Int,
|
|
||||||
useSystemVibrator: Boolean
|
|
||||||
) : YuzuInputDevice {
|
|
||||||
private val vibrator = if (useSystemVibrator) {
|
|
||||||
YuzuVibrator.getSystemVibrator()
|
|
||||||
} else {
|
|
||||||
YuzuVibrator.getControllerVibrator(device)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getName(): String {
|
|
||||||
return device.name
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getGUID(): String {
|
|
||||||
return device.getGUID()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getPort(): Int {
|
|
||||||
return port
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getSupportsVibration(): Boolean {
|
|
||||||
return vibrator.supportsVibration()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun vibrate(intensity: Float) {
|
|
||||||
vibrator.vibrate(intensity)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getAxes(): Array<Int> = device.motionRanges.map { it.axis }.toTypedArray()
|
|
||||||
override fun hasKeys(keys: IntArray): BooleanArray = device.hasKeys(*keys)
|
|
||||||
}
|
|
||||||
|
|
||||||
class YuzuInputOverlayDevice(
|
|
||||||
private val vibration: Boolean,
|
|
||||||
private val port: Int
|
|
||||||
) : YuzuInputDevice {
|
|
||||||
private val vibrator = YuzuVibrator.getSystemVibrator()
|
|
||||||
|
|
||||||
override fun getName(): String {
|
|
||||||
return YuzuApplication.appContext.getString(R.string.input_overlay)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getGUID(): String {
|
|
||||||
return "00000000000000000000000000000000"
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getPort(): Int {
|
|
||||||
return port
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getSupportsVibration(): Boolean {
|
|
||||||
if (vibration) {
|
|
||||||
return vibrator.supportsVibration()
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun vibrate(intensity: Float) {
|
|
||||||
if (vibration) {
|
|
||||||
vibrator.vibrate(intensity)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,76 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.input
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.CombinedVibration
|
|
||||||
import android.os.VibrationEffect
|
|
||||||
import android.os.Vibrator
|
|
||||||
import android.os.VibratorManager
|
|
||||||
import android.view.InputDevice
|
|
||||||
import androidx.annotation.Keep
|
|
||||||
import androidx.annotation.RequiresApi
|
|
||||||
import org.yuzu.yuzu_emu.YuzuApplication
|
|
||||||
|
|
||||||
@Keep
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
interface YuzuVibrator {
|
|
||||||
fun supportsVibration(): Boolean
|
|
||||||
|
|
||||||
fun vibrate(intensity: Float)
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun getControllerVibrator(device: InputDevice): YuzuVibrator =
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
||||||
YuzuVibratorManager(device.vibratorManager)
|
|
||||||
} else {
|
|
||||||
YuzuVibratorManagerCompat(device.vibrator)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getSystemVibrator(): YuzuVibrator =
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
||||||
val vibratorManager = YuzuApplication.appContext
|
|
||||||
.getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager
|
|
||||||
YuzuVibratorManager(vibratorManager)
|
|
||||||
} else {
|
|
||||||
val vibrator = YuzuApplication.appContext
|
|
||||||
.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
|
|
||||||
YuzuVibratorManagerCompat(vibrator)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getVibrationEffect(intensity: Float): VibrationEffect? {
|
|
||||||
if (intensity > 0f) {
|
|
||||||
return VibrationEffect.createOneShot(
|
|
||||||
50,
|
|
||||||
(255.0 * intensity).toInt().coerceIn(1, 255)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.S)
|
|
||||||
class YuzuVibratorManager(private val vibratorManager: VibratorManager) : YuzuVibrator {
|
|
||||||
override fun supportsVibration(): Boolean {
|
|
||||||
return vibratorManager.vibratorIds.isNotEmpty()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun vibrate(intensity: Float) {
|
|
||||||
val vibration = YuzuVibrator.getVibrationEffect(intensity) ?: return
|
|
||||||
vibratorManager.vibrate(CombinedVibration.createParallel(vibration))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class YuzuVibratorManagerCompat(private val vibrator: Vibrator) : YuzuVibrator {
|
|
||||||
override fun supportsVibration(): Boolean {
|
|
||||||
return vibrator.hasVibrator()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun vibrate(intensity: Float) {
|
|
||||||
val vibration = YuzuVibrator.getVibrationEffect(intensity) ?: return
|
|
||||||
vibrator.vibrate(vibration)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.input.model
|
|
||||||
|
|
||||||
enum class AnalogDirection(val int: Int, val param: String) {
|
|
||||||
Up(0, "up"),
|
|
||||||
Down(1, "down"),
|
|
||||||
Left(2, "left"),
|
|
||||||
Right(3, "right")
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.input.model
|
|
||||||
|
|
||||||
// Loosely matches the enum in common/input.h
|
|
||||||
enum class ButtonName(val int: Int) {
|
|
||||||
Invalid(1),
|
|
||||||
|
|
||||||
// This will display the engine name instead of the button name
|
|
||||||
Engine(2),
|
|
||||||
|
|
||||||
// This will display the button by value instead of the button name
|
|
||||||
Value(3);
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun from(int: Int): ButtonName = entries.firstOrNull { it.int == int } ?: Invalid
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.input.model
|
|
||||||
|
|
||||||
// Must match the corresponding enum in input_common/main.h
|
|
||||||
enum class InputType(val int: Int) {
|
|
||||||
None(0),
|
|
||||||
Button(1),
|
|
||||||
Stick(2),
|
|
||||||
Motion(3),
|
|
||||||
Touch(4)
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.input.model
|
|
||||||
|
|
||||||
// Must match enum in src/common/settings_input.h
|
|
||||||
enum class NativeAnalog(val int: Int) {
|
|
||||||
LStick(0),
|
|
||||||
RStick(1);
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun from(int: Int): NativeAnalog = entries.firstOrNull { it.int == int } ?: LStick
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.input.model
|
|
||||||
|
|
||||||
// Must match enum in src/common/settings_input.h
|
|
||||||
enum class NativeButton(val int: Int) {
|
|
||||||
A(0),
|
|
||||||
B(1),
|
|
||||||
X(2),
|
|
||||||
Y(3),
|
|
||||||
LStick(4),
|
|
||||||
RStick(5),
|
|
||||||
L(6),
|
|
||||||
R(7),
|
|
||||||
ZL(8),
|
|
||||||
ZR(9),
|
|
||||||
Plus(10),
|
|
||||||
Minus(11),
|
|
||||||
|
|
||||||
DLeft(12),
|
|
||||||
DUp(13),
|
|
||||||
DRight(14),
|
|
||||||
DDown(15),
|
|
||||||
|
|
||||||
SLLeft(16),
|
|
||||||
SRLeft(17),
|
|
||||||
|
|
||||||
Home(18),
|
|
||||||
Capture(19),
|
|
||||||
|
|
||||||
SLRight(20),
|
|
||||||
SRRight(21);
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun from(int: Int): NativeButton = entries.firstOrNull { it.int == int } ?: A
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.input.model
|
|
||||||
|
|
||||||
// Must match enum in src/common/settings_input.h
|
|
||||||
enum class NativeTrigger(val int: Int) {
|
|
||||||
LTrigger(0),
|
|
||||||
RTrigger(1)
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.input.model
|
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import org.yuzu.yuzu_emu.R
|
|
||||||
|
|
||||||
// Must match enum in src/core/hid/hid_types.h
|
|
||||||
enum class NpadStyleIndex(val int: Int, @StringRes val nameId: Int = 0) {
|
|
||||||
None(0),
|
|
||||||
Fullkey(3, R.string.pro_controller),
|
|
||||||
Handheld(4, R.string.handheld),
|
|
||||||
HandheldNES(4),
|
|
||||||
JoyconDual(5, R.string.dual_joycons),
|
|
||||||
JoyconLeft(6, R.string.left_joycon),
|
|
||||||
JoyconRight(7, R.string.right_joycon),
|
|
||||||
GameCube(8, R.string.gamecube_controller),
|
|
||||||
Pokeball(9),
|
|
||||||
NES(10),
|
|
||||||
SNES(12),
|
|
||||||
N64(13),
|
|
||||||
SegaGenesis(14),
|
|
||||||
SystemExt(32),
|
|
||||||
System(33);
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun from(int: Int): NpadStyleIndex = entries.firstOrNull { it.int == int } ?: None
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,83 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.input.model
|
|
||||||
|
|
||||||
import androidx.annotation.Keep
|
|
||||||
|
|
||||||
@Keep
|
|
||||||
data class PlayerInput(
|
|
||||||
var connected: Boolean,
|
|
||||||
var buttons: Array<String>,
|
|
||||||
var analogs: Array<String>,
|
|
||||||
var motions: Array<String>,
|
|
||||||
|
|
||||||
var vibrationEnabled: Boolean,
|
|
||||||
var vibrationStrength: Int,
|
|
||||||
|
|
||||||
var bodyColorLeft: Long,
|
|
||||||
var bodyColorRight: Long,
|
|
||||||
var buttonColorLeft: Long,
|
|
||||||
var buttonColorRight: Long,
|
|
||||||
var profileName: String,
|
|
||||||
|
|
||||||
var useSystemVibrator: Boolean
|
|
||||||
) {
|
|
||||||
// It's recommended to use the generated equals() and hashCode() methods
|
|
||||||
// when using arrays in a data class
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as PlayerInput
|
|
||||||
|
|
||||||
if (connected != other.connected) return false
|
|
||||||
if (!buttons.contentEquals(other.buttons)) return false
|
|
||||||
if (!analogs.contentEquals(other.analogs)) return false
|
|
||||||
if (!motions.contentEquals(other.motions)) return false
|
|
||||||
if (vibrationEnabled != other.vibrationEnabled) return false
|
|
||||||
if (vibrationStrength != other.vibrationStrength) return false
|
|
||||||
if (bodyColorLeft != other.bodyColorLeft) return false
|
|
||||||
if (bodyColorRight != other.bodyColorRight) return false
|
|
||||||
if (buttonColorLeft != other.buttonColorLeft) return false
|
|
||||||
if (buttonColorRight != other.buttonColorRight) return false
|
|
||||||
if (profileName != other.profileName) return false
|
|
||||||
return useSystemVibrator == other.useSystemVibrator
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
var result = connected.hashCode()
|
|
||||||
result = 31 * result + buttons.contentHashCode()
|
|
||||||
result = 31 * result + analogs.contentHashCode()
|
|
||||||
result = 31 * result + motions.contentHashCode()
|
|
||||||
result = 31 * result + vibrationEnabled.hashCode()
|
|
||||||
result = 31 * result + vibrationStrength
|
|
||||||
result = 31 * result + bodyColorLeft.hashCode()
|
|
||||||
result = 31 * result + bodyColorRight.hashCode()
|
|
||||||
result = 31 * result + buttonColorLeft.hashCode()
|
|
||||||
result = 31 * result + buttonColorRight.hashCode()
|
|
||||||
result = 31 * result + profileName.hashCode()
|
|
||||||
result = 31 * result + useSystemVibrator.hashCode()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
fun hasMapping(): Boolean {
|
|
||||||
var hasMapping = false
|
|
||||||
buttons.forEach {
|
|
||||||
if (it != "[empty]" && it.isNotEmpty()) {
|
|
||||||
hasMapping = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
analogs.forEach {
|
|
||||||
if (it != "[empty]" && it.isNotEmpty()) {
|
|
||||||
hasMapping = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
motions.forEach {
|
|
||||||
if (it != "[empty]" && it.isNotEmpty()) {
|
|
||||||
hasMapping = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return hasMapping
|
|
||||||
}
|
|
||||||
}
|
|
@ -18,15 +18,7 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
|
|||||||
RENDERER_REACTIVE_FLUSHING("use_reactive_flushing"),
|
RENDERER_REACTIVE_FLUSHING("use_reactive_flushing"),
|
||||||
RENDERER_DEBUG("debug"),
|
RENDERER_DEBUG("debug"),
|
||||||
PICTURE_IN_PICTURE("picture_in_picture"),
|
PICTURE_IN_PICTURE("picture_in_picture"),
|
||||||
USE_CUSTOM_RTC("custom_rtc_enabled"),
|
USE_CUSTOM_RTC("custom_rtc_enabled");
|
||||||
BLACK_BACKGROUNDS("black_backgrounds"),
|
|
||||||
JOYSTICK_REL_CENTER("joystick_rel_center"),
|
|
||||||
DPAD_SLIDE("dpad_slide"),
|
|
||||||
HAPTIC_FEEDBACK("haptic_feedback"),
|
|
||||||
SHOW_PERFORMANCE_OVERLAY("show_performance_overlay"),
|
|
||||||
SHOW_INPUT_OVERLAY("show_input_overlay"),
|
|
||||||
TOUCHSCREEN("touchscreen"),
|
|
||||||
SHOW_THERMAL_OVERLAY("show_thermal_overlay");
|
|
||||||
|
|
||||||
override fun getBoolean(needsGlobal: Boolean): Boolean =
|
override fun getBoolean(needsGlobal: Boolean): Boolean =
|
||||||
NativeConfig.getBoolean(key, needsGlobal)
|
NativeConfig.getBoolean(key, needsGlobal)
|
||||||
|
@ -18,15 +18,7 @@ enum class IntSetting(override val key: String) : AbstractIntSetting {
|
|||||||
RENDERER_ANTI_ALIASING("anti_aliasing"),
|
RENDERER_ANTI_ALIASING("anti_aliasing"),
|
||||||
RENDERER_SCREEN_LAYOUT("screen_layout"),
|
RENDERER_SCREEN_LAYOUT("screen_layout"),
|
||||||
RENDERER_ASPECT_RATIO("aspect_ratio"),
|
RENDERER_ASPECT_RATIO("aspect_ratio"),
|
||||||
AUDIO_OUTPUT_ENGINE("output_engine"),
|
AUDIO_OUTPUT_ENGINE("output_engine");
|
||||||
MAX_ANISOTROPY("max_anisotropy"),
|
|
||||||
THEME("theme"),
|
|
||||||
THEME_MODE("theme_mode"),
|
|
||||||
OVERLAY_SCALE("control_scale"),
|
|
||||||
OVERLAY_OPACITY("control_opacity"),
|
|
||||||
LOCK_DRAWER("lock_drawer"),
|
|
||||||
VERTICAL_ALIGNMENT("vertical_alignment"),
|
|
||||||
FSR_SHARPENING_SLIDER("fsr_sharpening_slider");
|
|
||||||
|
|
||||||
override fun getInt(needsGlobal: Boolean): Int = NativeConfig.getInt(key, needsGlobal)
|
override fun getInt(needsGlobal: Boolean): Int = NativeConfig.getInt(key, needsGlobal)
|
||||||
|
|
||||||
|
@ -4,34 +4,29 @@
|
|||||||
package org.yuzu.yuzu_emu.features.settings.model
|
package org.yuzu.yuzu_emu.features.settings.model
|
||||||
|
|
||||||
import org.yuzu.yuzu_emu.R
|
import org.yuzu.yuzu_emu.R
|
||||||
import org.yuzu.yuzu_emu.YuzuApplication
|
|
||||||
|
|
||||||
object Settings {
|
object Settings {
|
||||||
enum class MenuTag(val titleId: Int = 0) {
|
enum class MenuTag(val titleId: Int) {
|
||||||
SECTION_ROOT(R.string.advanced_settings),
|
SECTION_ROOT(R.string.advanced_settings),
|
||||||
SECTION_SYSTEM(R.string.preferences_system),
|
SECTION_SYSTEM(R.string.preferences_system),
|
||||||
SECTION_RENDERER(R.string.preferences_graphics),
|
SECTION_RENDERER(R.string.preferences_graphics),
|
||||||
SECTION_AUDIO(R.string.preferences_audio),
|
SECTION_AUDIO(R.string.preferences_audio),
|
||||||
SECTION_INPUT(R.string.preferences_controls),
|
|
||||||
SECTION_INPUT_PLAYER_ONE,
|
|
||||||
SECTION_INPUT_PLAYER_TWO,
|
|
||||||
SECTION_INPUT_PLAYER_THREE,
|
|
||||||
SECTION_INPUT_PLAYER_FOUR,
|
|
||||||
SECTION_INPUT_PLAYER_FIVE,
|
|
||||||
SECTION_INPUT_PLAYER_SIX,
|
|
||||||
SECTION_INPUT_PLAYER_SEVEN,
|
|
||||||
SECTION_INPUT_PLAYER_EIGHT,
|
|
||||||
SECTION_THEME(R.string.preferences_theme),
|
SECTION_THEME(R.string.preferences_theme),
|
||||||
SECTION_DEBUG(R.string.preferences_debug);
|
SECTION_DEBUG(R.string.preferences_debug);
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getPlayerString(player: Int): String =
|
|
||||||
YuzuApplication.appContext.getString(R.string.preferences_player, player)
|
|
||||||
|
|
||||||
const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch"
|
|
||||||
const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown"
|
const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown"
|
||||||
|
|
||||||
// Deprecated input overlay preference keys
|
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_SCALE = "controlScale"
|
||||||
const val PREF_CONTROL_OPACITY = "controlOpacity"
|
const val PREF_CONTROL_OPACITY = "controlOpacity"
|
||||||
const val PREF_TOUCH_ENABLED = "isTouchEnabled"
|
const val PREF_TOUCH_ENABLED = "isTouchEnabled"
|
||||||
@ -52,12 +47,23 @@ object Settings {
|
|||||||
const val PREF_BUTTON_STICK_R = "buttonToggle14"
|
const val PREF_BUTTON_STICK_R = "buttonToggle14"
|
||||||
const val PREF_BUTTON_HOME = "buttonToggle15"
|
const val PREF_BUTTON_HOME = "buttonToggle15"
|
||||||
const val PREF_BUTTON_SCREENSHOT = "buttonToggle16"
|
const val PREF_BUTTON_SCREENSHOT = "buttonToggle16"
|
||||||
|
|
||||||
const val PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER = "EmulationMenuSettings_JoystickRelCenter"
|
const val PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER = "EmulationMenuSettings_JoystickRelCenter"
|
||||||
const val PREF_MENU_SETTINGS_DPAD_SLIDE = "EmulationMenuSettings_DpadSlideEnable"
|
const val PREF_MENU_SETTINGS_DPAD_SLIDE = "EmulationMenuSettings_DpadSlideEnable"
|
||||||
const val PREF_MENU_SETTINGS_HAPTICS = "EmulationMenuSettings_Haptics"
|
const val PREF_MENU_SETTINGS_HAPTICS = "EmulationMenuSettings_Haptics"
|
||||||
const val PREF_MENU_SETTINGS_SHOW_FPS = "EmulationMenuSettings_ShowFps"
|
const val PREF_MENU_SETTINGS_SHOW_FPS = "EmulationMenuSettings_ShowFps"
|
||||||
const val PREF_MENU_SETTINGS_SHOW_OVERLAY = "EmulationMenuSettings_ShowOverlay"
|
const val PREF_MENU_SETTINGS_SHOW_OVERLAY = "EmulationMenuSettings_ShowOverlay"
|
||||||
|
|
||||||
|
const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch"
|
||||||
|
const val PREF_THEME = "Theme"
|
||||||
|
const val PREF_THEME_MODE = "ThemeMode"
|
||||||
|
const val PREF_BLACK_BACKGROUNDS = "BlackBackgrounds"
|
||||||
|
|
||||||
val overlayPreferences = listOf(
|
val overlayPreferences = listOf(
|
||||||
|
PREF_OVERLAY_VERSION,
|
||||||
|
PREF_CONTROL_SCALE,
|
||||||
|
PREF_CONTROL_OPACITY,
|
||||||
|
PREF_TOUCH_ENABLED,
|
||||||
PREF_BUTTON_A,
|
PREF_BUTTON_A,
|
||||||
PREF_BUTTON_B,
|
PREF_BUTTON_B,
|
||||||
PREF_BUTTON_X,
|
PREF_BUTTON_X,
|
||||||
@ -77,44 +83,7 @@ object Settings {
|
|||||||
PREF_BUTTON_STICK_R
|
PREF_BUTTON_STICK_R
|
||||||
)
|
)
|
||||||
|
|
||||||
// Deprecated layout preference keys
|
const val LayoutOption_Unspecified = 0
|
||||||
const val PREF_LANDSCAPE_SUFFIX = "_Landscape"
|
const val LayoutOption_MobilePortrait = 4
|
||||||
const val PREF_PORTRAIT_SUFFIX = "_Portrait"
|
const val LayoutOption_MobileLandscape = 5
|
||||||
const val PREF_FOLDABLE_SUFFIX = "_Foldable"
|
|
||||||
val overlayLayoutSuffixes = listOf(
|
|
||||||
PREF_LANDSCAPE_SUFFIX,
|
|
||||||
PREF_PORTRAIT_SUFFIX,
|
|
||||||
PREF_FOLDABLE_SUFFIX
|
|
||||||
)
|
|
||||||
|
|
||||||
// Deprecated theme preference keys
|
|
||||||
const val PREF_THEME = "Theme"
|
|
||||||
const val PREF_THEME_MODE = "ThemeMode"
|
|
||||||
const val PREF_BLACK_BACKGROUNDS = "BlackBackgrounds"
|
|
||||||
|
|
||||||
enum class EmulationOrientation(val int: Int) {
|
|
||||||
Unspecified(0),
|
|
||||||
SensorLandscape(5),
|
|
||||||
Landscape(1),
|
|
||||||
ReverseLandscape(2),
|
|
||||||
SensorPortrait(6),
|
|
||||||
Portrait(4),
|
|
||||||
ReversePortrait(3);
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun from(int: Int): EmulationOrientation =
|
|
||||||
entries.firstOrNull { it.int == int } ?: Unspecified
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class EmulationVerticalAlignment(val int: Int) {
|
|
||||||
Top(1),
|
|
||||||
Center(0),
|
|
||||||
Bottom(2);
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun from(int: Int): EmulationVerticalAlignment =
|
|
||||||
entries.firstOrNull { it.int == int } ?: Center
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -6,8 +6,7 @@ package org.yuzu.yuzu_emu.features.settings.model
|
|||||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||||
|
|
||||||
enum class StringSetting(override val key: String) : AbstractStringSetting {
|
enum class StringSetting(override val key: String) : AbstractStringSetting {
|
||||||
DRIVER_PATH("driver_path"),
|
DRIVER_PATH("driver_path");
|
||||||
DEVICE_NAME("device_name");
|
|
||||||
|
|
||||||
override fun getString(needsGlobal: Boolean): String = NativeConfig.getString(key, needsGlobal)
|
override fun getString(needsGlobal: Boolean): String = NativeConfig.getString(key, needsGlobal)
|
||||||
|
|
||||||
|
@ -1,31 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.settings.model.view
|
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import org.yuzu.yuzu_emu.features.input.NativeInput
|
|
||||||
import org.yuzu.yuzu_emu.features.input.model.AnalogDirection
|
|
||||||
import org.yuzu.yuzu_emu.features.input.model.InputType
|
|
||||||
import org.yuzu.yuzu_emu.features.input.model.NativeAnalog
|
|
||||||
import org.yuzu.yuzu_emu.utils.ParamPackage
|
|
||||||
|
|
||||||
class AnalogInputSetting(
|
|
||||||
override val playerIndex: Int,
|
|
||||||
val nativeAnalog: NativeAnalog,
|
|
||||||
val analogDirection: AnalogDirection,
|
|
||||||
@StringRes titleId: Int = 0,
|
|
||||||
titleString: String = ""
|
|
||||||
) : InputSetting(titleId, titleString) {
|
|
||||||
override val type = TYPE_INPUT
|
|
||||||
override val inputType = InputType.Stick
|
|
||||||
|
|
||||||
override fun getSelectedValue(): String {
|
|
||||||
val params = NativeInput.getStickParam(playerIndex, nativeAnalog)
|
|
||||||
val analog = analogToText(params, analogDirection.param)
|
|
||||||
return getDisplayString(params, analog)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setSelectedValue(param: ParamPackage) =
|
|
||||||
NativeInput.setStickParam(playerIndex, nativeAnalog, param)
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.settings.model.view
|
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import org.yuzu.yuzu_emu.utils.ParamPackage
|
|
||||||
import org.yuzu.yuzu_emu.features.input.NativeInput
|
|
||||||
import org.yuzu.yuzu_emu.features.input.model.InputType
|
|
||||||
import org.yuzu.yuzu_emu.features.input.model.NativeButton
|
|
||||||
|
|
||||||
class ButtonInputSetting(
|
|
||||||
override val playerIndex: Int,
|
|
||||||
val nativeButton: NativeButton,
|
|
||||||
@StringRes titleId: Int = 0,
|
|
||||||
titleString: String = ""
|
|
||||||
) : InputSetting(titleId, titleString) {
|
|
||||||
override val type = TYPE_INPUT
|
|
||||||
override val inputType = InputType.Button
|
|
||||||
|
|
||||||
override fun getSelectedValue(): String {
|
|
||||||
val params = NativeInput.getButtonParam(playerIndex, nativeButton)
|
|
||||||
val button = buttonToText(params)
|
|
||||||
return getDisplayString(params, button)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setSelectedValue(param: ParamPackage) =
|
|
||||||
NativeInput.setButtonParam(playerIndex, nativeButton, param)
|
|
||||||
}
|
|
@ -3,16 +3,13 @@
|
|||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.settings.model.view
|
package org.yuzu.yuzu_emu.features.settings.model.view
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractLongSetting
|
import org.yuzu.yuzu_emu.features.settings.model.AbstractLongSetting
|
||||||
|
|
||||||
class DateTimeSetting(
|
class DateTimeSetting(
|
||||||
private val longSetting: AbstractLongSetting,
|
private val longSetting: AbstractLongSetting,
|
||||||
@StringRes titleId: Int = 0,
|
titleId: Int,
|
||||||
titleString: String = "",
|
descriptionId: Int
|
||||||
@StringRes descriptionId: Int = 0,
|
) : SettingsItem(longSetting, titleId, descriptionId) {
|
||||||
descriptionString: String = ""
|
|
||||||
) : SettingsItem(longSetting, titleId, titleString, descriptionId, descriptionString) {
|
|
||||||
override val type = TYPE_DATETIME_SETTING
|
override val type = TYPE_DATETIME_SETTING
|
||||||
|
|
||||||
fun getValue(needsGlobal: Boolean = false): Long = longSetting.getLong(needsGlobal)
|
fun getValue(needsGlobal: Boolean = false): Long = longSetting.getLong(needsGlobal)
|
||||||
|
@ -3,11 +3,8 @@
|
|||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.settings.model.view
|
package org.yuzu.yuzu_emu.features.settings.model.view
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
|
|
||||||
class HeaderSetting(
|
class HeaderSetting(
|
||||||
@StringRes titleId: Int = 0,
|
titleId: Int
|
||||||
titleString: String = ""
|
) : SettingsItem(emptySetting, titleId, 0) {
|
||||||
) : SettingsItem(emptySetting, titleId, titleString, 0, "") {
|
|
||||||
override val type = TYPE_HEADER
|
override val type = TYPE_HEADER
|
||||||
}
|
}
|
||||||
|
@ -1,32 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.settings.model.view
|
|
||||||
|
|
||||||
import org.yuzu.yuzu_emu.R
|
|
||||||
import org.yuzu.yuzu_emu.features.input.NativeInput
|
|
||||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
|
||||||
|
|
||||||
class InputProfileSetting(private val playerIndex: Int) :
|
|
||||||
SettingsItem(emptySetting, R.string.profile, "", 0, "") {
|
|
||||||
override val type = TYPE_INPUT_PROFILE
|
|
||||||
|
|
||||||
fun getCurrentProfile(): String =
|
|
||||||
NativeConfig.getInputSettings(true)[playerIndex].profileName
|
|
||||||
|
|
||||||
fun getProfileNames(): Array<String> = NativeInput.getInputProfileNames()
|
|
||||||
|
|
||||||
fun isProfileNameValid(name: String): Boolean = NativeInput.isProfileNameValid(name)
|
|
||||||
|
|
||||||
fun createProfile(name: String): Boolean = NativeInput.createProfile(name, playerIndex)
|
|
||||||
|
|
||||||
fun deleteProfile(name: String): Boolean = NativeInput.deleteProfile(name, playerIndex)
|
|
||||||
|
|
||||||
fun loadProfile(name: String): Boolean {
|
|
||||||
val result = NativeInput.loadProfile(name, playerIndex)
|
|
||||||
NativeInput.reloadInputDevices()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
fun saveProfile(name: String): Boolean = NativeInput.saveProfile(name, playerIndex)
|
|
||||||
}
|
|
@ -1,134 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.settings.model.view
|
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import org.yuzu.yuzu_emu.R
|
|
||||||
import org.yuzu.yuzu_emu.YuzuApplication
|
|
||||||
import org.yuzu.yuzu_emu.features.input.NativeInput
|
|
||||||
import org.yuzu.yuzu_emu.features.input.model.ButtonName
|
|
||||||
import org.yuzu.yuzu_emu.features.input.model.InputType
|
|
||||||
import org.yuzu.yuzu_emu.utils.ParamPackage
|
|
||||||
|
|
||||||
sealed class InputSetting(
|
|
||||||
@StringRes titleId: Int,
|
|
||||||
titleString: String
|
|
||||||
) : SettingsItem(emptySetting, titleId, titleString, 0, "") {
|
|
||||||
override val type = TYPE_INPUT
|
|
||||||
abstract val inputType: InputType
|
|
||||||
abstract val playerIndex: Int
|
|
||||||
|
|
||||||
protected val context get() = YuzuApplication.appContext
|
|
||||||
|
|
||||||
abstract fun getSelectedValue(): String
|
|
||||||
|
|
||||||
abstract fun setSelectedValue(param: ParamPackage)
|
|
||||||
|
|
||||||
protected fun getDisplayString(params: ParamPackage, control: String): String {
|
|
||||||
val deviceName = params.get("display", "")
|
|
||||||
deviceName.ifEmpty {
|
|
||||||
return context.getString(R.string.not_set)
|
|
||||||
}
|
|
||||||
return "$deviceName: $control"
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getDirectionName(direction: String): String =
|
|
||||||
when (direction) {
|
|
||||||
"up" -> context.getString(R.string.up)
|
|
||||||
"down" -> context.getString(R.string.down)
|
|
||||||
"left" -> context.getString(R.string.left)
|
|
||||||
"right" -> context.getString(R.string.right)
|
|
||||||
else -> direction
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fun buttonToText(param: ParamPackage): String {
|
|
||||||
if (!param.has("engine")) {
|
|
||||||
return context.getString(R.string.not_set)
|
|
||||||
}
|
|
||||||
|
|
||||||
val toggle = if (param.get("toggle", false)) "~" else ""
|
|
||||||
val inverted = if (param.get("inverted", false)) "!" else ""
|
|
||||||
val invert = if (param.get("invert", "+") == "-") "-" else ""
|
|
||||||
val turbo = if (param.get("turbo", false)) "$" else ""
|
|
||||||
val commonButtonName = NativeInput.getButtonName(param)
|
|
||||||
|
|
||||||
if (commonButtonName == ButtonName.Invalid) {
|
|
||||||
return context.getString(R.string.invalid)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (commonButtonName == ButtonName.Engine) {
|
|
||||||
return param.get("engine", "")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (commonButtonName == ButtonName.Value) {
|
|
||||||
if (param.has("hat")) {
|
|
||||||
val hat = getDirectionName(param.get("direction", ""))
|
|
||||||
return context.getString(R.string.qualified_hat, turbo, toggle, inverted, hat)
|
|
||||||
}
|
|
||||||
if (param.has("axis")) {
|
|
||||||
val axis = param.get("axis", "")
|
|
||||||
return context.getString(
|
|
||||||
R.string.qualified_button_stick_axis,
|
|
||||||
toggle,
|
|
||||||
inverted,
|
|
||||||
invert,
|
|
||||||
axis
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (param.has("button")) {
|
|
||||||
val button = param.get("button", "")
|
|
||||||
return context.getString(R.string.qualified_button, turbo, toggle, inverted, button)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return context.getString(R.string.unknown)
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fun analogToText(param: ParamPackage, direction: String): String {
|
|
||||||
if (!param.has("engine")) {
|
|
||||||
return context.getString(R.string.not_set)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (param.get("engine", "") == "analog_from_button") {
|
|
||||||
return buttonToText(ParamPackage(param.get(direction, "")))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!param.has("axis_x") || !param.has("axis_y")) {
|
|
||||||
return context.getString(R.string.unknown)
|
|
||||||
}
|
|
||||||
|
|
||||||
val xAxis = param.get("axis_x", "")
|
|
||||||
val yAxis = param.get("axis_y", "")
|
|
||||||
val xInvert = param.get("invert_x", "+") == "-"
|
|
||||||
val yInvert = param.get("invert_y", "+") == "-"
|
|
||||||
|
|
||||||
if (direction == "modifier") {
|
|
||||||
return context.getString(R.string.unused)
|
|
||||||
}
|
|
||||||
|
|
||||||
when (direction) {
|
|
||||||
"up" -> {
|
|
||||||
val yInvertString = if (yInvert) "+" else "-"
|
|
||||||
return context.getString(R.string.qualified_axis, yAxis, yInvertString)
|
|
||||||
}
|
|
||||||
|
|
||||||
"down" -> {
|
|
||||||
val yInvertString = if (yInvert) "-" else "+"
|
|
||||||
return context.getString(R.string.qualified_axis, yAxis, yInvertString)
|
|
||||||
}
|
|
||||||
|
|
||||||
"left" -> {
|
|
||||||
val xInvertString = if (xInvert) "+" else "-"
|
|
||||||
return context.getString(R.string.qualified_axis, xAxis, xInvertString)
|
|
||||||
}
|
|
||||||
|
|
||||||
"right" -> {
|
|
||||||
val xInvertString = if (xInvert) "-" else "+"
|
|
||||||
return context.getString(R.string.qualified_axis, xAxis, xInvertString)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return context.getString(R.string.unknown)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.settings.model.view
|
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
|
|
||||||
|
|
||||||
class IntSingleChoiceSetting(
|
|
||||||
private val intSetting: AbstractIntSetting,
|
|
||||||
@StringRes titleId: Int = 0,
|
|
||||||
titleString: String = "",
|
|
||||||
@StringRes descriptionId: Int = 0,
|
|
||||||
descriptionString: String = "",
|
|
||||||
val choices: Array<String>,
|
|
||||||
val values: Array<Int>
|
|
||||||
) : SettingsItem(intSetting, titleId, titleString, descriptionId, descriptionString) {
|
|
||||||
override val type = TYPE_INT_SINGLE_CHOICE
|
|
||||||
|
|
||||||
fun getValueAt(index: Int): Int =
|
|
||||||
if (values.indices.contains(index)) values[index] else -1
|
|
||||||
|
|
||||||
fun getChoiceAt(index: Int): String =
|
|
||||||
if (choices.indices.contains(index)) choices[index] else ""
|
|
||||||
|
|
||||||
fun getSelectedValue(needsGlobal: Boolean = false) = intSetting.getInt(needsGlobal)
|
|
||||||
fun setSelectedValue(value: Int) = intSetting.setInt(value)
|
|
||||||
|
|
||||||
val selectedValueIndex: Int
|
|
||||||
get() {
|
|
||||||
for (i in values.indices) {
|
|
||||||
if (values[i] == getSelectedValue()) {
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.settings.model.view
|
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import org.yuzu.yuzu_emu.features.input.NativeInput
|
|
||||||
import org.yuzu.yuzu_emu.features.input.model.InputType
|
|
||||||
import org.yuzu.yuzu_emu.features.input.model.NativeAnalog
|
|
||||||
import org.yuzu.yuzu_emu.utils.ParamPackage
|
|
||||||
|
|
||||||
class ModifierInputSetting(
|
|
||||||
override val playerIndex: Int,
|
|
||||||
val nativeAnalog: NativeAnalog,
|
|
||||||
@StringRes titleId: Int = 0,
|
|
||||||
titleString: String = ""
|
|
||||||
) : InputSetting(titleId, titleString) {
|
|
||||||
override val inputType = InputType.Button
|
|
||||||
|
|
||||||
override fun getSelectedValue(): String {
|
|
||||||
val analogParam = NativeInput.getStickParam(playerIndex, nativeAnalog)
|
|
||||||
val modifierParam = ParamPackage(analogParam.get("modifier", ""))
|
|
||||||
return buttonToText(modifierParam)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setSelectedValue(param: ParamPackage) {
|
|
||||||
val newParam = NativeInput.getStickParam(playerIndex, nativeAnalog)
|
|
||||||
newParam.set("modifier", param.serialize())
|
|
||||||
NativeInput.setStickParam(playerIndex, nativeAnalog, newParam)
|
|
||||||
}
|
|
||||||
}
|
|
@ -4,16 +4,13 @@
|
|||||||
package org.yuzu.yuzu_emu.features.settings.model.view
|
package org.yuzu.yuzu_emu.features.settings.model.view
|
||||||
|
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import androidx.annotation.StringRes
|
|
||||||
|
|
||||||
class RunnableSetting(
|
class RunnableSetting(
|
||||||
@StringRes titleId: Int = 0,
|
titleId: Int,
|
||||||
titleString: String = "",
|
descriptionId: Int,
|
||||||
@StringRes descriptionId: Int = 0,
|
val isRuntimeRunnable: Boolean,
|
||||||
descriptionString: String = "",
|
|
||||||
val isRunnable: Boolean,
|
|
||||||
@DrawableRes val iconId: Int = 0,
|
@DrawableRes val iconId: Int = 0,
|
||||||
val runnable: () -> Unit
|
val runnable: () -> Unit
|
||||||
) : SettingsItem(emptySetting, titleId, titleString, descriptionId, descriptionString) {
|
) : SettingsItem(emptySetting, titleId, descriptionId) {
|
||||||
override val type = TYPE_RUNNABLE
|
override val type = TYPE_RUNNABLE
|
||||||
}
|
}
|
||||||
|
@ -3,12 +3,8 @@
|
|||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.settings.model.view
|
package org.yuzu.yuzu_emu.features.settings.model.view
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import org.yuzu.yuzu_emu.NativeLibrary
|
import org.yuzu.yuzu_emu.NativeLibrary
|
||||||
import org.yuzu.yuzu_emu.R
|
import org.yuzu.yuzu_emu.R
|
||||||
import org.yuzu.yuzu_emu.YuzuApplication
|
|
||||||
import org.yuzu.yuzu_emu.features.input.NativeInput
|
|
||||||
import org.yuzu.yuzu_emu.features.input.model.NpadStyleIndex
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting
|
import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
|
import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
|
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
|
||||||
@ -16,7 +12,6 @@ import org.yuzu.yuzu_emu.features.settings.model.ByteSetting
|
|||||||
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.LongSetting
|
import org.yuzu.yuzu_emu.features.settings.model.LongSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
|
import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.StringSetting
|
|
||||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -28,34 +23,13 @@ import org.yuzu.yuzu_emu.utils.NativeConfig
|
|||||||
*/
|
*/
|
||||||
abstract class SettingsItem(
|
abstract class SettingsItem(
|
||||||
val setting: AbstractSetting,
|
val setting: AbstractSetting,
|
||||||
@StringRes val titleId: Int,
|
val nameId: Int,
|
||||||
val titleString: String,
|
val descriptionId: Int
|
||||||
@StringRes val descriptionId: Int,
|
|
||||||
val descriptionString: String
|
|
||||||
) {
|
) {
|
||||||
abstract val type: Int
|
abstract val type: Int
|
||||||
|
|
||||||
val title: String by lazy {
|
|
||||||
if (titleId != 0) {
|
|
||||||
return@lazy YuzuApplication.appContext.getString(titleId)
|
|
||||||
}
|
|
||||||
return@lazy titleString
|
|
||||||
}
|
|
||||||
|
|
||||||
val description: String by lazy {
|
|
||||||
if (descriptionId != 0) {
|
|
||||||
return@lazy YuzuApplication.appContext.getString(descriptionId)
|
|
||||||
}
|
|
||||||
return@lazy descriptionString
|
|
||||||
}
|
|
||||||
|
|
||||||
val isEditable: Boolean
|
val isEditable: Boolean
|
||||||
get() {
|
get() {
|
||||||
// Can't change docked mode toggle when using handheld mode
|
|
||||||
if (setting.key == BooleanSetting.USE_DOCKED_MODE.key) {
|
|
||||||
return NativeInput.getStyleIndex(0) != NpadStyleIndex.Handheld
|
|
||||||
}
|
|
||||||
|
|
||||||
// Can't edit settings that aren't saveable in per-game config even if they are switchable
|
// Can't edit settings that aren't saveable in per-game config even if they are switchable
|
||||||
if (NativeConfig.isPerGameConfigLoaded() && !setting.isSaveable) {
|
if (NativeConfig.isPerGameConfigLoaded() && !setting.isSaveable) {
|
||||||
return false
|
return false
|
||||||
@ -76,9 +50,6 @@ abstract class SettingsItem(
|
|||||||
get() = NativeLibrary.isRunning() && !setting.global &&
|
get() = NativeLibrary.isRunning() && !setting.global &&
|
||||||
!NativeConfig.isPerGameConfigLoaded()
|
!NativeConfig.isPerGameConfigLoaded()
|
||||||
|
|
||||||
val clearable: Boolean
|
|
||||||
get() = !setting.global && NativeConfig.isPerGameConfigLoaded()
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val TYPE_HEADER = 0
|
const val TYPE_HEADER = 0
|
||||||
const val TYPE_SWITCH = 1
|
const val TYPE_SWITCH = 1
|
||||||
@ -88,10 +59,6 @@ abstract class SettingsItem(
|
|||||||
const val TYPE_STRING_SINGLE_CHOICE = 5
|
const val TYPE_STRING_SINGLE_CHOICE = 5
|
||||||
const val TYPE_DATETIME_SETTING = 6
|
const val TYPE_DATETIME_SETTING = 6
|
||||||
const val TYPE_RUNNABLE = 7
|
const val TYPE_RUNNABLE = 7
|
||||||
const val TYPE_INPUT = 8
|
|
||||||
const val TYPE_INT_SINGLE_CHOICE = 9
|
|
||||||
const val TYPE_INPUT_PROFILE = 10
|
|
||||||
const val TYPE_STRING_INPUT = 11
|
|
||||||
|
|
||||||
const val FASTMEM_COMBINED = "fastmem_combined"
|
const val FASTMEM_COMBINED = "fastmem_combined"
|
||||||
|
|
||||||
@ -110,246 +77,212 @@ abstract class SettingsItem(
|
|||||||
|
|
||||||
// List of all general
|
// List of all general
|
||||||
val settingsItems = HashMap<String, SettingsItem>().apply {
|
val settingsItems = HashMap<String, SettingsItem>().apply {
|
||||||
put(StringInputSetting(StringSetting.DEVICE_NAME, titleId = R.string.device_name))
|
|
||||||
put(
|
put(
|
||||||
SwitchSetting(
|
SwitchSetting(
|
||||||
BooleanSetting.RENDERER_USE_SPEED_LIMIT,
|
BooleanSetting.RENDERER_USE_SPEED_LIMIT,
|
||||||
titleId = R.string.frame_limit_enable,
|
R.string.frame_limit_enable,
|
||||||
descriptionId = R.string.frame_limit_enable_description
|
R.string.frame_limit_enable_description
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
put(
|
put(
|
||||||
SliderSetting(
|
SliderSetting(
|
||||||
ShortSetting.RENDERER_SPEED_LIMIT,
|
ShortSetting.RENDERER_SPEED_LIMIT,
|
||||||
titleId = R.string.frame_limit_slider,
|
R.string.frame_limit_slider,
|
||||||
descriptionId = R.string.frame_limit_slider_description,
|
R.string.frame_limit_slider_description,
|
||||||
min = 1,
|
1,
|
||||||
max = 400,
|
400,
|
||||||
units = "%"
|
"%"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
put(
|
put(
|
||||||
SingleChoiceSetting(
|
SingleChoiceSetting(
|
||||||
IntSetting.CPU_BACKEND,
|
IntSetting.CPU_BACKEND,
|
||||||
titleId = R.string.cpu_backend,
|
R.string.cpu_backend,
|
||||||
choicesId = R.array.cpuBackendArm64Names,
|
0,
|
||||||
valuesId = R.array.cpuBackendArm64Values
|
R.array.cpuBackendArm64Names,
|
||||||
|
R.array.cpuBackendArm64Values
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
put(
|
put(
|
||||||
SingleChoiceSetting(
|
SingleChoiceSetting(
|
||||||
IntSetting.CPU_ACCURACY,
|
IntSetting.CPU_ACCURACY,
|
||||||
titleId = R.string.cpu_accuracy,
|
R.string.cpu_accuracy,
|
||||||
choicesId = R.array.cpuAccuracyNames,
|
0,
|
||||||
valuesId = R.array.cpuAccuracyValues
|
R.array.cpuAccuracyNames,
|
||||||
|
R.array.cpuAccuracyValues
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
put(
|
put(
|
||||||
SwitchSetting(
|
SwitchSetting(
|
||||||
BooleanSetting.PICTURE_IN_PICTURE,
|
BooleanSetting.PICTURE_IN_PICTURE,
|
||||||
titleId = R.string.picture_in_picture,
|
R.string.picture_in_picture,
|
||||||
descriptionId = R.string.picture_in_picture_description
|
R.string.picture_in_picture_description
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
val dockedModeSetting = object : AbstractBooleanSetting {
|
|
||||||
override val key = BooleanSetting.USE_DOCKED_MODE.key
|
|
||||||
|
|
||||||
override fun getBoolean(needsGlobal: Boolean): Boolean {
|
|
||||||
if (NativeInput.getStyleIndex(0) == NpadStyleIndex.Handheld) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return BooleanSetting.USE_DOCKED_MODE.getBoolean(needsGlobal)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setBoolean(value: Boolean) =
|
|
||||||
BooleanSetting.USE_DOCKED_MODE.setBoolean(value)
|
|
||||||
|
|
||||||
override val defaultValue = BooleanSetting.USE_DOCKED_MODE.defaultValue
|
|
||||||
|
|
||||||
override fun getValueAsString(needsGlobal: Boolean): String =
|
|
||||||
BooleanSetting.USE_DOCKED_MODE.getValueAsString(needsGlobal)
|
|
||||||
|
|
||||||
override fun reset() = BooleanSetting.USE_DOCKED_MODE.reset()
|
|
||||||
}
|
|
||||||
put(
|
put(
|
||||||
SwitchSetting(
|
SwitchSetting(
|
||||||
dockedModeSetting,
|
BooleanSetting.USE_DOCKED_MODE,
|
||||||
titleId = R.string.use_docked_mode,
|
R.string.use_docked_mode,
|
||||||
descriptionId = R.string.use_docked_mode_description
|
R.string.use_docked_mode_description
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
put(
|
put(
|
||||||
SingleChoiceSetting(
|
SingleChoiceSetting(
|
||||||
IntSetting.REGION_INDEX,
|
IntSetting.REGION_INDEX,
|
||||||
titleId = R.string.emulated_region,
|
R.string.emulated_region,
|
||||||
choicesId = R.array.regionNames,
|
0,
|
||||||
valuesId = R.array.regionValues
|
R.array.regionNames,
|
||||||
|
R.array.regionValues
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
put(
|
put(
|
||||||
SingleChoiceSetting(
|
SingleChoiceSetting(
|
||||||
IntSetting.LANGUAGE_INDEX,
|
IntSetting.LANGUAGE_INDEX,
|
||||||
titleId = R.string.emulated_language,
|
R.string.emulated_language,
|
||||||
choicesId = R.array.languageNames,
|
0,
|
||||||
valuesId = R.array.languageValues
|
R.array.languageNames,
|
||||||
|
R.array.languageValues
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
put(
|
put(
|
||||||
SwitchSetting(
|
SwitchSetting(
|
||||||
BooleanSetting.USE_CUSTOM_RTC,
|
BooleanSetting.USE_CUSTOM_RTC,
|
||||||
titleId = R.string.use_custom_rtc,
|
R.string.use_custom_rtc,
|
||||||
descriptionId = R.string.use_custom_rtc_description
|
R.string.use_custom_rtc_description
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
put(DateTimeSetting(LongSetting.CUSTOM_RTC, titleId = R.string.set_custom_rtc))
|
put(DateTimeSetting(LongSetting.CUSTOM_RTC, R.string.set_custom_rtc, 0))
|
||||||
put(
|
put(
|
||||||
SingleChoiceSetting(
|
SingleChoiceSetting(
|
||||||
IntSetting.RENDERER_ACCURACY,
|
IntSetting.RENDERER_ACCURACY,
|
||||||
titleId = R.string.renderer_accuracy,
|
R.string.renderer_accuracy,
|
||||||
choicesId = R.array.rendererAccuracyNames,
|
0,
|
||||||
valuesId = R.array.rendererAccuracyValues
|
R.array.rendererAccuracyNames,
|
||||||
|
R.array.rendererAccuracyValues
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
put(
|
put(
|
||||||
SingleChoiceSetting(
|
SingleChoiceSetting(
|
||||||
IntSetting.RENDERER_RESOLUTION,
|
IntSetting.RENDERER_RESOLUTION,
|
||||||
titleId = R.string.renderer_resolution,
|
R.string.renderer_resolution,
|
||||||
choicesId = R.array.rendererResolutionNames,
|
0,
|
||||||
valuesId = R.array.rendererResolutionValues
|
R.array.rendererResolutionNames,
|
||||||
|
R.array.rendererResolutionValues
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
put(
|
put(
|
||||||
SingleChoiceSetting(
|
SingleChoiceSetting(
|
||||||
IntSetting.RENDERER_VSYNC,
|
IntSetting.RENDERER_VSYNC,
|
||||||
titleId = R.string.renderer_vsync,
|
R.string.renderer_vsync,
|
||||||
choicesId = R.array.rendererVSyncNames,
|
0,
|
||||||
valuesId = R.array.rendererVSyncValues
|
R.array.rendererVSyncNames,
|
||||||
|
R.array.rendererVSyncValues
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
put(
|
put(
|
||||||
SingleChoiceSetting(
|
SingleChoiceSetting(
|
||||||
IntSetting.RENDERER_SCALING_FILTER,
|
IntSetting.RENDERER_SCALING_FILTER,
|
||||||
titleId = R.string.renderer_scaling_filter,
|
R.string.renderer_scaling_filter,
|
||||||
choicesId = R.array.rendererScalingFilterNames,
|
0,
|
||||||
valuesId = R.array.rendererScalingFilterValues
|
R.array.rendererScalingFilterNames,
|
||||||
)
|
R.array.rendererScalingFilterValues
|
||||||
)
|
|
||||||
put(
|
|
||||||
SliderSetting(
|
|
||||||
IntSetting.FSR_SHARPENING_SLIDER,
|
|
||||||
titleId = R.string.fsr_sharpness,
|
|
||||||
descriptionId = R.string.fsr_sharpness_description,
|
|
||||||
units = "%"
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
put(
|
put(
|
||||||
SingleChoiceSetting(
|
SingleChoiceSetting(
|
||||||
IntSetting.RENDERER_ANTI_ALIASING,
|
IntSetting.RENDERER_ANTI_ALIASING,
|
||||||
titleId = R.string.renderer_anti_aliasing,
|
R.string.renderer_anti_aliasing,
|
||||||
choicesId = R.array.rendererAntiAliasingNames,
|
0,
|
||||||
valuesId = R.array.rendererAntiAliasingValues
|
R.array.rendererAntiAliasingNames,
|
||||||
|
R.array.rendererAntiAliasingValues
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
put(
|
put(
|
||||||
SingleChoiceSetting(
|
SingleChoiceSetting(
|
||||||
IntSetting.RENDERER_SCREEN_LAYOUT,
|
IntSetting.RENDERER_SCREEN_LAYOUT,
|
||||||
titleId = R.string.renderer_screen_layout,
|
R.string.renderer_screen_layout,
|
||||||
choicesId = R.array.rendererScreenLayoutNames,
|
0,
|
||||||
valuesId = R.array.rendererScreenLayoutValues
|
R.array.rendererScreenLayoutNames,
|
||||||
|
R.array.rendererScreenLayoutValues
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
put(
|
put(
|
||||||
SingleChoiceSetting(
|
SingleChoiceSetting(
|
||||||
IntSetting.RENDERER_ASPECT_RATIO,
|
IntSetting.RENDERER_ASPECT_RATIO,
|
||||||
titleId = R.string.renderer_aspect_ratio,
|
R.string.renderer_aspect_ratio,
|
||||||
choicesId = R.array.rendererAspectRatioNames,
|
0,
|
||||||
valuesId = R.array.rendererAspectRatioValues
|
R.array.rendererAspectRatioNames,
|
||||||
)
|
R.array.rendererAspectRatioValues
|
||||||
)
|
|
||||||
put(
|
|
||||||
SingleChoiceSetting(
|
|
||||||
IntSetting.VERTICAL_ALIGNMENT,
|
|
||||||
titleId = R.string.vertical_alignment,
|
|
||||||
descriptionId = 0,
|
|
||||||
choicesId = R.array.verticalAlignmentEntries,
|
|
||||||
valuesId = R.array.verticalAlignmentValues
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
put(
|
put(
|
||||||
SwitchSetting(
|
SwitchSetting(
|
||||||
BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE,
|
BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE,
|
||||||
titleId = R.string.use_disk_shader_cache,
|
R.string.use_disk_shader_cache,
|
||||||
descriptionId = R.string.use_disk_shader_cache_description
|
R.string.use_disk_shader_cache_description
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
put(
|
put(
|
||||||
SwitchSetting(
|
SwitchSetting(
|
||||||
BooleanSetting.RENDERER_FORCE_MAX_CLOCK,
|
BooleanSetting.RENDERER_FORCE_MAX_CLOCK,
|
||||||
titleId = R.string.renderer_force_max_clock,
|
R.string.renderer_force_max_clock,
|
||||||
descriptionId = R.string.renderer_force_max_clock_description
|
R.string.renderer_force_max_clock_description
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
put(
|
put(
|
||||||
SwitchSetting(
|
SwitchSetting(
|
||||||
BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS,
|
BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS,
|
||||||
titleId = R.string.renderer_asynchronous_shaders,
|
R.string.renderer_asynchronous_shaders,
|
||||||
descriptionId = R.string.renderer_asynchronous_shaders_description
|
R.string.renderer_asynchronous_shaders_description
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
put(
|
put(
|
||||||
SwitchSetting(
|
SwitchSetting(
|
||||||
BooleanSetting.RENDERER_REACTIVE_FLUSHING,
|
BooleanSetting.RENDERER_REACTIVE_FLUSHING,
|
||||||
titleId = R.string.renderer_reactive_flushing,
|
R.string.renderer_reactive_flushing,
|
||||||
descriptionId = R.string.renderer_reactive_flushing_description
|
R.string.renderer_reactive_flushing_description
|
||||||
)
|
|
||||||
)
|
|
||||||
put(
|
|
||||||
SingleChoiceSetting(
|
|
||||||
IntSetting.MAX_ANISOTROPY,
|
|
||||||
titleId = R.string.anisotropic_filtering,
|
|
||||||
descriptionId = R.string.anisotropic_filtering_description,
|
|
||||||
choicesId = R.array.anisoEntries,
|
|
||||||
valuesId = R.array.anisoValues
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
put(
|
put(
|
||||||
SingleChoiceSetting(
|
SingleChoiceSetting(
|
||||||
IntSetting.AUDIO_OUTPUT_ENGINE,
|
IntSetting.AUDIO_OUTPUT_ENGINE,
|
||||||
titleId = R.string.audio_output_engine,
|
R.string.audio_output_engine,
|
||||||
choicesId = R.array.outputEngineEntries,
|
0,
|
||||||
valuesId = R.array.outputEngineValues
|
R.array.outputEngineEntries,
|
||||||
|
R.array.outputEngineValues
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
put(
|
put(
|
||||||
SliderSetting(
|
SliderSetting(
|
||||||
ByteSetting.AUDIO_VOLUME,
|
ByteSetting.AUDIO_VOLUME,
|
||||||
titleId = R.string.audio_volume,
|
R.string.audio_volume,
|
||||||
descriptionId = R.string.audio_volume_description,
|
R.string.audio_volume_description,
|
||||||
units = "%"
|
0,
|
||||||
|
100,
|
||||||
|
"%"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
put(
|
put(
|
||||||
SingleChoiceSetting(
|
SingleChoiceSetting(
|
||||||
IntSetting.RENDERER_BACKEND,
|
IntSetting.RENDERER_BACKEND,
|
||||||
titleId = R.string.renderer_api,
|
R.string.renderer_api,
|
||||||
choicesId = R.array.rendererApiNames,
|
0,
|
||||||
valuesId = R.array.rendererApiValues
|
R.array.rendererApiNames,
|
||||||
|
R.array.rendererApiValues
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
put(
|
put(
|
||||||
SwitchSetting(
|
SwitchSetting(
|
||||||
BooleanSetting.RENDERER_DEBUG,
|
BooleanSetting.RENDERER_DEBUG,
|
||||||
titleId = R.string.renderer_debug,
|
R.string.renderer_debug,
|
||||||
descriptionId = R.string.renderer_debug_description
|
R.string.renderer_debug_description
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
put(
|
put(
|
||||||
SwitchSetting(
|
SwitchSetting(
|
||||||
BooleanSetting.CPU_DEBUG_MODE,
|
BooleanSetting.CPU_DEBUG_MODE,
|
||||||
titleId = R.string.cpu_debug_mode,
|
R.string.cpu_debug_mode,
|
||||||
descriptionId = R.string.cpu_debug_mode_description
|
R.string.cpu_debug_mode_description
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -365,7 +298,6 @@ abstract class SettingsItem(
|
|||||||
|
|
||||||
override val key: String = FASTMEM_COMBINED
|
override val key: String = FASTMEM_COMBINED
|
||||||
override val isRuntimeModifiable: Boolean = false
|
override val isRuntimeModifiable: Boolean = false
|
||||||
override val pairedSettingKey = BooleanSetting.CPU_DEBUG_MODE.key
|
|
||||||
override val defaultValue: Boolean = true
|
override val defaultValue: Boolean = true
|
||||||
override val isSwitchable: Boolean = true
|
override val isSwitchable: Boolean = true
|
||||||
override var global: Boolean
|
override var global: Boolean
|
||||||
@ -385,7 +317,7 @@ abstract class SettingsItem(
|
|||||||
|
|
||||||
override fun reset() = setBoolean(defaultValue)
|
override fun reset() = setBoolean(defaultValue)
|
||||||
}
|
}
|
||||||
put(SwitchSetting(fastmem, R.string.fastmem))
|
put(SwitchSetting(fastmem, R.string.fastmem, 0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,20 +3,16 @@
|
|||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.settings.model.view
|
package org.yuzu.yuzu_emu.features.settings.model.view
|
||||||
|
|
||||||
import androidx.annotation.ArrayRes
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
|
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
|
import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
|
||||||
|
|
||||||
class SingleChoiceSetting(
|
class SingleChoiceSetting(
|
||||||
setting: AbstractSetting,
|
setting: AbstractSetting,
|
||||||
@StringRes titleId: Int = 0,
|
titleId: Int,
|
||||||
titleString: String = "",
|
descriptionId: Int,
|
||||||
@StringRes descriptionId: Int = 0,
|
val choicesId: Int,
|
||||||
descriptionString: String = "",
|
val valuesId: Int
|
||||||
@ArrayRes val choicesId: Int,
|
) : SettingsItem(setting, titleId, descriptionId) {
|
||||||
@ArrayRes val valuesId: Int
|
|
||||||
) : SettingsItem(setting, titleId, titleString, descriptionId, descriptionString) {
|
|
||||||
override val type = TYPE_SINGLE_CHOICE
|
override val type = TYPE_SINGLE_CHOICE
|
||||||
|
|
||||||
fun getSelectedValue(needsGlobal: Boolean = false) =
|
fun getSelectedValue(needsGlobal: Boolean = false) =
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user