mirror of
https://git.ryujinx.app/ryubing/ryujinx.git
synced 2025-06-28 01:49:39 -06:00
Compare commits
78 Commits
gitlab-rel
...
0e9acdfed0
Author | SHA1 | Date | |
---|---|---|---|
0e9acdfed0 | |||
154f056422 | |||
58c2dc4ac4 | |||
0cc94fdf37 | |||
d6232008d5 | |||
076dd9a56a | |||
952be47f3c | |||
b3c2fbcfa7 | |||
030d9f768f | |||
d4f7dfabf4 | |||
3ec079855d | |||
ef0dac5533 | |||
10e6fbcb72 | |||
de25673820 | |||
ba250df73d | |||
bfd715b607 | |||
d7929a7f0e | |||
1e86aa9764 | |||
175d5f9bb3 | |||
8765dc9901 | |||
74a9b94227 | |||
d3208a4c44 | |||
5d136980a3 | |||
572ad1eac5 | |||
6bb2af0091 | |||
534a194ed9 | |||
331805791e | |||
6773406bb6 | |||
6226eadf55 | |||
b1cde5fd97 | |||
39944b2063 | |||
973c6ba5df | |||
6803c91da8 | |||
557c2a50b2 | |||
77a797f154 | |||
faf9e3cdd7 | |||
7bc80ed4fe | |||
a1d44ec496 | |||
bab3beb0ac | |||
aa9e74339b | |||
908273d848 | |||
b51ad11574 | |||
ea027d65a7 | |||
d03ae9c164 | |||
90e9492f6c | |||
512120db04 | |||
90582e9e93 | |||
b97fae08b5 | |||
eed6ef632d | |||
0409c15903 | |||
c58272ac20 | |||
9d83dfd19c | |||
ce31a47934 | |||
d31d1f91cf | |||
ef02194a77 | |||
a16764d191 | |||
5108ab790f | |||
71dc71fee8 | |||
c95bf748b2 | |||
b5e9acc50b | |||
e3fba4e32f | |||
efa25d471e | |||
b37aa61e47 | |||
8feeb977b7 | |||
b761a2c86d | |||
693837dca7 | |||
70abff072b | |||
1e861b99a9 | |||
13e404bde0 | |||
04561a0cd3 | |||
0652d7e740 | |||
f2aea4fb22 | |||
3950e8adff | |||
0e84f2b1f0 | |||
051c794cc4 | |||
053a9cb549 | |||
d688fed7d2 | |||
8f5102aa2a |
171
.github/workflows/canary.yml
vendored
171
.github/workflows/canary.yml
vendored
@ -24,54 +24,6 @@ env:
|
|||||||
RELEASE: 1
|
RELEASE: 1
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
tag:
|
|
||||||
name: Create tag
|
|
||||||
runs-on: ubuntu-24.04
|
|
||||||
steps:
|
|
||||||
- name: Get version info
|
|
||||||
id: version_info
|
|
||||||
run: |
|
|
||||||
echo "build_version=${{ env.RYUJINX_BASE_VERSION }}.${{ github.run_number }}" >> $GITHUB_OUTPUT
|
|
||||||
echo "prev_build_version=${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} - 1))" >> $GITHUB_OUTPUT
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Install GitLabCli
|
|
||||||
run: |
|
|
||||||
mkdir -p $HOME/.bin
|
|
||||||
gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64'
|
|
||||||
chmod +x gli
|
|
||||||
mv gli $HOME/.bin/
|
|
||||||
echo "$HOME/.bin" >> $GITHUB_PATH
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Create GitLab tag
|
|
||||||
run: gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=CreateTag "Canary-${{ steps.version_info.outputs.build_version }}|master"
|
|
||||||
|
|
||||||
- name: Create release
|
|
||||||
uses: ncipollo/release-action@v1
|
|
||||||
with:
|
|
||||||
name: "Canary ${{ steps.version_info.outputs.build_version }}"
|
|
||||||
tag: ${{ steps.version_info.outputs.build_version }}
|
|
||||||
body: |
|
|
||||||
# Canary builds:
|
|
||||||
|
|
||||||
These builds are experimental and may sometimes not work, use [regular builds](https://github.com/${{ secrets.RC_OWNER }}/${{ secrets.RC_STABLE_NAME }}/releases/latest) instead if that sounds like something you don't want to deal with.
|
|
||||||
|
|
||||||
| Platform | Artifact |
|
|
||||||
|--|--|
|
|
||||||
| Windows 64-bit | [Canary Windows Artifact](https://github.com/${{ secrets.RC_OWNER }}/${{ secrets.RC_CANARY_NAME }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-win_x64.zip) |
|
|
||||||
| Windows ARM 64-bit | [Canary Windows ARM Artifact](https://github.com/${{ secrets.RC_OWNER }}/${{ secrets.RC_CANARY_NAME }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-win_arm64.zip) |
|
|
||||||
| Linux 64-bit | [Canary Linux Artifact](https://github.com/${{ secrets.RC_OWNER }}/${{ secrets.RC_CANARY_NAME }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz) |
|
|
||||||
| Linux ARM 64-bit | [Canary Linux ARM Artifact](https://github.com/${{ secrets.RC_OWNER }}/${{ secrets.RC_CANARY_NAME }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-linux_arm64.tar.gz) |
|
|
||||||
| macOS | [Canary macOS Artifact](https://github.com/${{ secrets.RC_OWNER }}/${{ secrets.RC_CANARY_NAME }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz) |
|
|
||||||
|
|
||||||
**[Full Changelog](https://git.ryujinx.app/ryubing/ryujinx/-/compare/Canary-${{ steps.version_info.outputs.prev_build_version }}...Canary-${{ steps.version_info.outputs.build_version }})**
|
|
||||||
omitBodyDuringUpdate: true
|
|
||||||
owner: ${{ secrets.RC_OWNER }}
|
|
||||||
repo: ${{ secrets.RC_CANARY_NAME }}
|
|
||||||
token: ${{ secrets.ALT_RELEASE_TOKEN }}
|
|
||||||
|
|
||||||
release:
|
release:
|
||||||
name: Release for ${{ matrix.platform.name }}
|
name: Release for ${{ matrix.platform.name }}
|
||||||
runs-on: ${{ matrix.platform.os }}
|
runs-on: ${{ matrix.platform.os }}
|
||||||
@ -79,7 +31,7 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
platform:
|
platform:
|
||||||
- { name: win-x64, os: windows-latest, zip_os_name: win_x64 }
|
- { name: win-x64, os: windows-latest, zip_os_name: win_x64 }
|
||||||
- { name: win-arm64, os: windows-latest, zip_os_name: win_arm64 }
|
#- { name: win-arm64, os: windows-latest, zip_os_name: win_arm64 }
|
||||||
- { name: linux-x64, os: ubuntu-latest, zip_os_name: linux_x64 }
|
- { name: linux-x64, os: ubuntu-latest, zip_os_name: linux_x64 }
|
||||||
- { name: linux-arm64, os: ubuntu-latest, zip_os_name: linux_arm64 }
|
- { name: linux-arm64, os: ubuntu-latest, zip_os_name: linux_arm64 }
|
||||||
steps:
|
steps:
|
||||||
@ -106,7 +58,7 @@ jobs:
|
|||||||
sed -r --in-place 's/\%\%RYUJINX_BUILD_GIT_HASH\%\%/${{ steps.version_info.outputs.git_short_hash }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
sed -r --in-place 's/\%\%RYUJINX_BUILD_GIT_HASH\%\%/${{ steps.version_info.outputs.git_short_hash }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_NAME\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_NAME }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_NAME\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_NAME }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||||
sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/Config\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/Config\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||||
sed -r --in-place '/^Name=Ryujinx$/s/Name=Ryujinx/Name=Ryujinx-Canary/' distribution/linux/Ryujinx.desktop
|
sed -r --in-place '/^Name=Ryujinx$/s/Name=Ryujinx/Name=Ryujinx-Canary/' distribution/linux/Ryujinx.desktop
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Create output dir
|
- name: Create output dir
|
||||||
@ -123,7 +75,24 @@ jobs:
|
|||||||
rm libarmeilleure-jitsupport.dylib
|
rm libarmeilleure-jitsupport.dylib
|
||||||
7z a ../release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip ../publish
|
7z a ../release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip ../publish
|
||||||
popd
|
popd
|
||||||
|
|
||||||
|
gh release download -R GreemDev/GLI -O gli.exe -p 'GitLabCli-win_x64.exe'
|
||||||
|
|
||||||
|
./gli.exe --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/canary --command=UploadGenericPackage "Ryubing-Canary|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip"
|
||||||
shell: bash
|
shell: bash
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Install GitLabCli
|
||||||
|
if: matrix.platform.os == 'ubuntu-latest'
|
||||||
|
run: |
|
||||||
|
mkdir -p $HOME/.bin
|
||||||
|
gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64'
|
||||||
|
chmod +x gli
|
||||||
|
mv gli $HOME/.bin/
|
||||||
|
echo "$HOME/.bin" >> $GITHUB_PATH
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Packing Linux builds
|
- name: Packing Linux builds
|
||||||
if: matrix.platform.os == 'ubuntu-latest'
|
if: matrix.platform.os == 'ubuntu-latest'
|
||||||
@ -133,6 +102,8 @@ jobs:
|
|||||||
chmod +x Ryujinx.sh Ryujinx
|
chmod +x Ryujinx.sh Ryujinx
|
||||||
tar -czvf ../release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz ../publish
|
tar -czvf ../release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz ../publish
|
||||||
popd
|
popd
|
||||||
|
|
||||||
|
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/canary --command=UploadGenericPackage "Ryubing-Canary|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz"
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Build AppImage (Linux)
|
- name: Build AppImage (Linux)
|
||||||
@ -169,35 +140,11 @@ jobs:
|
|||||||
pushd publish_appimage
|
pushd publish_appimage
|
||||||
mv Ryujinx.AppImage ../release_output/ryujinx-canary-$BUILD_VERSION-$ARCH_NAME.AppImage
|
mv Ryujinx.AppImage ../release_output/ryujinx-canary-$BUILD_VERSION-$ARCH_NAME.AppImage
|
||||||
mv Ryujinx.AppImage.zsync ../release_output/ryujinx-canary-$BUILD_VERSION-$ARCH_NAME.AppImage.zsync
|
mv Ryujinx.AppImage.zsync ../release_output/ryujinx-canary-$BUILD_VERSION-$ARCH_NAME.AppImage.zsync
|
||||||
popd
|
popd
|
||||||
shell: bash
|
|
||||||
|
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/canary --command=UploadGenericPackage "Ryubing-Canary|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-canary-$BUILD_VERSION-$ARCH_NAME.AppImage"
|
||||||
- name: Pushing new release
|
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/canary --command=UploadGenericPackage "Ryubing-Canary|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-canary-$BUILD_VERSION-$ARCH_NAME.AppImage.zsync"
|
||||||
uses: ncipollo/release-action@v1
|
shell: bash
|
||||||
with:
|
|
||||||
name: ${{ steps.version_info.outputs.build_version }}
|
|
||||||
artifacts: "release_output/*.tar.gz,release_output/*.zip,release_output/*AppImage*"
|
|
||||||
tag: ${{ steps.version_info.outputs.build_version }}
|
|
||||||
body: |
|
|
||||||
# Canary builds:
|
|
||||||
|
|
||||||
These builds are experimental and may sometimes not work, use [regular builds](https://github.com/${{ secrets.RC_OWNER }}/${{ secrets.RC_STABLE_NAME }}/releases/latest) instead if that sounds like something you don't want to deal with.
|
|
||||||
|
|
||||||
| Platform | Artifact |
|
|
||||||
|--|--|
|
|
||||||
| Windows 64-bit | [Canary Windows Artifact](https://github.com/${{ secrets.RC_OWNER }}/${{ secrets.RC_CANARY_NAME }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-win_x64.zip) |
|
|
||||||
| Windows ARM 64-bit | [Canary Windows ARM Artifact](https://github.com/${{ secrets.RC_OWNER }}/${{ secrets.RC_CANARY_NAME }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-win_arm64.zip) |
|
|
||||||
| Linux 64-bit | [Canary Linux Artifact](https://github.com/${{ secrets.RC_OWNER }}/${{ secrets.RC_CANARY_NAME }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz) |
|
|
||||||
| Linux ARM 64-bit | [Canary Linux ARM Artifact](https://github.com/${{ secrets.RC_OWNER }}/${{ secrets.RC_CANARY_NAME }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-linux_arm64.tar.gz) |
|
|
||||||
| macOS | [Canary macOS Artifact](https://github.com/${{ secrets.RC_OWNER }}/${{ secrets.RC_CANARY_NAME }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz) |
|
|
||||||
|
|
||||||
**[Full Changelog](https://git.ryujinx.app/ryubing/ryujinx/-/compare/Canary-${{ steps.version_info.outputs.prev_build_version }}...Canary-${{ steps.version_info.outputs.build_version }})**
|
|
||||||
omitBodyDuringUpdate: true
|
|
||||||
allowUpdates: true
|
|
||||||
replacesArtifacts: true
|
|
||||||
owner: ${{ secrets.RC_OWNER }}
|
|
||||||
repo: ${{ secrets.RC_CANARY_NAME }}
|
|
||||||
token: ${{ secrets.ALT_RELEASE_TOKEN }}
|
|
||||||
|
|
||||||
macos_release:
|
macos_release:
|
||||||
name: Release MacOS universal
|
name: Release MacOS universal
|
||||||
@ -214,6 +161,16 @@ jobs:
|
|||||||
wget https://apt.llvm.org/llvm.sh
|
wget https://apt.llvm.org/llvm.sh
|
||||||
chmod +x llvm.sh
|
chmod +x llvm.sh
|
||||||
sudo ./llvm.sh 17
|
sudo ./llvm.sh 17
|
||||||
|
|
||||||
|
- name: Install GitLabCli
|
||||||
|
run: |
|
||||||
|
mkdir -p $HOME/.bin
|
||||||
|
gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64'
|
||||||
|
chmod +x gli
|
||||||
|
mv gli $HOME/.bin/
|
||||||
|
echo "$HOME/.bin" >> $GITHUB_PATH
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Install rcodesign
|
- name: Install rcodesign
|
||||||
run: |
|
run: |
|
||||||
@ -246,17 +203,47 @@ jobs:
|
|||||||
- name: Publish macOS Ryujinx
|
- name: Publish macOS Ryujinx
|
||||||
run: |
|
run: |
|
||||||
./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish_ava ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release 1
|
./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish_ava ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release 1
|
||||||
|
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/canary --command=UploadGenericPackage "Ryubing-Canary|${{ steps.version_info.outputs.build_version }}|publish_ava/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz"
|
||||||
|
|
||||||
- name: Pushing new release
|
create_gitlab_release:
|
||||||
uses: ncipollo/release-action@v1
|
name: Create GitLab Release
|
||||||
with:
|
runs-on: ubuntu-24.04
|
||||||
name: "Canary ${{ steps.version_info.outputs.build_version }}"
|
needs:
|
||||||
artifacts: "publish_ava/*.tar.gz"
|
- macos_release
|
||||||
tag: ${{ steps.version_info.outputs.build_version }}
|
- release
|
||||||
body: ""
|
steps:
|
||||||
omitBodyDuringUpdate: true
|
- uses: actions/checkout@v4
|
||||||
allowUpdates: true
|
|
||||||
replacesArtifacts: true
|
- name: Get version info
|
||||||
owner: ${{ secrets.RC_OWNER }}
|
id: version_info
|
||||||
repo: ${{ secrets.RC_CANARY_NAME }}
|
run: |
|
||||||
token: ${{ secrets.ALT_RELEASE_TOKEN }}
|
echo "build_version=${{ env.RYUJINX_BASE_VERSION }}.${{ github.run_number }}" >> $GITHUB_OUTPUT
|
||||||
|
echo "prev_build_version=${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} - 1))" >> $GITHUB_OUTPUT
|
||||||
|
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Install GitLabCli
|
||||||
|
run: |
|
||||||
|
mkdir -p $HOME/.bin
|
||||||
|
gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64'
|
||||||
|
chmod +x gli
|
||||||
|
mv gli $HOME/.bin/
|
||||||
|
echo "$HOME/.bin" >> $GITHUB_PATH
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Create tag
|
||||||
|
run: |
|
||||||
|
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=CreateTag "Canary-${{ steps.version_info.outputs.build_version }}|${{ steps.version_info.outputs.git_short_hash }}"
|
||||||
|
|
||||||
|
- name: Create release
|
||||||
|
run: |
|
||||||
|
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/canary --command=CreateReleaseFromGenericPackageFiles "Ryubing-Canary|${{ steps.version_info.outputs.build_version }}|main|Canary ${{ steps.version_info.outputs.build_version }}|**Full Changelog:** [${{ steps.version_info.outputs.prev_build_version }}...${{ steps.version_info.outputs.build_version }}](https://git.ryujinx.app/ryubing/ryujinx/-/compare/Canary-${{ steps.version_info.outputs.prev_build_version }}...Canary-${{ steps.version_info.outputs.build_version }})"
|
||||||
|
|
||||||
|
- name: Send notification webhook
|
||||||
|
run: |
|
||||||
|
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/canary --command=SendUpdateMessage "${{ steps.version_info.outputs.build_version }}|FF4500|${{ secrets.CANARY_DISCORD_WEBHOOK }}|https://avatars.githubusercontent.com/u/192939710?s=200&v=4|false"
|
||||||
|
|
||||||
|
- name: Notify update server of new builds
|
||||||
|
run: |
|
||||||
|
curl 'https://update.ryujinx.app/api/v1/admin/refresh_cache?rc=canary' -X PATCH -H 'accept: */*' -H 'Authorization: ${{ secrets.UPDATE_SERVER_ADMIN_TOKEN }}'
|
||||||
|
33
.github/workflows/debug_release.yml
vendored
33
.github/workflows/debug_release.yml
vendored
@ -67,8 +67,21 @@ jobs:
|
|||||||
|
|
||||||
gh release download -R GreemDev/GLI -O gli.exe -p 'GitLabCli-win_x64.exe'
|
gh release download -R GreemDev/GLI -O gli.exe -p 'GitLabCli-win_x64.exe'
|
||||||
|
|
||||||
./gli.exe --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip"
|
./gli.exe --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip"
|
||||||
shell: bash
|
shell: bash
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Install GitLabCli
|
||||||
|
if: matrix.platform.os == 'ubuntu-latest'
|
||||||
|
run: |
|
||||||
|
mkdir -p $HOME/.bin
|
||||||
|
gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64'
|
||||||
|
chmod +x gli
|
||||||
|
mv gli $HOME/.bin/
|
||||||
|
echo "$HOME/.bin" >> $GITHUB_PATH
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Packing Linux builds
|
- name: Packing Linux builds
|
||||||
if: matrix.platform.os == 'ubuntu-latest'
|
if: matrix.platform.os == 'ubuntu-latest'
|
||||||
@ -79,13 +92,7 @@ jobs:
|
|||||||
tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz ../publish
|
tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz ../publish
|
||||||
popd
|
popd
|
||||||
|
|
||||||
mkdir -p $HOME/.bin
|
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz"
|
||||||
gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64'
|
|
||||||
chmod +x gli
|
|
||||||
mv gli $HOME/.bin/
|
|
||||||
echo "$HOME/.bin" >> $GITHUB_PATH
|
|
||||||
|
|
||||||
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz"
|
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Build AppImage (Linux)
|
- name: Build AppImage (Linux)
|
||||||
@ -124,8 +131,8 @@ jobs:
|
|||||||
mv Ryujinx.AppImage.zsync ../release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage.zsync
|
mv Ryujinx.AppImage.zsync ../release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage.zsync
|
||||||
popd
|
popd
|
||||||
|
|
||||||
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage"
|
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage"
|
||||||
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage.zsync"
|
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage.zsync"
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
macos_release:
|
macos_release:
|
||||||
@ -183,7 +190,7 @@ jobs:
|
|||||||
- name: Publish macOS Ryujinx
|
- name: Publish macOS Ryujinx
|
||||||
run: |
|
run: |
|
||||||
./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release 0
|
./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release 0
|
||||||
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "ryubing|${{ steps.version_info.outputs.build_version }}|publish_ava/ryujinx-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz"
|
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|publish/ryujinx-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz"
|
||||||
|
|
||||||
create_gitlab_release:
|
create_gitlab_release:
|
||||||
name: Create GitLab Release
|
name: Create GitLab Release
|
||||||
@ -192,6 +199,8 @@ jobs:
|
|||||||
- macos_release
|
- macos_release
|
||||||
- release
|
- release
|
||||||
steps:
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Get version info
|
- name: Get version info
|
||||||
id: version_info
|
id: version_info
|
||||||
run: |
|
run: |
|
||||||
@ -212,4 +221,4 @@ jobs:
|
|||||||
|
|
||||||
- name: Create release
|
- name: Create release
|
||||||
run: |
|
run: |
|
||||||
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=CreateReleaseFromGenericPackageFiles "ryubing|${{ steps.version_info.outputs.build_version }}|${{ steps.version_info.outputs.git_short_hash }}|test|THIS IS NOT INTENDED FOR END USER USAGE"
|
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=CreateReleaseFromGenericPackageFiles "Ryubing|${{ steps.version_info.outputs.build_version }}|${{ steps.version_info.outputs.git_short_hash }}|test|THIS IS NOT INTENDED FOR END USER USAGE"
|
||||||
|
157
.github/workflows/release.yml
vendored
157
.github/workflows/release.yml
vendored
@ -14,51 +14,6 @@ env:
|
|||||||
RELEASE: 1
|
RELEASE: 1
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
tag:
|
|
||||||
name: Create tag
|
|
||||||
runs-on: ubuntu-24.04
|
|
||||||
steps:
|
|
||||||
- name: Get version info
|
|
||||||
id: version_info
|
|
||||||
run: |
|
|
||||||
echo "build_version=${{ env.RYUJINX_BASE_VERSION }}.${{ github.run_number }}" >> $GITHUB_OUTPUT
|
|
||||||
echo "prev_build_version=${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} - 1))" >> $GITHUB_OUTPUT
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Install GitLabCli
|
|
||||||
run: |
|
|
||||||
mkdir -p $HOME/.bin
|
|
||||||
gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64'
|
|
||||||
chmod +x gli
|
|
||||||
mv gli $HOME/.bin/
|
|
||||||
echo "$HOME/.bin" >> $GITHUB_PATH
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Create GitLab tag
|
|
||||||
run: gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=CreateTag "${{ steps.version_info.outputs.build_version }}|master"
|
|
||||||
|
|
||||||
- name: Create release
|
|
||||||
uses: ncipollo/release-action@v1
|
|
||||||
with:
|
|
||||||
name: ${{ steps.version_info.outputs.build_version }}
|
|
||||||
tag: ${{ steps.version_info.outputs.build_version }}
|
|
||||||
body: |
|
|
||||||
# Stable builds:
|
|
||||||
| Platform | Artifact |
|
|
||||||
|--|--|
|
|
||||||
| Windows 64-bit | [Stable Windows Artifact](https://github.com/${{ secrets.RC_OWNER }}/${{ secrets.RC_STABLE_NAME }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-${{ steps.version_info.outputs.build_version }}-win_x64.zip) |
|
|
||||||
| Windows ARM 64-bit | [Stable Windows ARM Artifact](https://github.com/${{ secrets.RC_OWNER }}/${{ secrets.RC_STABLE_NAME }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-${{ steps.version_info.outputs.build_version }}-win_arm64.zip) |
|
|
||||||
| Linux 64-bit | [Stable Linux Artifact](https://github.com/${{ secrets.RC_OWNER }}/${{ secrets.RC_STABLE_NAME }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz) |
|
|
||||||
| Linux ARM 64-bit | [Stable Linux ARM Artifact](https://github.com/${{ secrets.RC_OWNER }}/${{ secrets.RC_STABLE_NAME }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_arm64.tar.gz) |
|
|
||||||
| macOS | [Stable macOS Artifact](https://github.com/${{ secrets.RC_OWNER }}/${{ secrets.RC_STABLE_NAME }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz) |
|
|
||||||
|
|
||||||
**[Full Changelog](https://git.ryujinx.app/ryubing/ryujinx/-/compare/${{ steps.version_info.outputs.prev_build_version }}...${{ steps.version_info.outputs.build_version }})**
|
|
||||||
omitBodyDuringUpdate: true
|
|
||||||
owner: ${{ secrets.RC_OWNER }}
|
|
||||||
repo: ${{ secrets.RC_STABLE_NAME }}
|
|
||||||
token: ${{ secrets.ALT_RELEASE_TOKEN }}
|
|
||||||
|
|
||||||
release:
|
release:
|
||||||
name: Release for ${{ matrix.platform.name }}
|
name: Release for ${{ matrix.platform.name }}
|
||||||
runs-on: ${{ matrix.platform.os }}
|
runs-on: ${{ matrix.platform.os }}
|
||||||
@ -66,7 +21,7 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
platform:
|
platform:
|
||||||
- { name: win-x64, os: windows-latest, zip_os_name: win_x64 }
|
- { name: win-x64, os: windows-latest, zip_os_name: win_x64 }
|
||||||
- { name: win-arm64, os: windows-latest, zip_os_name: win_arm64 }
|
#- { name: win-arm64, os: windows-latest, zip_os_name: win_arm64 }
|
||||||
- { name: linux-x64, os: ubuntu-latest, zip_os_name: linux_x64 }
|
- { name: linux-x64, os: ubuntu-latest, zip_os_name: linux_x64 }
|
||||||
- { name: linux-arm64, os: ubuntu-latest, zip_os_name: linux_arm64 }
|
- { name: linux-arm64, os: ubuntu-latest, zip_os_name: linux_arm64 }
|
||||||
steps:
|
steps:
|
||||||
@ -109,7 +64,24 @@ jobs:
|
|||||||
rm libarmeilleure-jitsupport.dylib
|
rm libarmeilleure-jitsupport.dylib
|
||||||
7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip ../publish
|
7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip ../publish
|
||||||
popd
|
popd
|
||||||
|
|
||||||
|
gh release download -R GreemDev/GLI -O gli.exe -p 'GitLabCli-win_x64.exe'
|
||||||
|
|
||||||
|
./gli.exe --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip"
|
||||||
shell: bash
|
shell: bash
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Install GitLabCli
|
||||||
|
if: matrix.platform.os == 'ubuntu-latest'
|
||||||
|
run: |
|
||||||
|
mkdir -p $HOME/.bin
|
||||||
|
gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64'
|
||||||
|
chmod +x gli
|
||||||
|
mv gli $HOME/.bin/
|
||||||
|
echo "$HOME/.bin" >> $GITHUB_PATH
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Packing Linux builds
|
- name: Packing Linux builds
|
||||||
if: matrix.platform.os == 'ubuntu-latest'
|
if: matrix.platform.os == 'ubuntu-latest'
|
||||||
@ -119,7 +91,11 @@ jobs:
|
|||||||
chmod +x Ryujinx.sh Ryujinx
|
chmod +x Ryujinx.sh Ryujinx
|
||||||
tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz ../publish
|
tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz ../publish
|
||||||
popd
|
popd
|
||||||
|
|
||||||
|
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz"
|
||||||
shell: bash
|
shell: bash
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Build AppImage (Linux)
|
- name: Build AppImage (Linux)
|
||||||
if: matrix.platform.os == 'ubuntu-latest'
|
if: matrix.platform.os == 'ubuntu-latest'
|
||||||
@ -156,32 +132,11 @@ jobs:
|
|||||||
mv Ryujinx.AppImage ../release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage
|
mv Ryujinx.AppImage ../release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage
|
||||||
mv Ryujinx.AppImage.zsync ../release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage.zsync
|
mv Ryujinx.AppImage.zsync ../release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage.zsync
|
||||||
popd
|
popd
|
||||||
|
|
||||||
|
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage"
|
||||||
|
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage.zsync"
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Pushing new release
|
|
||||||
uses: ncipollo/release-action@v1
|
|
||||||
with:
|
|
||||||
name: ${{ steps.version_info.outputs.build_version }}
|
|
||||||
artifacts: "release_output/*.tar.gz,release_output/*.zip,release_output/*AppImage*"
|
|
||||||
tag: ${{ steps.version_info.outputs.build_version }}
|
|
||||||
body: |
|
|
||||||
# Stable builds:
|
|
||||||
| Platform | Artifact |
|
|
||||||
|--|--|
|
|
||||||
| Windows 64-bit | [Stable Windows Artifact](https://github.com/${{ secrets.RC_OWNER }}/${{ secrets.RC_STABLE_NAME }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-${{ steps.version_info.outputs.build_version }}-win_x64.zip) |
|
|
||||||
| Windows ARM 64-bit | [Stable Windows ARM Artifact](https://github.com/${{ secrets.RC_OWNER }}/${{ secrets.RC_STABLE_NAME }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-${{ steps.version_info.outputs.build_version }}-win_arm64.zip) |
|
|
||||||
| Linux 64-bit | [Stable Linux Artifact](https://github.com/${{ secrets.RC_OWNER }}/${{ secrets.RC_STABLE_NAME }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz) |
|
|
||||||
| Linux ARM 64-bit | [Stable Linux ARM Artifact](https://github.com/${{ secrets.RC_OWNER }}/${{ secrets.RC_STABLE_NAME }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_arm64.tar.gz) |
|
|
||||||
| macOS | [Stable macOS Artifact](https://github.com/${{ secrets.RC_OWNER }}/${{ secrets.RC_STABLE_NAME }}/releases/download/${{ steps.version_info.outputs.build_version }}/ryujinx-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz) |
|
|
||||||
|
|
||||||
**[Full Changelog](https://git.ryujinx.app/ryubing/ryujinx/-/compare/${{ steps.version_info.outputs.prev_build_version }}...${{ steps.version_info.outputs.build_version }})**
|
|
||||||
omitBodyDuringUpdate: true
|
|
||||||
allowUpdates: true
|
|
||||||
replacesArtifacts: true
|
|
||||||
owner: ${{ secrets.RC_OWNER }}
|
|
||||||
repo: ${{ secrets.RC_STABLE_NAME }}
|
|
||||||
token: ${{ secrets.ALT_RELEASE_TOKEN }}
|
|
||||||
|
|
||||||
macos_release:
|
macos_release:
|
||||||
name: Release MacOS universal
|
name: Release MacOS universal
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
@ -197,6 +152,16 @@ jobs:
|
|||||||
wget https://apt.llvm.org/llvm.sh
|
wget https://apt.llvm.org/llvm.sh
|
||||||
chmod +x llvm.sh
|
chmod +x llvm.sh
|
||||||
sudo ./llvm.sh 17
|
sudo ./llvm.sh 17
|
||||||
|
|
||||||
|
- name: Install GitLabCli
|
||||||
|
run: |
|
||||||
|
mkdir -p $HOME/.bin
|
||||||
|
gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64'
|
||||||
|
chmod +x gli
|
||||||
|
mv gli $HOME/.bin/
|
||||||
|
echo "$HOME/.bin" >> $GITHUB_PATH
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Install rcodesign
|
- name: Install rcodesign
|
||||||
run: |
|
run: |
|
||||||
@ -227,17 +192,43 @@ jobs:
|
|||||||
- name: Publish macOS Ryujinx
|
- name: Publish macOS Ryujinx
|
||||||
run: |
|
run: |
|
||||||
./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release 0
|
./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release 0
|
||||||
|
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|publish/ryujinx-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz"
|
||||||
|
|
||||||
|
create_gitlab_release:
|
||||||
|
name: Create GitLab Release
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
needs:
|
||||||
|
- macos_release
|
||||||
|
- release
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Get version info
|
||||||
|
id: version_info
|
||||||
|
run: |
|
||||||
|
echo "build_version=${{ env.RYUJINX_BASE_VERSION }}.${{ github.run_number }}" >> $GITHUB_OUTPUT
|
||||||
|
echo "prev_build_version=${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} - 1))" >> $GITHUB_OUTPUT
|
||||||
|
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
||||||
|
shell: bash
|
||||||
|
|
||||||
- name: Pushing new release
|
- name: Install GitLabCli
|
||||||
uses: ncipollo/release-action@v1
|
run: |
|
||||||
with:
|
mkdir -p $HOME/.bin
|
||||||
name: ${{ steps.version_info.outputs.build_version }}
|
gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64'
|
||||||
artifacts: "publish/*.tar.gz"
|
chmod +x gli
|
||||||
tag: ${{ steps.version_info.outputs.build_version }}
|
mv gli $HOME/.bin/
|
||||||
body: ""
|
echo "$HOME/.bin" >> $GITHUB_PATH
|
||||||
omitBodyDuringUpdate: true
|
env:
|
||||||
allowUpdates: true
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
replacesArtifacts: true
|
|
||||||
owner: ${{ secrets.RC_OWNER }}
|
- name: Create release
|
||||||
repo: ${{ secrets.RC_STABLE_NAME }}
|
run: |
|
||||||
token: ${{ secrets.ALT_RELEASE_TOKEN }}
|
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=CreateReleaseFromGenericPackageFiles "Ryubing|${{ steps.version_info.outputs.build_version }}|${{ steps.version_info.outputs.git_short_hash }}|${{ steps.version_info.outputs.build_version }}|msd:${{ steps.version_info.outputs.build_version }}"
|
||||||
|
|
||||||
|
- name: Send notification webhook
|
||||||
|
run: |
|
||||||
|
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=SendUpdateMessage "${{ steps.version_info.outputs.build_version }}|32cd32|${{ secrets.STABLE_DISCORD_WEBHOOK }}|https://avatars.githubusercontent.com/u/192939710?s=200&v=4|false"
|
||||||
|
|
||||||
|
- name: Notify update server of new builds
|
||||||
|
run: |
|
||||||
|
curl 'https://update.ryujinx.app/api/v1/admin/refresh_cache?rc=stable' -X PATCH -H 'accept: */*' -H 'Authorization: ${{ secrets.UPDATE_SERVER_ADMIN_TOKEN }}'
|
||||||
|
@ -2,20 +2,17 @@
|
|||||||
|
|
||||||
All updates to this Ryujinx branch will be documented in this file.
|
All updates to this Ryujinx branch will be documented in this file.
|
||||||
|
|
||||||
|
## [1.3.2](<https://git.ryujinx.app/ryubing/ryujinx/-/releases/1.3.2>) - 2025-06-09
|
||||||
|
|
||||||
## [1.3.1](<https://git.ryujinx.app/ryubing/ryujinx/-/releases/1.3.1>) - 2025-04-23
|
## [1.3.1](<https://git.ryujinx.app/ryubing/ryujinx/-/releases/1.3.1>) - 2025-04-23
|
||||||
A list of notable changes can be found on the release linked in the version number above.
|
|
||||||
|
|
||||||
## [1.2.86](<https://github.com/Ryubing/Stable-Releases/releases/tag/1.2.86>) - 2025-03-13
|
## [1.2.86](<https://github.com/Ryubing/Stable-Releases/releases/tag/1.2.86>) - 2025-03-13
|
||||||
A list of notable changes can be found on the release linked in the version number above.
|
|
||||||
|
|
||||||
## [1.2.82](<https://web.archive.org/web/20250312010534/https://github.com/Ryubing/Ryujinx/releases/tag/1.2.82>) - 2025-02-16
|
## [1.2.82](<https://web.archive.org/web/20250312010534/https://github.com/Ryubing/Ryujinx/releases/tag/1.2.82>) - 2025-02-16
|
||||||
A list of notable changes can be found on the release linked in the version number above.
|
|
||||||
|
|
||||||
## [1.2.80-81](<https://web.archive.org/web/20250302064257/https://github.com/Ryubing/Ryujinx/releases/tag/1.2.81>) - 2025-01-22
|
## [1.2.80-81](<https://web.archive.org/web/20250302064257/https://github.com/Ryubing/Ryujinx/releases/tag/1.2.81>) - 2025-01-22
|
||||||
A list of notable changes can be found on the release linked in the version number above.
|
|
||||||
|
|
||||||
## [1.2.78](<https://web.archive.org/web/20250301174537/https://github.com/Ryubing/Ryujinx/releases/tag/1.2.78>) - 2024-12-19
|
## [1.2.78](<https://web.archive.org/web/20250301174537/https://github.com/Ryubing/Ryujinx/releases/tag/1.2.78>) - 2024-12-19
|
||||||
A list of notable changes can be found on the release linked in the version number above.
|
|
||||||
|
|
||||||
## [1.2.73-1.2.76](<https://web.archive.org/web/20250209202612/https://github.com/Ryubing/Ryujinx/releases/tag/1.2.76>) - 2024-11-19
|
## [1.2.73-1.2.76](<https://web.archive.org/web/20250209202612/https://github.com/Ryubing/Ryujinx/releases/tag/1.2.76>) - 2024-11-19
|
||||||
A list of notable changes can be found on the release linked in the version number above.
|
A list of notable changes can be found on the release linked in the version number above.
|
||||||
@ -254,4 +251,4 @@ Added Low-power PPTC mode strings to the translation files.
|
|||||||
- Autoload DLC/Updates from dir ([#12](https://github.com/GreemDev/Ryujinx/pull/12)).
|
- Autoload DLC/Updates from dir ([#12](https://github.com/GreemDev/Ryujinx/pull/12)).
|
||||||
- Changed executable icon to rainbow logo.
|
- Changed executable icon to rainbow logo.
|
||||||
- Extract Data > Logo now also extracts the square thumbnail you see for the game in the UI.
|
- Extract Data > Logo now also extracts the square thumbnail you see for the game in the UI.
|
||||||
- The "use random UUID hack" checkbox in the Amiibo screen now remembers its last state when you reopen the window in a given session.
|
- The "use random UUID hack" checkbox in the Amiibo screen now remembers its last state when you reopen the window in a given session.
|
||||||
|
@ -40,8 +40,10 @@
|
|||||||
<PackageVersion Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" />
|
<PackageVersion Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" />
|
||||||
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies.AllArch" Version="6.1.2-build3" />
|
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies.AllArch" Version="6.1.2-build3" />
|
||||||
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
|
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
|
||||||
<PackageVersion Include="Ryujinx.LibHac" Version="0.20.0-alpha.107" />
|
<PackageVersion Include="Ryujinx.LibHac" Version="0.20.0" />
|
||||||
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.30.0-build32" />
|
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.30.0-build32" />
|
||||||
|
<PackageVersion Include="Ryujinx.UpdateClient" Version="1.0.29" />
|
||||||
|
<PackageVersion Include="Ryujinx.Systems.Update.Common" Version="1.0.29" />
|
||||||
<PackageVersion Include="Gommon" Version="2.7.1.1" />
|
<PackageVersion Include="Gommon" Version="2.7.1.1" />
|
||||||
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
|
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
|
||||||
<PackageVersion Include="Sep" Version="0.6.0" />
|
<PackageVersion Include="Sep" Version="0.6.0" />
|
||||||
|
14
README.md
14
README.md
@ -7,8 +7,8 @@
|
|||||||
|
|
||||||
# Ryujinx
|
# Ryujinx
|
||||||
|
|
||||||
[](https://github.com/Ryubing/Stable-Releases/releases/latest)
|
[](https://git.ryujinx.app/ryubing/ryujinx/-/releases)
|
||||||
[](https://github.com/Ryubing/Canary-Releases/releases/latest)
|
[](https://git.ryujinx.app/ryubing/canary/-/releases)
|
||||||
<br>
|
<br>
|
||||||
<a href="https://discord.gg/PEuzjrFXUA">
|
<a href="https://discord.gg/PEuzjrFXUA">
|
||||||
<img src="https://img.shields.io/discord/1294443224030511104?color=5865F2&label=Ryubing&logo=discord&logoColor=white" alt="Discord">
|
<img src="https://img.shields.io/discord/1294443224030511104?color=5865F2&label=Ryubing&logo=discord&logoColor=white" alt="Discord">
|
||||||
@ -31,7 +31,7 @@
|
|||||||
<br>
|
<br>
|
||||||
This is not a Ryujinx revival project. This is not a Phoenix project.
|
This is not a Ryujinx revival project. This is not a Phoenix project.
|
||||||
<br>
|
<br>
|
||||||
Guides and documentation can be found on the <a href="https://git.ryujinx.app/ryubing/ryujinx/-/wikis/home">Wiki tab</a>.
|
Guides and documentation can be found on the <a href="https://git.ryujinx.app/groups/ryubing/-/wikis/home">Wiki tab</a>.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
@ -49,13 +49,13 @@ Stable builds are made every so often, based on the `master` branch, that then g
|
|||||||
These stable builds exist so that the end user can get a more **enjoyable and stable experience**.
|
These stable builds exist so that the end user can get a more **enjoyable and stable experience**.
|
||||||
They are released every month or so, to ensure consistent updates, while not being an annoying amount of individual updates to download over the course of that month.
|
They are released every month or so, to ensure consistent updates, while not being an annoying amount of individual updates to download over the course of that month.
|
||||||
|
|
||||||
You can find the latest stable release [here](https://github.com/Ryubing/Stable-Releases/releases/latest).
|
You can find the stable releases [here](https://git.ryujinx.app/ryubing/ryujinx/-/releases).
|
||||||
|
|
||||||
Canary builds are compiled automatically for each commit on the `master` branch.
|
Canary builds are compiled automatically for each commit on the `master` branch.
|
||||||
While we strive to ensure optimal stability and performance prior to pushing an update, these builds **may be unstable or completely broken**.
|
While we strive to ensure optimal stability and performance prior to pushing an update, these builds **may be unstable or completely broken**.
|
||||||
These canary builds are only recommended for experienced users.
|
These canary builds are only recommended for experienced users.
|
||||||
|
|
||||||
You can find the latest canary release [here](https://github.com/Ryubing/Canary-Releases/releases/latest).
|
You can find the canary releases [here](https://git.ryujinx.app/ryubing/canary/-/releases).
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
@ -111,7 +111,7 @@ See [LICENSE.txt](LICENSE.txt) and [THIRDPARTY.md](distribution/legal/THIRDPARTY
|
|||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
- [LibHac](https://github.com/Thealexbarney/LibHac) is used for our file-system.
|
- [LibHac](https://git.ryujinx.app/ryubing/libhac) is used for our file-system.
|
||||||
- [AmiiboAPI](https://www.amiiboapi.com) is used in our Amiibo emulation.
|
- [AmiiboAPI](https://www.amiiboapi.com) is used in our Amiibo emulation.
|
||||||
- [ldn_mitm](https://github.com/spacemeowx2/ldn_mitm) is used for one of our available multiplayer modes.
|
- [ldn_mitm](https://github.com/spacemeowx2/ldn_mitm) is used for one of our available multiplayer modes.
|
||||||
- [ShellLink](https://github.com/securifybv/ShellLink) is used for Windows shortcut generation.
|
- [ShellLink](https://github.com/securifybv/ShellLink) is used for Windows shortcut generation.
|
@ -77,6 +77,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon.Kernel.Gene
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.HLE.Generators", "src\Ryujinx.HLE.Generators\Ryujinx.HLE.Generators.csproj", "{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.HLE.Generators", "src\Ryujinx.HLE.Generators\Ryujinx.HLE.Generators.csproj", "{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.BuildValidationTasks", "src\Ryujinx.BuildValidationTasks\Ryujinx.BuildValidationTasks.csproj", "{4A89A234-4F19-497D-A576-DDE8CDFC5B22}"
|
||||||
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{36F870C1-3E5F-485F-B426-F0645AF78751}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{36F870C1-3E5F-485F-B426-F0645AF78751}"
|
||||||
ProjectSection(SolutionItems) = preProject
|
ProjectSection(SolutionItems) = preProject
|
||||||
.editorconfig = .editorconfig
|
.editorconfig = .editorconfig
|
||||||
@ -84,10 +86,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
|||||||
.github\workflows\canary.yml = .github\workflows\canary.yml
|
.github\workflows\canary.yml = .github\workflows\canary.yml
|
||||||
Directory.Packages.props = Directory.Packages.props
|
Directory.Packages.props = Directory.Packages.props
|
||||||
.github\workflows\release.yml = .github\workflows\release.yml
|
.github\workflows\release.yml = .github\workflows\release.yml
|
||||||
|
nuget.config = nuget.config
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.BuildValidationTasks", "src\Ryujinx.BuildValidationTasks\Ryujinx.BuildValidationTasks.csproj", "{4A89A234-4F19-497D-A576-DDE8CDFC5B22}"
|
|
||||||
EndProject
|
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
1311
assets/locales.json
1311
assets/locales.json
File diff suppressed because it is too large
Load Diff
@ -33,23 +33,29 @@ echo -n "APPL????" > "$APP_BUNDLE_DIRECTORY/Contents/PkgInfo"
|
|||||||
echo "Running bundle fix up python script"
|
echo "Running bundle fix up python script"
|
||||||
python3 bundle_fix_up.py "$APP_BUNDLE_DIRECTORY" MacOS/Ryujinx
|
python3 bundle_fix_up.py "$APP_BUNDLE_DIRECTORY" MacOS/Ryujinx
|
||||||
|
|
||||||
# Resign all dyplib files as ad-hoc after changing them
|
|
||||||
find "$APP_BUNDLE_DIRECTORY/Contents/Frameworks" -type f -name "*.dylib" -exec codesign --force --sign - {} \;
|
|
||||||
|
|
||||||
# Now sign it
|
# Now sign it
|
||||||
echo "Starting signing process"
|
echo "Starting signing process"
|
||||||
if ! [ -x "$(command -v codesign)" ];
|
if ! [ -x "$(command -v codesign)" ];
|
||||||
then
|
then
|
||||||
if ! [ -x "$(command -v rcodesign)" ];
|
if ! [ -x "$(command -v rcodesign)" ];
|
||||||
then
|
then
|
||||||
echo "Cannot find rcodesign on your system, please install rcodesign."
|
echo "Cannot find rcodesign on your system, please install rcodesign and ensure it is in your search path."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# cargo install apple-codesign
|
|
||||||
echo "Using rcodesign for ad-hoc signing"
|
echo "Using rcodesign for ad-hoc signing"
|
||||||
|
|
||||||
|
echo "Resigning all frameworks dylib files as ad-hoc"
|
||||||
|
find "$APP_BUNDLE_DIRECTORY/Contents/Frameworks" -type f -name "*.dylib" -exec rcodesign sign {} \;
|
||||||
|
|
||||||
|
echo "Signing app bundle as ad-hoc"
|
||||||
rcodesign sign --entitlements-xml-path "$ENTITLEMENTS_FILE_PATH" "$APP_BUNDLE_DIRECTORY"
|
rcodesign sign --entitlements-xml-path "$ENTITLEMENTS_FILE_PATH" "$APP_BUNDLE_DIRECTORY"
|
||||||
else
|
else
|
||||||
echo "Using codesign for ad-hoc signing"
|
echo "Using codesign for ad-hoc signing"
|
||||||
|
|
||||||
|
echo "Resigning all frameworks dylib files as ad-hoc"
|
||||||
|
find "$APP_BUNDLE_DIRECTORY/Contents/Frameworks" -type f -name "*.dylib" -exec codesign --force --sign - {} \;
|
||||||
|
|
||||||
|
echo "Signing app bundle as ad-hoc"
|
||||||
codesign --entitlements "$ENTITLEMENTS_FILE_PATH" -f -s - "$APP_BUNDLE_DIRECTORY"
|
codesign --entitlements "$ENTITLEMENTS_FILE_PATH" -f -s - "$APP_BUNDLE_DIRECTORY"
|
||||||
fi
|
fi
|
||||||
|
@ -20,6 +20,18 @@ SOURCE_REVISION_ID=$6
|
|||||||
CONFIGURATION=$7
|
CONFIGURATION=$7
|
||||||
CANARY=$8
|
CANARY=$8
|
||||||
|
|
||||||
|
if [[ "$(uname)" == "Darwin" ]]; then
|
||||||
|
echo "Clearing xattr on all dot undercsore files"
|
||||||
|
find "$BASE_DIR" -type f -name "._*" -exec sh -c '
|
||||||
|
for f; do
|
||||||
|
dir=$(dirname "$f")
|
||||||
|
base=$(basename "$f")
|
||||||
|
orig="$dir/${base#._}"
|
||||||
|
[ -f "$orig" ] && xattr -c "$orig" || true
|
||||||
|
done
|
||||||
|
' sh {} +
|
||||||
|
fi
|
||||||
|
|
||||||
if [ "$CANARY" == "1" ]; then
|
if [ "$CANARY" == "1" ]; then
|
||||||
RELEASE_TAR_FILE_NAME=ryujinx-canary-$VERSION-macos_universal.app.tar
|
RELEASE_TAR_FILE_NAME=ryujinx-canary-$VERSION-macos_universal.app.tar
|
||||||
elif [ "$VERSION" == "1.1.0" ]; then
|
elif [ "$VERSION" == "1.1.0" ]; then
|
||||||
|
@ -20,6 +20,18 @@ SOURCE_REVISION_ID=$6
|
|||||||
CONFIGURATION=$7
|
CONFIGURATION=$7
|
||||||
CANARY=$8
|
CANARY=$8
|
||||||
|
|
||||||
|
if [[ "$(uname)" == "Darwin" ]]; then
|
||||||
|
echo "Clearing xattr on all dot undercsore files"
|
||||||
|
find "$BASE_DIR" -type f -name "._*" -exec sh -c '
|
||||||
|
for f; do
|
||||||
|
dir=$(dirname "$f")
|
||||||
|
base=$(basename "$f")
|
||||||
|
orig="$dir/${base#._}"
|
||||||
|
[ -f "$orig" ] && xattr -c "$orig" || true
|
||||||
|
done
|
||||||
|
' sh {} +
|
||||||
|
fi
|
||||||
|
|
||||||
if [ "$CANARY" == "1" ]; then
|
if [ "$CANARY" == "1" ]; then
|
||||||
RELEASE_TAR_FILE_NAME=nogui-ryujinx-canary-$VERSION-macos_universal.tar
|
RELEASE_TAR_FILE_NAME=nogui-ryujinx-canary-$VERSION-macos_universal.tar
|
||||||
elif [ "$VERSION" == "1.1.0" ]; then
|
elif [ "$VERSION" == "1.1.0" ]; then
|
||||||
|
@ -1125,6 +1125,7 @@
|
|||||||
0100034012606000,"Family Mysteries: Poisonous Promises",audio;crash,menus,2021-11-26 12:35:06
|
0100034012606000,"Family Mysteries: Poisonous Promises",audio;crash,menus,2021-11-26 12:35:06
|
||||||
010017C012726000,"Fantasy Friends",,playable,2022-10-17 19:42:39
|
010017C012726000,"Fantasy Friends",,playable,2022-10-17 19:42:39
|
||||||
0100767008502000,"FANTASY HERO ~unsigned legacy~",,playable,2022-07-26 12:28:52
|
0100767008502000,"FANTASY HERO ~unsigned legacy~",,playable,2022-07-26 12:28:52
|
||||||
|
0100755017EE0000,"FANTASY LIFE i: The Girl Who Steals Time",gpu;crash;vulkan-backend-bug,ingame,2025-06-08 20:41:00
|
||||||
0100944003820000,"Fantasy Strike",online,playable,2021-02-27 01:59:18
|
0100944003820000,"Fantasy Strike",online,playable,2021-02-27 01:59:18
|
||||||
01000E2012F6E000,"Fantasy Tavern Sextet -Vol.1 New World Days-",gpu;crash;Needs Update,ingame,2022-12-05 16:48:00
|
01000E2012F6E000,"Fantasy Tavern Sextet -Vol.1 New World Days-",gpu;crash;Needs Update,ingame,2022-12-05 16:48:00
|
||||||
01005C10136CA000,"Fantasy Tavern Sextet -Vol.2 Adventurer's Days-",gpu;slow;crash,ingame,2021-11-06 02:57:29
|
01005C10136CA000,"Fantasy Tavern Sextet -Vol.2 Adventurer's Days-",gpu;slow;crash,ingame,2021-11-06 02:57:29
|
||||||
@ -2745,6 +2746,7 @@
|
|||||||
01005D701264A000,"SpyHack",,playable,2021-04-15 10:53:51
|
01005D701264A000,"SpyHack",,playable,2021-04-15 10:53:51
|
||||||
010077B00E046000,"Spyro™ Reignited Trilogy",nvdec;UE4,playable,2022-09-11 18:38:33
|
010077B00E046000,"Spyro™ Reignited Trilogy",nvdec;UE4,playable,2022-09-11 18:38:33
|
||||||
0100085012A0E000,"Squeakers",,playable,2020-12-13 12:13:05
|
0100085012A0E000,"Squeakers",,playable,2020-12-13 12:13:05
|
||||||
|
0100E1D01EB2E000,"Squeakross: Home Squeak Home",,playable,2025-06-16 02:02:00
|
||||||
010009300D31C000,"Squidgies Takeover",,playable,2020-07-20 22:28:08
|
010009300D31C000,"Squidgies Takeover",,playable,2020-07-20 22:28:08
|
||||||
0100FCD0102EC000,"Squidlit",,playable,2020-08-06 12:38:32
|
0100FCD0102EC000,"Squidlit",,playable,2020-08-06 12:38:32
|
||||||
0100EBF00E702000,"STAR OCEAN First Departure R",nvdec,playable,2021-07-05 19:29:16
|
0100EBF00E702000,"STAR OCEAN First Departure R",nvdec,playable,2021-07-05 19:29:16
|
||||||
@ -3015,6 +3017,7 @@
|
|||||||
01009B101044C000,"The Legend of Heroes: Trails of Cold Steel III Demo",demo;nvdec,playable,2021-04-23 01:07:32
|
01009B101044C000,"The Legend of Heroes: Trails of Cold Steel III Demo",demo;nvdec,playable,2021-04-23 01:07:32
|
||||||
0100D3C010DE8000,"The Legend of Heroes: Trails of Cold Steel IV",nvdec,playable,2021-04-23 14:01:05
|
0100D3C010DE8000,"The Legend of Heroes: Trails of Cold Steel IV",nvdec,playable,2021-04-23 14:01:05
|
||||||
01005E5013862000,"THE LEGEND OF HEROES: ZERO NO KISEKI KAI [英雄傳說 零之軌跡:改]",crash,nothing,2021-09-30 14:41:07
|
01005E5013862000,"THE LEGEND OF HEROES: ZERO NO KISEKI KAI [英雄傳說 零之軌跡:改]",crash,nothing,2021-09-30 14:41:07
|
||||||
|
01009C901ACEE000,"The Legend of Nayuta: Boundless Trails",,ingame,2025-06-12 15:47
|
||||||
01008CF01BAAC000,"The Legend of Zelda Echoes of Wisdom",nvdec;ASTC;intel-vendor-bug,playable,2024-10-01 14:11:01
|
01008CF01BAAC000,"The Legend of Zelda Echoes of Wisdom",nvdec;ASTC;intel-vendor-bug,playable,2024-10-01 14:11:01
|
||||||
0100509005AF2000,"The Legend of Zelda: Breath of the Wild Demo",demo,ingame,2022-12-24 05:02:58
|
0100509005AF2000,"The Legend of Zelda: Breath of the Wild Demo",demo,ingame,2022-12-24 05:02:58
|
||||||
01007EF00011E000,"The Legend of Zelda™: Breath of the Wild",gpu;amd-vendor-bug;mac-bug,ingame,2024-09-23 19:35:46
|
01007EF00011E000,"The Legend of Zelda™: Breath of the Wild",gpu;amd-vendor-bug;mac-bug,ingame,2024-09-23 19:35:46
|
||||||
|
|
18
nuget.config
18
nuget.config
@ -4,20 +4,22 @@
|
|||||||
<packageSources>
|
<packageSources>
|
||||||
<clear />
|
<clear />
|
||||||
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
|
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
|
||||||
<add key="LibHacAlpha" value="https://git.ryujinx.app/api/v4/projects/17/packages/nuget/index.json" />
|
<!-- Only needed when using pre-release versions of Ryujinx.LibHac. -->
|
||||||
<add key="RyubingPkgs" value="https://git.ryujinx.app/api/v4/projects/1/packages/nuget/index.json" />
|
<!--<add key="LibHacAlpha" value="https://git.ryujinx.app/api/v4/projects/17/packages/nuget/index.json" />-->
|
||||||
|
<add key="Ryujinx.UpdateClient" value="https://git.ryujinx.app/api/v4/projects/71/packages/nuget/index.json" />
|
||||||
</packageSources>
|
</packageSources>
|
||||||
|
|
||||||
<!-- Define mappings by adding package patterns beneath the target source. -->
|
|
||||||
<!-- Ryujinx.LibHac packages will be restored from LibHacAlpha,
|
|
||||||
everything else from nuget.org. -->
|
|
||||||
<packageSourceMapping>
|
<packageSourceMapping>
|
||||||
<!-- key value for <packageSource> should match key values from <packageSources> element -->
|
<!-- key value for <packageSource> should match key values from <packageSources> element -->
|
||||||
|
<!-- These are defined and .NET still yells about multiple package sources with no mappings. Not sure what to do, this is in the docs lol -->
|
||||||
<packageSource key="nuget.org">
|
<packageSource key="nuget.org">
|
||||||
<package pattern="*" />
|
<package pattern="*" />
|
||||||
</packageSource>
|
</packageSource>
|
||||||
<packageSource key="LibHacAlpha">
|
<packageSource key="Ryujinx.UpdateClient">
|
||||||
<package pattern="Ryujinx.LibHac" />
|
<package pattern="Ryujinx.UpdateClient" />
|
||||||
|
<package pattern="Ryujinx.Systems.Update.Common" />
|
||||||
</packageSource>
|
</packageSource>
|
||||||
|
<!--<packageSource key="LibHacAlpha">
|
||||||
|
<package pattern="Ryujinx.LibHac" />
|
||||||
|
</packageSource>-->
|
||||||
</packageSourceMapping>
|
</packageSourceMapping>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
@ -193,7 +193,7 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
_infosStream.Seek(0L, SeekOrigin.Begin);
|
_infosStream.Seek(0L, SeekOrigin.Begin);
|
||||||
bool foundBadFunction = false;
|
bool foundBadFunction = false;
|
||||||
|
|
||||||
for (int index = 0; index < GetEntriesCount(); index++)
|
for (int index = 0; index < _infosStream.Length / Unsafe.SizeOf<InfoEntry>(); index++)
|
||||||
{
|
{
|
||||||
InfoEntry infoEntry = DeserializeStructure<InfoEntry>(_infosStream);
|
InfoEntry infoEntry = DeserializeStructure<InfoEntry>(_infosStream);
|
||||||
foreach (ulong address in blacklist)
|
foreach (ulong address in blacklist)
|
||||||
|
@ -0,0 +1,200 @@
|
|||||||
|
using System;
|
||||||
|
using Ryujinx.Common.Configuration.Hid.Controller;
|
||||||
|
using Ryujinx.Common.Configuration.Hid.Controller.Motion;
|
||||||
|
using Ryujinx.Common.Configuration.Hid.Keyboard;
|
||||||
|
using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId;
|
||||||
|
using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId;
|
||||||
|
|
||||||
|
namespace Ryujinx.Common.Configuration.Hid
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides default input configurations for keyboard and controller devices
|
||||||
|
/// </summary>
|
||||||
|
public static class DefaultInputConfigurationProvider
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a default keyboard input configuration
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">Device ID</param>
|
||||||
|
/// <param name="name">Device name</param>
|
||||||
|
/// <param name="playerIndex">Player index</param>
|
||||||
|
/// <param name="controllerType">Controller type (defaults to ProController)</param>
|
||||||
|
/// <returns>Default keyboard input configuration</returns>
|
||||||
|
public static StandardKeyboardInputConfig CreateDefaultKeyboardConfig(string id, string name, PlayerIndex playerIndex, ControllerType controllerType = ControllerType.ProController)
|
||||||
|
{
|
||||||
|
return new StandardKeyboardInputConfig
|
||||||
|
{
|
||||||
|
Version = InputConfig.CurrentVersion,
|
||||||
|
Backend = InputBackendType.WindowKeyboard,
|
||||||
|
Id = id,
|
||||||
|
Name = name,
|
||||||
|
ControllerType = ControllerType.ProController,
|
||||||
|
PlayerIndex = playerIndex,
|
||||||
|
LeftJoycon = new LeftJoyconCommonConfig<Key>
|
||||||
|
{
|
||||||
|
DpadUp = Key.Up,
|
||||||
|
DpadDown = Key.Down,
|
||||||
|
DpadLeft = Key.Left,
|
||||||
|
DpadRight = Key.Right,
|
||||||
|
ButtonMinus = Key.Minus,
|
||||||
|
ButtonL = Key.E,
|
||||||
|
ButtonZl = Key.Q,
|
||||||
|
ButtonSl = Key.Unbound,
|
||||||
|
ButtonSr = Key.Unbound,
|
||||||
|
},
|
||||||
|
LeftJoyconStick = new JoyconConfigKeyboardStick<Key>
|
||||||
|
{
|
||||||
|
StickUp = Key.W,
|
||||||
|
StickDown = Key.S,
|
||||||
|
StickLeft = Key.A,
|
||||||
|
StickRight = Key.D,
|
||||||
|
StickButton = Key.F,
|
||||||
|
},
|
||||||
|
RightJoycon = new RightJoyconCommonConfig<Key>
|
||||||
|
{
|
||||||
|
ButtonA = Key.Z,
|
||||||
|
ButtonB = Key.X,
|
||||||
|
ButtonX = Key.C,
|
||||||
|
ButtonY = Key.V,
|
||||||
|
ButtonPlus = Key.Plus,
|
||||||
|
ButtonR = Key.U,
|
||||||
|
ButtonZr = Key.O,
|
||||||
|
ButtonSl = Key.Unbound,
|
||||||
|
ButtonSr = Key.Unbound,
|
||||||
|
},
|
||||||
|
RightJoyconStick = new JoyconConfigKeyboardStick<Key>
|
||||||
|
{
|
||||||
|
StickUp = Key.I,
|
||||||
|
StickDown = Key.K,
|
||||||
|
StickLeft = Key.J,
|
||||||
|
StickRight = Key.L,
|
||||||
|
StickButton = Key.H,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a default controller input configuration
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">Device ID</param>
|
||||||
|
/// <param name="name">Device name</param>
|
||||||
|
/// <param name="playerIndex">Player index</param>
|
||||||
|
/// <param name="isNintendoStyle">Whether to use Nintendo-style button mapping</param>
|
||||||
|
/// <returns>Default controller input configuration</returns>
|
||||||
|
public static StandardControllerInputConfig CreateDefaultControllerConfig(string id, string name, PlayerIndex playerIndex, bool isNintendoStyle = false)
|
||||||
|
{
|
||||||
|
// Split the ID for controller configs
|
||||||
|
string cleanId = id.Split(" ")[0];
|
||||||
|
|
||||||
|
return new StandardControllerInputConfig
|
||||||
|
{
|
||||||
|
Version = InputConfig.CurrentVersion,
|
||||||
|
Backend = InputBackendType.GamepadSDL2,
|
||||||
|
Id = cleanId,
|
||||||
|
Name = name,
|
||||||
|
ControllerType = ControllerType.ProController,
|
||||||
|
PlayerIndex = playerIndex,
|
||||||
|
DeadzoneLeft = 0.1f,
|
||||||
|
DeadzoneRight = 0.1f,
|
||||||
|
RangeLeft = 1.0f,
|
||||||
|
RangeRight = 1.0f,
|
||||||
|
TriggerThreshold = 0.5f,
|
||||||
|
LeftJoycon = new LeftJoyconCommonConfig<ConfigGamepadInputId>
|
||||||
|
{
|
||||||
|
DpadUp = ConfigGamepadInputId.DpadUp,
|
||||||
|
DpadDown = ConfigGamepadInputId.DpadDown,
|
||||||
|
DpadLeft = ConfigGamepadInputId.DpadLeft,
|
||||||
|
DpadRight = ConfigGamepadInputId.DpadRight,
|
||||||
|
ButtonMinus = ConfigGamepadInputId.Minus,
|
||||||
|
ButtonL = ConfigGamepadInputId.LeftShoulder,
|
||||||
|
ButtonZl = ConfigGamepadInputId.LeftTrigger,
|
||||||
|
ButtonSl = ConfigGamepadInputId.Unbound,
|
||||||
|
ButtonSr = ConfigGamepadInputId.Unbound,
|
||||||
|
},
|
||||||
|
LeftJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId>
|
||||||
|
{
|
||||||
|
Joystick = ConfigStickInputId.Left,
|
||||||
|
StickButton = ConfigGamepadInputId.LeftStick,
|
||||||
|
InvertStickX = false,
|
||||||
|
InvertStickY = false,
|
||||||
|
},
|
||||||
|
RightJoycon = new RightJoyconCommonConfig<ConfigGamepadInputId>
|
||||||
|
{
|
||||||
|
ButtonA = isNintendoStyle ? ConfigGamepadInputId.A : ConfigGamepadInputId.B,
|
||||||
|
ButtonB = isNintendoStyle ? ConfigGamepadInputId.B : ConfigGamepadInputId.A,
|
||||||
|
ButtonX = isNintendoStyle ? ConfigGamepadInputId.X : ConfigGamepadInputId.Y,
|
||||||
|
ButtonY = isNintendoStyle ? ConfigGamepadInputId.Y : ConfigGamepadInputId.X,
|
||||||
|
ButtonPlus = ConfigGamepadInputId.Plus,
|
||||||
|
ButtonR = ConfigGamepadInputId.RightShoulder,
|
||||||
|
ButtonZr = ConfigGamepadInputId.RightTrigger,
|
||||||
|
ButtonSl = ConfigGamepadInputId.Unbound,
|
||||||
|
ButtonSr = ConfigGamepadInputId.Unbound,
|
||||||
|
},
|
||||||
|
RightJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId>
|
||||||
|
{
|
||||||
|
Joystick = ConfigStickInputId.Right,
|
||||||
|
StickButton = ConfigGamepadInputId.RightStick,
|
||||||
|
InvertStickX = false,
|
||||||
|
InvertStickY = false,
|
||||||
|
},
|
||||||
|
Motion = new StandardMotionConfigController
|
||||||
|
{
|
||||||
|
EnableMotion = true,
|
||||||
|
MotionBackend = MotionInputBackendType.GamepadDriver,
|
||||||
|
GyroDeadzone = 1,
|
||||||
|
Sensitivity = 100,
|
||||||
|
},
|
||||||
|
Rumble = new RumbleConfigController
|
||||||
|
{
|
||||||
|
EnableRumble = false,
|
||||||
|
WeakRumble = 1f,
|
||||||
|
StrongRumble = 1f,
|
||||||
|
},
|
||||||
|
Led = new LedConfigController
|
||||||
|
{
|
||||||
|
EnableLed = false,
|
||||||
|
TurnOffLed = false,
|
||||||
|
UseRainbow = false,
|
||||||
|
LedColor = 0xFFFFFFFF,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the short name of a gamepad by removing SDL prefix and truncating if too long
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">Full gamepad name</param>
|
||||||
|
/// <param name="maxLength">Maximum length before truncation (default: 50)</param>
|
||||||
|
/// <returns>Short gamepad name</returns>
|
||||||
|
public static string GetShortGamepadName(string name, int maxLength = 50)
|
||||||
|
{
|
||||||
|
const string SdlGamepadNamePrefix = "SDL2 Gamepad ";
|
||||||
|
const string Ellipsis = "...";
|
||||||
|
|
||||||
|
// First remove SDL prefix if present
|
||||||
|
string shortName = name;
|
||||||
|
if (name.StartsWith(SdlGamepadNamePrefix))
|
||||||
|
{
|
||||||
|
shortName = name[SdlGamepadNamePrefix.Length..];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then truncate if too long
|
||||||
|
if (shortName.Length > maxLength)
|
||||||
|
{
|
||||||
|
return $"{shortName.AsSpan(0, maxLength - Ellipsis.Length)}{Ellipsis}";
|
||||||
|
}
|
||||||
|
|
||||||
|
return shortName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines if a controller uses Nintendo-style button mapping
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">Controller name</param>
|
||||||
|
/// <returns>True if Nintendo-style mapping should be used</returns>
|
||||||
|
public static bool IsNintendoStyleController(string name)
|
||||||
|
{
|
||||||
|
return name.Contains("Nintendo");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -15,5 +15,14 @@ namespace Ryujinx.Common.Configuration.Hid
|
|||||||
public Key CustomVSyncIntervalDecrement { get; set; }
|
public Key CustomVSyncIntervalDecrement { get; set; }
|
||||||
public Key TurboMode { get; set; }
|
public Key TurboMode { get; set; }
|
||||||
public bool TurboModeWhileHeld { get; set; }
|
public bool TurboModeWhileHeld { get; set; }
|
||||||
|
public Key CycleInputDevicePlayer1 { get; set; }
|
||||||
|
public Key CycleInputDevicePlayer2 { get; set; }
|
||||||
|
public Key CycleInputDevicePlayer3 { get; set; }
|
||||||
|
public Key CycleInputDevicePlayer4 { get; set; }
|
||||||
|
public Key CycleInputDevicePlayer5 { get; set; }
|
||||||
|
public Key CycleInputDevicePlayer6 { get; set; }
|
||||||
|
public Key CycleInputDevicePlayer7 { get; set; }
|
||||||
|
public Key CycleInputDevicePlayer8 { get; set; }
|
||||||
|
public Key CycleInputDeviceHandheld { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,59 +32,11 @@ namespace Ryujinx.Common
|
|||||||
|
|
||||||
public static string Version => IsValid ? BuildVersion : Assembly.GetEntryAssembly()!.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
|
public static string Version => IsValid ? BuildVersion : Assembly.GetEntryAssembly()!.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
|
||||||
|
|
||||||
public static string GetChangelogUrl(Version currentVersion, Version newVersion, ReleaseChannels.Channel releaseChannel) =>
|
public static string GetChangelogUrl(Version currentVersion, Version newVersion) =>
|
||||||
IsCanaryBuild
|
IsCanaryBuild
|
||||||
? $"https://git.ryujinx.app/ryubing/ryujinx/-/compare/Canary-{currentVersion}...Canary-{newVersion}"
|
? $"https://git.ryujinx.app/ryubing/ryujinx/-/compare/Canary-{currentVersion}...Canary-{newVersion}"
|
||||||
: GetChangelogForVersion(newVersion, releaseChannel);
|
: $"https://git.ryujinx.app/ryubing/ryujinx/-/releases/{newVersion}";
|
||||||
|
|
||||||
public static string GetChangelogForVersion(Version version, ReleaseChannels.Channel releaseChannel) =>
|
|
||||||
$"https://github.com/{releaseChannel}/releases/{version}";
|
|
||||||
|
|
||||||
public static async Task<ReleaseChannels> GetReleaseChannelsAsync(HttpClient httpClient)
|
|
||||||
{
|
|
||||||
ReleaseChannelPair releaseChannelPair = await httpClient.GetFromJsonAsync("https://ryujinx.app/api/release-channels", ReleaseChannelPairContext.Default.ReleaseChannelPair);
|
|
||||||
return new ReleaseChannels(releaseChannelPair);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly struct ReleaseChannels
|
|
||||||
{
|
|
||||||
internal ReleaseChannels(ReleaseChannelPair channelPair)
|
|
||||||
{
|
|
||||||
Stable = new Channel(channelPair.Stable);
|
|
||||||
Canary = new Channel(channelPair.Canary);
|
|
||||||
}
|
|
||||||
|
|
||||||
public readonly Channel Stable;
|
|
||||||
public readonly Channel Canary;
|
|
||||||
|
|
||||||
public readonly struct Channel
|
|
||||||
{
|
|
||||||
public Channel(string raw)
|
|
||||||
{
|
|
||||||
string[] parts = raw.Split('/');
|
|
||||||
Owner = parts[0];
|
|
||||||
Repo = parts[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
public readonly string Owner;
|
|
||||||
public readonly string Repo;
|
|
||||||
|
|
||||||
public override string ToString() => $"{Owner}/{Repo}";
|
|
||||||
|
|
||||||
public string GetLatestReleaseApiUrl() =>
|
|
||||||
$"https://api.github.com/repos/{ToString()}/releases/latest";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[JsonSerializable(typeof(ReleaseChannelPair))]
|
|
||||||
partial class ReleaseChannelPairContext : JsonSerializerContext;
|
|
||||||
|
|
||||||
class ReleaseChannelPair
|
|
||||||
{
|
|
||||||
[JsonPropertyName("stable")]
|
|
||||||
public string Stable { get; set; }
|
|
||||||
[JsonPropertyName("canary")]
|
|
||||||
public string Canary { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -133,7 +133,6 @@ namespace Ryujinx.Common
|
|||||||
"0100c1f0051b6000", // Donkey Kong Country: Tropical Freeze
|
"0100c1f0051b6000", // Donkey Kong Country: Tropical Freeze
|
||||||
"0100ed000d390000", // Dr. Kawashima's Brain Training
|
"0100ed000d390000", // Dr. Kawashima's Brain Training
|
||||||
"010067b017588000", // Endless Ocean Luminous
|
"010067b017588000", // Endless Ocean Luminous
|
||||||
"0100d2f00d5c0000", // Nintendo Switch Sports
|
|
||||||
"01006b5012b32000", // Part Time UFO
|
"01006b5012b32000", // Part Time UFO
|
||||||
"0100704000B3A000", // Snipperclips
|
"0100704000B3A000", // Snipperclips
|
||||||
"01006a800016e000", // Super Smash Bros. Ultimate
|
"01006a800016e000", // Super Smash Bros. Ultimate
|
||||||
@ -169,6 +168,8 @@ namespace Ryujinx.Common
|
|||||||
"010056e00853a000", // A Hat in Time
|
"010056e00853a000", // A Hat in Time
|
||||||
"0100fd1014726000", // Baldurs Gate: Dark Alliance
|
"0100fd1014726000", // Baldurs Gate: Dark Alliance
|
||||||
"01008c2019598000", // Bluey: The Video Game
|
"01008c2019598000", // Bluey: The Video Game
|
||||||
|
"010096f00ff22000", // Borderlands 2: Game of the Year Edition
|
||||||
|
"010007400ff24000", // Borderlands: The Pre-Sequel Ultimate Edition
|
||||||
"0100c6800b934000", // Brawlhalla
|
"0100c6800b934000", // Brawlhalla
|
||||||
"0100dbf01000a000", // Burnout Paradise Remastered
|
"0100dbf01000a000", // Burnout Paradise Remastered
|
||||||
"0100744001588000", // Cars 3: Driven to Win
|
"0100744001588000", // Cars 3: Driven to Win
|
||||||
@ -194,6 +195,7 @@ namespace Ryujinx.Common
|
|||||||
"01008d100d43e000", // Saints Row IV
|
"01008d100d43e000", // Saints Row IV
|
||||||
"0100de600beee000", // Saints Row: The Third - The Full Package
|
"0100de600beee000", // Saints Row: The Third - The Full Package
|
||||||
"01001180021fa000", // Shovel Knight: Specter of Torment
|
"01001180021fa000", // Shovel Knight: Specter of Torment
|
||||||
|
"0100e1D01eb2e000", // Squeakross: Home Squeak Home
|
||||||
"0100e65002bb8000", // Stardew Valley
|
"0100e65002bb8000", // Stardew Valley
|
||||||
"0100d7a01b7a2000", // Star Wars: Bounty Hunter
|
"0100d7a01b7a2000", // Star Wars: Bounty Hunter
|
||||||
"0100800015926000", // Suika Game
|
"0100800015926000", // Suika Game
|
||||||
|
@ -41,5 +41,10 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
|||||||
public void SetScalingFilterLevel(float level) { }
|
public void SetScalingFilterLevel(float level) { }
|
||||||
|
|
||||||
public void SetColorSpacePassthrough(bool colorSpacePassthroughEnabled) { }
|
public void SetColorSpacePassthrough(bool colorSpacePassthroughEnabled) { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the underlying implementation window for direct access
|
||||||
|
/// </summary>
|
||||||
|
public IWindow BaseWindow => _impl.Window;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,7 @@ namespace Ryujinx.Graphics.Gpu
|
|||||||
/// GPU synchronization manager.
|
/// GPU synchronization manager.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public SynchronizationManager Synchronization { get; }
|
public SynchronizationManager Synchronization { get; }
|
||||||
|
public IOverlayManager OverlayManager { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Presentation window.
|
/// Presentation window.
|
||||||
@ -121,14 +122,18 @@ namespace Ryujinx.Graphics.Gpu
|
|||||||
/// Creates a new instance of the GPU emulation context.
|
/// Creates a new instance of the GPU emulation context.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="renderer">Host renderer</param>
|
/// <param name="renderer">Host renderer</param>
|
||||||
public GpuContext(IRenderer renderer, DirtyHacks hacks)
|
/// <param name="hacks">Enabled dirty hacks</param>
|
||||||
|
/// <param name="overlayManager">Overlay manager for rendering overlays</param>
|
||||||
|
public GpuContext(IRenderer renderer, DirtyHacks hacks, IOverlayManager overlayManager)
|
||||||
{
|
{
|
||||||
Renderer = renderer;
|
Renderer = renderer;
|
||||||
|
|
||||||
GPFifo = new GPFifoDevice(this);
|
GPFifo = new GPFifoDevice(this);
|
||||||
|
|
||||||
Synchronization = new SynchronizationManager();
|
Synchronization = new SynchronizationManager();
|
||||||
|
|
||||||
|
OverlayManager = overlayManager;
|
||||||
|
|
||||||
Window = new Window(this);
|
Window = new Window(this);
|
||||||
|
|
||||||
HostInitalized = new ManualResetEvent(false);
|
HostInitalized = new ManualResetEvent(false);
|
||||||
@ -462,6 +467,8 @@ namespace Ryujinx.Graphics.Gpu
|
|||||||
RunDeferredActions();
|
RunDeferredActions();
|
||||||
|
|
||||||
Renderer.Dispose();
|
Renderer.Dispose();
|
||||||
|
|
||||||
|
OverlayManager.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
54
src/Ryujinx.Graphics.Gpu/IOverlay.cs
Normal file
54
src/Ryujinx.Graphics.Gpu/IOverlay.cs
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
using SkiaSharp;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.Graphics.Gpu
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interface for overlay functionality
|
||||||
|
/// </summary>
|
||||||
|
public interface IOverlay : IDisposable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Name of the overlay
|
||||||
|
/// </summary>
|
||||||
|
string Name { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the overlay is visible
|
||||||
|
/// </summary>
|
||||||
|
bool IsVisible { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Opacity of the overlay (0.0 to 1.0)
|
||||||
|
/// </summary>
|
||||||
|
float Opacity { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// X position of the overlay
|
||||||
|
/// </summary>
|
||||||
|
float X { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Y position of the overlay
|
||||||
|
/// </summary>
|
||||||
|
float Y { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Z-index for overlay ordering
|
||||||
|
/// </summary>
|
||||||
|
int ZIndex { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update overlay (for animations)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="deltaTime">Time elapsed since last update</param>
|
||||||
|
/// <param name="screenSize">Current screen size</param>
|
||||||
|
void Update(float deltaTime, SKSize screenSize = default);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Render this overlay
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="canvas">The canvas to render to</param>
|
||||||
|
void Render(SKCanvas canvas);
|
||||||
|
}
|
||||||
|
}
|
30
src/Ryujinx.Graphics.Gpu/IOverlayManager.cs
Normal file
30
src/Ryujinx.Graphics.Gpu/IOverlayManager.cs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
using SkiaSharp;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.Graphics.Gpu
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interface for overlay management functionality
|
||||||
|
/// </summary>
|
||||||
|
public interface IOverlayManager : IDisposable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Add an overlay to the manager
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="overlay">The overlay to add</param>
|
||||||
|
void AddOverlay(IOverlay overlay);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update all overlays (for animations)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="deltaTime">Time elapsed since last update</param>
|
||||||
|
/// <param name="screenSize">Current screen size</param>
|
||||||
|
void Update(float deltaTime, SKSize screenSize = default);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Render all visible overlays
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="canvas">The canvas to render to</param>
|
||||||
|
void Render(SKCanvas canvas);
|
||||||
|
}
|
||||||
|
}
|
@ -14,4 +14,8 @@
|
|||||||
<ProjectReference Include="..\Ryujinx.Graphics.Shader\Ryujinx.Graphics.Shader.csproj" />
|
<ProjectReference Include="..\Ryujinx.Graphics.Shader\Ryujinx.Graphics.Shader.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="SkiaSharp" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
using Ryujinx.Graphics.GAL;
|
using Ryujinx.Graphics.GAL;
|
||||||
using Ryujinx.Graphics.Gpu.Image;
|
using Ryujinx.Graphics.Gpu.Image;
|
||||||
using Ryujinx.Graphics.Gpu.Memory;
|
using Ryujinx.Graphics.Gpu.Memory;
|
||||||
using Ryujinx.Graphics.Texture;
|
using Ryujinx.Graphics.Texture;
|
||||||
using Ryujinx.Memory.Range;
|
using Ryujinx.Memory.Range;
|
||||||
|
using SkiaSharp;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Gpu
|
namespace Ryujinx.Graphics.Gpu
|
||||||
@ -15,6 +19,7 @@ namespace Ryujinx.Graphics.Gpu
|
|||||||
public class Window
|
public class Window
|
||||||
{
|
{
|
||||||
private readonly GpuContext _context;
|
private readonly GpuContext _context;
|
||||||
|
private DateTime? _lastUpdateTime = null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Texture presented on the window.
|
/// Texture presented on the window.
|
||||||
@ -207,6 +212,9 @@ namespace Ryujinx.Graphics.Gpu
|
|||||||
|
|
||||||
texture.SynchronizeMemory();
|
texture.SynchronizeMemory();
|
||||||
|
|
||||||
|
// Add overlays by modifying texture data directly
|
||||||
|
AddOverlaysToTexture(texture, pt.Crop);
|
||||||
|
|
||||||
ImageCrop crop = new(
|
ImageCrop crop = new(
|
||||||
(int)(pt.Crop.Left * texture.ScaleFactor),
|
(int)(pt.Crop.Left * texture.ScaleFactor),
|
||||||
(int)MathF.Ceiling(pt.Crop.Right * texture.ScaleFactor),
|
(int)MathF.Ceiling(pt.Crop.Right * texture.ScaleFactor),
|
||||||
@ -244,6 +252,99 @@ namespace Ryujinx.Graphics.Gpu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add overlay to the overlay manager
|
||||||
|
/// </summary>
|
||||||
|
public void AddOverlay(IOverlay overlay)
|
||||||
|
{
|
||||||
|
_context.OverlayManager.AddOverlay(overlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add overlays to the texture using SkiaSharp
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="texture">The texture to modify</param>
|
||||||
|
/// <param name="crop">The crop information containing flip flags</param>
|
||||||
|
private void AddOverlaysToTexture(Image.Texture texture, ImageCrop crop)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
DateTime currentTime = DateTime.UtcNow;
|
||||||
|
if (_lastUpdateTime != null)
|
||||||
|
{
|
||||||
|
// Calculate delta time for lifespan updates
|
||||||
|
float deltaTime = (float)(currentTime - _lastUpdateTime.Value).TotalSeconds;
|
||||||
|
_context.OverlayManager.Update(deltaTime, new SKSize(texture.Info.Width, texture.Info.Height));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update overlay animations
|
||||||
|
_lastUpdateTime = currentTime;
|
||||||
|
|
||||||
|
// Get texture data from host texture
|
||||||
|
using var pinnedData = texture.HostTexture.GetData();
|
||||||
|
var data = pinnedData.Get().ToArray();
|
||||||
|
if (data == null || data.Length == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int width = texture.Info.Width;
|
||||||
|
int height = texture.Info.Height;
|
||||||
|
int bytesPerPixel = texture.Info.FormatInfo.BytesPerPixel;
|
||||||
|
|
||||||
|
// Determine the SKColorType based on bytes per pixel
|
||||||
|
SKColorType colorType = bytesPerPixel switch
|
||||||
|
{
|
||||||
|
4 => SKColorType.Rgba8888,
|
||||||
|
3 => SKColorType.Rgb888x,
|
||||||
|
2 => SKColorType.Rgb565,
|
||||||
|
_ => SKColorType.Rgba8888
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create SKBitmap from texture data
|
||||||
|
var imageInfo = new SKImageInfo(width, height, colorType, SKAlphaType.Premul);
|
||||||
|
using var bitmap = new SKBitmap(imageInfo);
|
||||||
|
|
||||||
|
// Copy texture data to bitmap
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
fixed (byte* dataPtr = data)
|
||||||
|
{
|
||||||
|
bitmap.SetPixels((IntPtr)dataPtr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create canvas for drawing overlays
|
||||||
|
using var canvas = new SKCanvas(bitmap);
|
||||||
|
|
||||||
|
// Flip Y-axis if the game/texture requires it
|
||||||
|
// Some games have textures that are already flipped, while others need flipping
|
||||||
|
if (crop.FlipY)
|
||||||
|
{
|
||||||
|
canvas.Scale(1, -1);
|
||||||
|
canvas.Translate(0, -height);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render all overlays
|
||||||
|
_context.OverlayManager.Render(canvas);
|
||||||
|
|
||||||
|
// Copy modified bitmap data back to texture data array
|
||||||
|
var pixels = bitmap.Bytes;
|
||||||
|
if (pixels.Length <= data.Length)
|
||||||
|
{
|
||||||
|
Array.Copy(pixels, data, pixels.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload modified data back to texture
|
||||||
|
var memoryOwner = MemoryOwner<byte>.Rent(data.Length);
|
||||||
|
data.CopyTo(memoryOwner.Span);
|
||||||
|
texture.HostTexture.SetData(memoryOwner); // SetData will dispose the MemoryOwner
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// Silently fail if overlay rendering doesn't work
|
||||||
|
Logger.Error?.Print(LogClass.Gpu, $"Overlay rendering failed: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicate that a frame on the queue is ready to be acquired.
|
/// Indicate that a frame on the queue is ready to be acquired.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -874,57 +874,42 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
public unsafe void ConvertIndexBuffer(VulkanRenderer gd,
|
public unsafe void ConvertIndexBuffer(VulkanRenderer gd,
|
||||||
CommandBufferScoped cbs,
|
CommandBufferScoped cbs,
|
||||||
BufferHolder src,
|
BufferHolder srcIndexBuffer,
|
||||||
BufferHolder dst,
|
BufferHolder dstIndexBuffer,
|
||||||
IndexBufferPattern pattern,
|
IndexBufferPattern pattern,
|
||||||
int indexSize,
|
int indexSize,
|
||||||
int srcOffset,
|
int srcOffset,
|
||||||
int indexCount)
|
int indexCount)
|
||||||
{
|
{
|
||||||
// TODO: Support conversion with primitive restart enabled.
|
// TODO: Support conversion with primitive restart enabled.
|
||||||
// TODO: Convert with a compute shader?
|
|
||||||
|
|
||||||
|
int primitiveCount = pattern.GetPrimitiveCount(indexCount);
|
||||||
int convertedCount = pattern.GetConvertedCount(indexCount);
|
int convertedCount = pattern.GetConvertedCount(indexCount);
|
||||||
int outputIndexSize = 4;
|
int outputIndexSize = 4;
|
||||||
|
|
||||||
|
Buffer dstBuffer = dstIndexBuffer.GetBuffer().Get(cbs, 0, convertedCount * outputIndexSize).Value;
|
||||||
|
|
||||||
Buffer srcBuffer = src.GetBuffer().Get(cbs, srcOffset, indexCount * indexSize).Value;
|
const int ParamsBufferSize = 16 * sizeof(int);
|
||||||
Buffer dstBuffer = dst.GetBuffer().Get(cbs, 0, convertedCount * outputIndexSize).Value;
|
|
||||||
|
|
||||||
gd.Api.CmdFillBuffer(cbs.CommandBuffer, dstBuffer, 0, Vk.WholeSize, 0);
|
Span<int> shaderParams = stackalloc int[ParamsBufferSize / sizeof(int)];
|
||||||
|
|
||||||
List<BufferCopy> bufferCopy = [];
|
shaderParams[8] = pattern.PrimitiveVertices;
|
||||||
int outputOffset = 0;
|
shaderParams[9] = pattern.PrimitiveVerticesOut;
|
||||||
|
shaderParams[10] = indexSize;
|
||||||
|
shaderParams[11] = outputIndexSize;
|
||||||
|
shaderParams[12] = pattern.BaseIndex;
|
||||||
|
shaderParams[13] = pattern.IndexStride;
|
||||||
|
shaderParams[14] = srcOffset;
|
||||||
|
shaderParams[15] = primitiveCount;
|
||||||
|
|
||||||
// Try to merge copies of adjacent indices to reduce copy count.
|
pattern.OffsetIndex.CopyTo(shaderParams[..pattern.OffsetIndex.Length]);
|
||||||
int sequenceStart = 0;
|
|
||||||
int sequenceLength = 0;
|
|
||||||
|
|
||||||
foreach (int index in pattern.GetIndexMapping(indexCount))
|
using var patternScoped = gd.BufferManager.ReserveOrCreate(gd, cbs, ParamsBufferSize);
|
||||||
{
|
var patternBuffer = patternScoped.Holder;
|
||||||
if (sequenceLength > 0)
|
|
||||||
{
|
|
||||||
if (index == sequenceStart + sequenceLength && indexSize == outputIndexSize)
|
|
||||||
{
|
|
||||||
sequenceLength++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Commit the copy so far.
|
patternBuffer.SetDataUnchecked<int>(patternScoped.Offset, shaderParams);
|
||||||
bufferCopy.Add(new BufferCopy((ulong)(srcOffset + sequenceStart * indexSize), (ulong)outputOffset, (ulong)(indexSize * sequenceLength)));
|
|
||||||
outputOffset += outputIndexSize * sequenceLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
sequenceStart = index;
|
_pipeline.SetCommandBuffer(cbs);
|
||||||
sequenceLength = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sequenceLength > 0)
|
|
||||||
{
|
|
||||||
// Commit final pending copy.
|
|
||||||
bufferCopy.Add(new BufferCopy((ulong)(srcOffset + sequenceStart * indexSize), (ulong)outputOffset, (ulong)(indexSize * sequenceLength)));
|
|
||||||
}
|
|
||||||
|
|
||||||
BufferCopy[] bufferCopyArray = bufferCopy.ToArray();
|
|
||||||
|
|
||||||
BufferHolder.InsertBufferBarrier(
|
BufferHolder.InsertBufferBarrier(
|
||||||
gd,
|
gd,
|
||||||
@ -937,10 +922,11 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
0,
|
0,
|
||||||
convertedCount * outputIndexSize);
|
convertedCount * outputIndexSize);
|
||||||
|
|
||||||
fixed (BufferCopy* pBufferCopy = bufferCopyArray)
|
_pipeline.SetUniformBuffers([new BufferAssignment(0, new BufferRange(patternScoped.Handle, patternScoped.Offset, ParamsBufferSize))]);
|
||||||
{
|
_pipeline.SetStorageBuffers(1, new[] { srcIndexBuffer.GetBuffer(), dstIndexBuffer.GetBuffer() });
|
||||||
gd.Api.CmdCopyBuffer(cbs.CommandBuffer, srcBuffer, dstBuffer, (uint)bufferCopyArray.Length, pBufferCopy);
|
|
||||||
}
|
_pipeline.SetProgram(_programConvertIndexBuffer);
|
||||||
|
_pipeline.DispatchCompute(BitUtils.DivRoundUp(primitiveCount, 16), 1, 1);
|
||||||
|
|
||||||
BufferHolder.InsertBufferBarrier(
|
BufferHolder.InsertBufferBarrier(
|
||||||
gd,
|
gd,
|
||||||
@ -952,6 +938,8 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
PipelineStageFlags.AllCommandsBit,
|
PipelineStageFlags.AllCommandsBit,
|
||||||
0,
|
0,
|
||||||
convertedCount * outputIndexSize);
|
convertedCount * outputIndexSize);
|
||||||
|
|
||||||
|
_pipeline.Finish(gd, cbs);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CopyIncompatibleFormats(
|
public void CopyIncompatibleFormats(
|
||||||
|
@ -47,28 +47,6 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
return primitiveCount * OffsetIndex.Length;
|
return primitiveCount * OffsetIndex.Length;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<int> GetIndexMapping(int indexCount)
|
|
||||||
{
|
|
||||||
int primitiveCount = GetPrimitiveCount(indexCount);
|
|
||||||
int index = BaseIndex;
|
|
||||||
|
|
||||||
for (int i = 0; i < primitiveCount; i++)
|
|
||||||
{
|
|
||||||
if (RepeatStart)
|
|
||||||
{
|
|
||||||
// Used for triangle fan
|
|
||||||
yield return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int j = RepeatStart ? 1 : 0; j < OffsetIndex.Length; j++)
|
|
||||||
{
|
|
||||||
yield return index + OffsetIndex[j];
|
|
||||||
}
|
|
||||||
|
|
||||||
index += IndexStride;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public BufferHandle GetRepeatingBuffer(int vertexCount, out int indexCount)
|
public BufferHandle GetRepeatingBuffer(int vertexCount, out int indexCount)
|
||||||
{
|
{
|
||||||
int primitiveCount = GetPrimitiveCount(vertexCount);
|
int primitiveCount = GetPrimitiveCount(vertexCount);
|
||||||
|
@ -377,7 +377,7 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
|
|||||||
|
|
||||||
bool cursorVisible = false;
|
bool cursorVisible = false;
|
||||||
|
|
||||||
if (state.CursorBegin != state.CursorEnd)
|
if (state.CursorBegin != state.CursorEnd && state.CursorEnd <= state.InputText.Length)
|
||||||
{
|
{
|
||||||
Debug.Assert(state.InputText.Length > 0);
|
Debug.Assert(state.InputText.Length > 0);
|
||||||
|
|
||||||
|
@ -21,6 +21,21 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
|||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[CommandCmif(3)] // 20.0.0+
|
||||||
|
// CreateLibraryAppletEx(u32, u32, u64) -> object<nn::am::service::ILibraryAppletAccessor>
|
||||||
|
public ResultCode CreateLibraryAppletEx(ServiceCtx context)
|
||||||
|
{
|
||||||
|
AppletId appletId = (AppletId)context.RequestData.ReadInt32();
|
||||||
|
|
||||||
|
_ = context.RequestData.ReadInt32(); // libraryAppletMode
|
||||||
|
|
||||||
|
_ = context.RequestData.ReadUInt64(); // threadId
|
||||||
|
|
||||||
|
MakeObject(context, new ILibraryAppletAccessor(appletId, context.Device.System));
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
[CommandCmif(10)]
|
[CommandCmif(10)]
|
||||||
// CreateStorage(u64) -> object<nn::am::service::IStorage>
|
// CreateStorage(u64) -> object<nn::am::service::IStorage>
|
||||||
public ResultCode CreateStorage(ServiceCtx context)
|
public ResultCode CreateStorage(ServiceCtx context)
|
||||||
|
@ -885,7 +885,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
|
|||||||
// F_SETFL
|
// F_SETFL
|
||||||
else if (cmd == 0x4)
|
else if (cmd == 0x4)
|
||||||
{
|
{
|
||||||
socket.Blocking = (arg & 0x800) != 0;
|
socket.Blocking = (arg & 0x800) == 0;
|
||||||
result = 0;
|
result = 0;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -3,6 +3,7 @@ using Ryujinx.Audio.Integration;
|
|||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Common.Configuration;
|
||||||
using Ryujinx.Common.Configuration.Multiplayer;
|
using Ryujinx.Common.Configuration.Multiplayer;
|
||||||
using Ryujinx.Graphics.GAL;
|
using Ryujinx.Graphics.GAL;
|
||||||
|
using Ryujinx.Graphics.Gpu;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
using Ryujinx.HLE.HOS;
|
using Ryujinx.HLE.HOS;
|
||||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||||
@ -65,6 +66,12 @@ namespace Ryujinx.HLE
|
|||||||
/// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks>
|
/// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks>
|
||||||
internal IHostUIHandler HostUIHandler { get; private set; }
|
internal IHostUIHandler HostUIHandler { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The overlay manager to use for all overlay operations.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks>
|
||||||
|
internal IOverlayManager OverlayManager { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Control the memory configuration used by the emulation context.
|
/// Control the memory configuration used by the emulation context.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -262,7 +269,8 @@ namespace Ryujinx.HLE
|
|||||||
UserChannelPersistence userChannelPersistence,
|
UserChannelPersistence userChannelPersistence,
|
||||||
IRenderer gpuRenderer,
|
IRenderer gpuRenderer,
|
||||||
IHardwareDeviceDriver audioDeviceDriver,
|
IHardwareDeviceDriver audioDeviceDriver,
|
||||||
IHostUIHandler hostUIHandler
|
IHostUIHandler hostUIHandler,
|
||||||
|
IOverlayManager overlayManager
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
VirtualFileSystem = virtualFileSystem;
|
VirtualFileSystem = virtualFileSystem;
|
||||||
@ -273,6 +281,7 @@ namespace Ryujinx.HLE
|
|||||||
GpuRenderer = gpuRenderer;
|
GpuRenderer = gpuRenderer;
|
||||||
AudioDeviceDriver = audioDeviceDriver;
|
AudioDeviceDriver = audioDeviceDriver;
|
||||||
HostUIHandler = hostUIHandler;
|
HostUIHandler = hostUIHandler;
|
||||||
|
OverlayManager = overlayManager;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -71,7 +71,7 @@ namespace Ryujinx.HLE
|
|||||||
DirtyHacks = new DirtyHacks(Configuration.Hacks);
|
DirtyHacks = new DirtyHacks(Configuration.Hacks);
|
||||||
AudioDeviceDriver = new CompatLayerHardwareDeviceDriver(Configuration.AudioDeviceDriver);
|
AudioDeviceDriver = new CompatLayerHardwareDeviceDriver(Configuration.AudioDeviceDriver);
|
||||||
Memory = new MemoryBlock(Configuration.MemoryConfiguration.ToDramSize(), memoryAllocationFlags);
|
Memory = new MemoryBlock(Configuration.MemoryConfiguration.ToDramSize(), memoryAllocationFlags);
|
||||||
Gpu = new GpuContext(Configuration.GpuRenderer, DirtyHacks);
|
Gpu = new GpuContext(Configuration.GpuRenderer, DirtyHacks, Configuration.OverlayManager);
|
||||||
System = new HOS.Horizon(this);
|
System = new HOS.Horizon(this);
|
||||||
Statistics = new PerformanceStatistics(this);
|
Statistics = new PerformanceStatistics(this);
|
||||||
Hid = new Hid(this, System.HidStorage);
|
Hid = new Hid(this, System.HidStorage);
|
||||||
|
@ -372,6 +372,12 @@
|
|||||||
<Setter Property="BorderThickness"
|
<Setter Property="BorderThickness"
|
||||||
Value="2"/>
|
Value="2"/>
|
||||||
</Style>
|
</Style>
|
||||||
|
<Style Selector="Border.listbox-item-style">
|
||||||
|
<Setter Property="Padding" Value="10" />
|
||||||
|
<Setter Property="Margin" Value="5,0,5,0" />
|
||||||
|
<Setter Property="CornerRadius" Value="5" />
|
||||||
|
<Setter Property="Background" Value="{DynamicResource AppListBackgroundColor}" />
|
||||||
|
</Style>
|
||||||
<Style Selector="ListBox ListBoxItem:selected /template/ ContentPresenter">
|
<Style Selector="ListBox ListBoxItem:selected /template/ ContentPresenter">
|
||||||
<Setter Property="Background"
|
<Setter Property="Background"
|
||||||
Value="{DynamicResource AppListBackgroundColor}" />
|
Value="{DynamicResource AppListBackgroundColor}" />
|
||||||
|
@ -15,5 +15,14 @@ namespace Ryujinx.Ava.Common
|
|||||||
CustomVSyncIntervalIncrement,
|
CustomVSyncIntervalIncrement,
|
||||||
CustomVSyncIntervalDecrement,
|
CustomVSyncIntervalDecrement,
|
||||||
TurboMode,
|
TurboMode,
|
||||||
|
CycleInputDevicePlayer1,
|
||||||
|
CycleInputDevicePlayer2,
|
||||||
|
CycleInputDevicePlayer3,
|
||||||
|
CycleInputDevicePlayer4,
|
||||||
|
CycleInputDevicePlayer5,
|
||||||
|
CycleInputDevicePlayer6,
|
||||||
|
CycleInputDevicePlayer7,
|
||||||
|
CycleInputDevicePlayer8,
|
||||||
|
CycleInputDeviceHandheld,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
using Avalonia.Data.Converters;
|
||||||
using Avalonia.Markup.Xaml.MarkupExtensions;
|
using Avalonia.Markup.Xaml.MarkupExtensions;
|
||||||
using Projektanker.Icons.Avalonia;
|
using Projektanker.Icons.Avalonia;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
@ -18,11 +19,19 @@ namespace Ryujinx.Ava.Common.Markup
|
|||||||
|
|
||||||
internal class LocaleExtension(LocaleKeys key) : BasicMarkupExtension<string>
|
internal class LocaleExtension(LocaleKeys key) : BasicMarkupExtension<string>
|
||||||
{
|
{
|
||||||
|
public IValueConverter Converter { get; set; }
|
||||||
|
|
||||||
public override string Name => "Translation";
|
public override string Name => "Translation";
|
||||||
protected override string Value => LocaleManager.Instance[key];
|
protected override string Value => LocaleManager.Instance[key];
|
||||||
|
|
||||||
protected override void ConfigureBindingExtension(CompiledBindingExtension bindingExtension)
|
protected override void ConfigureBindingExtension(CompiledBindingExtension bindingExtension)
|
||||||
=> bindingExtension.Source = LocaleManager.Instance;
|
{
|
||||||
|
bindingExtension.Source = LocaleManager.Instance;
|
||||||
|
if (Converter != null)
|
||||||
|
{
|
||||||
|
bindingExtension.Converter = Converter;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class WindowTitleExtension(LocaleKeys key, bool includeVersion) : BasicMarkupExtension<string>
|
internal class WindowTitleExtension(LocaleKeys key, bool includeVersion) : BasicMarkupExtension<string>
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
namespace Ryujinx.Ava.Common.Models.Github
|
|
||||||
{
|
|
||||||
public class GithubReleaseAssetJsonResponse
|
|
||||||
{
|
|
||||||
public string Name { get; set; }
|
|
||||||
public string State { get; set; }
|
|
||||||
public string BrowserDownloadUrl { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Ryujinx.Ava.Common.Models.Github
|
|
||||||
{
|
|
||||||
public class GithubReleasesJsonResponse
|
|
||||||
{
|
|
||||||
public string Name { get; set; }
|
|
||||||
|
|
||||||
public string TagName { get; set; }
|
|
||||||
public List<GithubReleaseAssetJsonResponse> Assets { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
using System.Text.Json.Serialization;
|
|
||||||
|
|
||||||
namespace Ryujinx.Ava.Common.Models.Github
|
|
||||||
{
|
|
||||||
[JsonSerializable(typeof(GithubReleasesJsonResponse), GenerationMode = JsonSourceGenerationMode.Metadata)]
|
|
||||||
public partial class GithubReleasesJsonSerializerContext : JsonSerializerContext;
|
|
||||||
}
|
|
@ -17,6 +17,7 @@ using Ryujinx.Graphics.OpenGL;
|
|||||||
using Ryujinx.Graphics.Vulkan;
|
using Ryujinx.Graphics.Vulkan;
|
||||||
using Ryujinx.HLE;
|
using Ryujinx.HLE;
|
||||||
using Ryujinx.Input;
|
using Ryujinx.Input;
|
||||||
|
using Ryujinx.UI.Overlay;
|
||||||
using Silk.NET.Vulkan;
|
using Silk.NET.Vulkan;
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
@ -348,7 +349,8 @@ namespace Ryujinx.Headless
|
|||||||
_userChannelPersistence,
|
_userChannelPersistence,
|
||||||
renderer.TryMakeThreaded(options.BackendThreading),
|
renderer.TryMakeThreaded(options.BackendThreading),
|
||||||
new SDL2HardwareDeviceDriver(),
|
new SDL2HardwareDeviceDriver(),
|
||||||
window
|
window,
|
||||||
|
new OverlayManager()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,7 @@ namespace Ryujinx.Ava
|
|||||||
public static string Version { get; private set; }
|
public static string Version { get; private set; }
|
||||||
public static string ConfigurationPath { get; private set; }
|
public static string ConfigurationPath { get; private set; }
|
||||||
public static string GlobalConfigurationPath { get; private set; }
|
public static string GlobalConfigurationPath { get; private set; }
|
||||||
|
public static bool UseExtraConfig { get; set; }
|
||||||
public static bool PreviewerDetached { get; private set; }
|
public static bool PreviewerDetached { get; private set; }
|
||||||
public static bool UseHardwareAcceleration { get; private set; }
|
public static bool UseHardwareAcceleration { get; private set; }
|
||||||
public static string BackendThreadingArg { get; private set; }
|
public static string BackendThreadingArg { get; private set; }
|
||||||
@ -159,7 +160,8 @@ namespace Ryujinx.Ava
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetDirGameUserConfig(string gameId, bool rememberGlobalDir = false, bool changeFolderForGame = false)
|
|
||||||
|
public static string GetDirGameUserConfig(string gameId, bool changeFolderForGame = false)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(gameId))
|
if (string.IsNullOrEmpty(gameId))
|
||||||
{
|
{
|
||||||
@ -168,15 +170,10 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
string gameDir = Path.Combine(AppDataManager.GamesDirPath, gameId, ReleaseInformation.ConfigName);
|
string gameDir = Path.Combine(AppDataManager.GamesDirPath, gameId, ReleaseInformation.ConfigName);
|
||||||
|
|
||||||
// Should load with the game if there is a custom setting for the game
|
|
||||||
if (rememberGlobalDir)
|
|
||||||
{
|
|
||||||
GlobalConfigurationPath = ConfigurationPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (changeFolderForGame)
|
if (changeFolderForGame)
|
||||||
{
|
{
|
||||||
ConfigurationPath = gameDir;
|
ConfigurationPath = gameDir;
|
||||||
|
UseExtraConfig = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return gameDir;
|
return gameDir;
|
||||||
@ -184,8 +181,6 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
public static void ReloadConfig()
|
public static void ReloadConfig()
|
||||||
{
|
{
|
||||||
//It is necessary that when a user setting appears, the global setting remains available
|
|
||||||
GlobalConfigurationPath = null;
|
|
||||||
|
|
||||||
string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ReleaseInformation.ConfigName);
|
string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ReleaseInformation.ConfigName);
|
||||||
string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, ReleaseInformation.ConfigName);
|
string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, ReleaseInformation.ConfigName);
|
||||||
@ -225,6 +220,12 @@ namespace Ryujinx.Ava
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// When you first load the program, copy to remember the path for the global configuration
|
||||||
|
if (GlobalConfigurationPath == null)
|
||||||
|
{
|
||||||
|
GlobalConfigurationPath = ConfigurationPath;
|
||||||
|
}
|
||||||
|
|
||||||
UseHardwareAcceleration = ConfigurationState.Instance.EnableHardwareAcceleration;
|
UseHardwareAcceleration = ConfigurationState.Instance.EnableHardwareAcceleration;
|
||||||
|
|
||||||
// Check if graphics backend was overridden
|
// Check if graphics backend was overridden
|
||||||
|
@ -65,6 +65,8 @@
|
|||||||
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'" />
|
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'" />
|
||||||
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies.AllArch" />
|
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies.AllArch" />
|
||||||
<PackageReference Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'win-x64' AND '$(RuntimeIdentifier)' != 'win-arm64'" />
|
<PackageReference Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'win-x64' AND '$(RuntimeIdentifier)' != 'win-arm64'" />
|
||||||
|
<PackageReference Include="Ryujinx.UpdateClient" />
|
||||||
|
<PackageReference Include="Ryujinx.Systems.Update.Common" />
|
||||||
<PackageReference Include="securifybv.ShellLink" />
|
<PackageReference Include="securifybv.ShellLink" />
|
||||||
<PackageReference Include="Sep" />
|
<PackageReference Include="Sep" />
|
||||||
<PackageReference Include="Silk.NET.Vulkan" />
|
<PackageReference Include="Silk.NET.Vulkan" />
|
||||||
|
@ -33,6 +33,7 @@ using Ryujinx.Common.Utilities;
|
|||||||
using Ryujinx.Graphics.GAL;
|
using Ryujinx.Graphics.GAL;
|
||||||
using Ryujinx.Graphics.GAL.Multithreading;
|
using Ryujinx.Graphics.GAL.Multithreading;
|
||||||
using Ryujinx.Graphics.Gpu;
|
using Ryujinx.Graphics.Gpu;
|
||||||
|
using Ryujinx.UI.Overlay;
|
||||||
using Ryujinx.Graphics.OpenGL;
|
using Ryujinx.Graphics.OpenGL;
|
||||||
using Ryujinx.Graphics.Vulkan;
|
using Ryujinx.Graphics.Vulkan;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
@ -40,6 +41,11 @@ using Ryujinx.HLE.HOS;
|
|||||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||||
using Ryujinx.Input;
|
using Ryujinx.Input;
|
||||||
using Ryujinx.Input.HLE;
|
using Ryujinx.Input.HLE;
|
||||||
|
using Ryujinx.Common.Configuration.Hid;
|
||||||
|
using Ryujinx.Common.Configuration.Hid.Controller;
|
||||||
|
using Ryujinx.Common.Configuration.Hid.Controller.Motion;
|
||||||
|
using Ryujinx.Common.Configuration.Hid.Keyboard;
|
||||||
|
using System.Linq;
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
using SPB.Graphics.Vulkan;
|
using SPB.Graphics.Vulkan;
|
||||||
using System;
|
using System;
|
||||||
@ -75,6 +81,7 @@ namespace Ryujinx.Ava.Systems
|
|||||||
|
|
||||||
private readonly long _ticksPerFrame;
|
private readonly long _ticksPerFrame;
|
||||||
private readonly Stopwatch _chrono;
|
private readonly Stopwatch _chrono;
|
||||||
|
private readonly Stopwatch _playTimer;
|
||||||
private long _ticks;
|
private long _ticks;
|
||||||
|
|
||||||
private readonly AccountManager _accountManager;
|
private readonly AccountManager _accountManager;
|
||||||
@ -123,6 +130,7 @@ namespace Ryujinx.Ava.Systems
|
|||||||
private readonly bool _isFirmwareTitle;
|
private readonly bool _isFirmwareTitle;
|
||||||
|
|
||||||
private readonly Lock _lockObject = new();
|
private readonly Lock _lockObject = new();
|
||||||
|
private ControllerOverlay _controllerOverlay;
|
||||||
|
|
||||||
public event EventHandler AppExit;
|
public event EventHandler AppExit;
|
||||||
public event EventHandler<StatusUpdatedEventArgs> StatusUpdatedEvent;
|
public event EventHandler<StatusUpdatedEventArgs> StatusUpdatedEvent;
|
||||||
@ -175,6 +183,7 @@ namespace Ryujinx.Ava.Systems
|
|||||||
|
|
||||||
_chrono = new Stopwatch();
|
_chrono = new Stopwatch();
|
||||||
_ticksPerFrame = Stopwatch.Frequency / TargetFps;
|
_ticksPerFrame = Stopwatch.Frequency / TargetFps;
|
||||||
|
_playTimer = new Stopwatch();
|
||||||
|
|
||||||
if (ApplicationPath.StartsWith("@SystemContent"))
|
if (ApplicationPath.StartsWith("@SystemContent"))
|
||||||
{
|
{
|
||||||
@ -461,7 +470,15 @@ namespace Ryujinx.Ava.Systems
|
|||||||
|
|
||||||
DisplaySleep.Prevent();
|
DisplaySleep.Prevent();
|
||||||
|
|
||||||
NpadManager.Initialize(Device, ConfigurationState.Instance.Hid.InputConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse);
|
if (ConfigurationState.Instance.System.UseInputGlobalConfig.Value && Program.UseExtraConfig)
|
||||||
|
{
|
||||||
|
NpadManager.Initialize(Device, ConfigurationState.InstanceExtra.Hid.InputConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
NpadManager.Initialize(Device, ConfigurationState.Instance.Hid.InputConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse);
|
||||||
|
}
|
||||||
|
|
||||||
TouchScreenManager.Initialize(Device);
|
TouchScreenManager.Initialize(Device);
|
||||||
|
|
||||||
_viewModel.IsGameRunning = true;
|
_viewModel.IsGameRunning = true;
|
||||||
@ -557,6 +574,7 @@ namespace Ryujinx.Ava.Systems
|
|||||||
public void Stop()
|
public void Stop()
|
||||||
{
|
{
|
||||||
_isActive = false;
|
_isActive = false;
|
||||||
|
_playTimer.Stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Exit()
|
private void Exit()
|
||||||
@ -608,7 +626,7 @@ namespace Ryujinx.Ava.Systems
|
|||||||
private void Dispose()
|
private void Dispose()
|
||||||
{
|
{
|
||||||
if (Device.Processes != null)
|
if (Device.Processes != null)
|
||||||
MainWindowViewModel.UpdateGameMetadata(Device.Processes.ActiveApplication.ProgramIdText);
|
MainWindowViewModel.UpdateGameMetadata(Device.Processes.ActiveApplication.ProgramIdText, _playTimer.Elapsed);
|
||||||
|
|
||||||
ConfigurationState.Instance.System.IgnoreMissingServices.Event -= UpdateIgnoreMissingServicesState;
|
ConfigurationState.Instance.System.IgnoreMissingServices.Event -= UpdateIgnoreMissingServicesState;
|
||||||
ConfigurationState.Instance.Graphics.AspectRatio.Event -= UpdateAspectRatioState;
|
ConfigurationState.Instance.Graphics.AspectRatio.Event -= UpdateAspectRatioState;
|
||||||
@ -627,6 +645,7 @@ namespace Ryujinx.Ava.Systems
|
|||||||
_gpuCancellationTokenSource.Dispose();
|
_gpuCancellationTokenSource.Dispose();
|
||||||
|
|
||||||
_chrono.Stop();
|
_chrono.Stop();
|
||||||
|
_playTimer.Stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DisposeGpu()
|
public void DisposeGpu()
|
||||||
@ -860,6 +879,7 @@ namespace Ryujinx.Ava.Systems
|
|||||||
ApplicationLibrary.LoadAndSaveMetaData(Device.Processes.ActiveApplication.ProgramIdText,
|
ApplicationLibrary.LoadAndSaveMetaData(Device.Processes.ActiveApplication.ProgramIdText,
|
||||||
appMetadata => appMetadata.UpdatePreGame()
|
appMetadata => appMetadata.UpdatePreGame()
|
||||||
);
|
);
|
||||||
|
_playTimer.Start();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -869,6 +889,7 @@ namespace Ryujinx.Ava.Systems
|
|||||||
Device?.System.TogglePauseEmulation(false);
|
Device?.System.TogglePauseEmulation(false);
|
||||||
|
|
||||||
_viewModel.IsPaused = false;
|
_viewModel.IsPaused = false;
|
||||||
|
_playTimer.Start();
|
||||||
_viewModel.Title = TitleHelper.ActiveApplicationTitle(Device?.Processes.ActiveApplication, Program.Version, !ConfigurationState.Instance.ShowOldUI);
|
_viewModel.Title = TitleHelper.ActiveApplicationTitle(Device?.Processes.ActiveApplication, Program.Version, !ConfigurationState.Instance.ShowOldUI);
|
||||||
Logger.Info?.Print(LogClass.Emulation, "Emulation was resumed");
|
Logger.Info?.Print(LogClass.Emulation, "Emulation was resumed");
|
||||||
}
|
}
|
||||||
@ -878,6 +899,7 @@ namespace Ryujinx.Ava.Systems
|
|||||||
Device?.System.TogglePauseEmulation(true);
|
Device?.System.TogglePauseEmulation(true);
|
||||||
|
|
||||||
_viewModel.IsPaused = true;
|
_viewModel.IsPaused = true;
|
||||||
|
_playTimer.Stop();
|
||||||
_viewModel.Title = TitleHelper.ActiveApplicationTitle(Device?.Processes.ActiveApplication, Program.Version, !ConfigurationState.Instance.ShowOldUI, LocaleManager.Instance[LocaleKeys.Paused]);
|
_viewModel.Title = TitleHelper.ActiveApplicationTitle(Device?.Processes.ActiveApplication, Program.Version, !ConfigurationState.Instance.ShowOldUI, LocaleManager.Instance[LocaleKeys.Paused]);
|
||||||
Logger.Info?.Print(LogClass.Emulation, "Emulation was paused");
|
Logger.Info?.Print(LogClass.Emulation, "Emulation was paused");
|
||||||
}
|
}
|
||||||
@ -909,9 +931,13 @@ namespace Ryujinx.Ava.Systems
|
|||||||
_userChannelPersistence,
|
_userChannelPersistence,
|
||||||
renderer.TryMakeThreaded(ConfigurationState.Instance.Graphics.BackendThreading),
|
renderer.TryMakeThreaded(ConfigurationState.Instance.Graphics.BackendThreading),
|
||||||
InitializeAudio(),
|
InitializeAudio(),
|
||||||
_viewModel.UiHandler
|
_viewModel.UiHandler,
|
||||||
|
new OverlayManager()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
_controllerOverlay = new ControllerOverlay();
|
||||||
|
Device.Gpu.Window.AddOverlay(_controllerOverlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IHardwareDeviceDriver InitializeAudio()
|
private static IHardwareDeviceDriver InitializeAudio()
|
||||||
@ -1144,6 +1170,24 @@ namespace Ryujinx.Ava.Systems
|
|||||||
|
|
||||||
_dialogShown = true;
|
_dialogShown = true;
|
||||||
|
|
||||||
|
// The hard-coded hotkey mapped to exit is Escape, but it's also the same key
|
||||||
|
// that causes the dialog we launch to close (without doing anything). In release
|
||||||
|
// mode, a race is observed that between ShowExitPrompt() appearing on KeyDown
|
||||||
|
// and the ContentDialog we create seeing the key state before KeyUp. Merely waiting
|
||||||
|
// for the key to no longer be pressed appears to be insufficient.
|
||||||
|
// NB: Using _keyboardInterface.IsPressed(Key.Escape) does not currently work.
|
||||||
|
if (OperatingSystem.IsWindows())
|
||||||
|
{
|
||||||
|
while (GetAsyncKeyState(0x1B) != 0)
|
||||||
|
{
|
||||||
|
await Task.Delay(100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await Task.Delay(250);
|
||||||
|
}
|
||||||
|
|
||||||
shouldExit = await ContentDialogHelper.CreateStopEmulationDialog();
|
shouldExit = await ContentDialogHelper.CreateStopEmulationDialog();
|
||||||
|
|
||||||
_dialogShown = false;
|
_dialogShown = false;
|
||||||
@ -1300,6 +1344,33 @@ namespace Ryujinx.Ava.Systems
|
|||||||
|
|
||||||
_viewModel.Volume = Device.GetVolume();
|
_viewModel.Volume = Device.GetVolume();
|
||||||
break;
|
break;
|
||||||
|
case KeyboardHotkeyState.CycleInputDevicePlayer1:
|
||||||
|
CycleInputDevice(HLE.HOS.Services.Hid.PlayerIndex.Player1);
|
||||||
|
break;
|
||||||
|
case KeyboardHotkeyState.CycleInputDevicePlayer2:
|
||||||
|
CycleInputDevice(HLE.HOS.Services.Hid.PlayerIndex.Player2);
|
||||||
|
break;
|
||||||
|
case KeyboardHotkeyState.CycleInputDevicePlayer3:
|
||||||
|
CycleInputDevice(HLE.HOS.Services.Hid.PlayerIndex.Player3);
|
||||||
|
break;
|
||||||
|
case KeyboardHotkeyState.CycleInputDevicePlayer4:
|
||||||
|
CycleInputDevice(HLE.HOS.Services.Hid.PlayerIndex.Player4);
|
||||||
|
break;
|
||||||
|
case KeyboardHotkeyState.CycleInputDevicePlayer5:
|
||||||
|
CycleInputDevice(HLE.HOS.Services.Hid.PlayerIndex.Player5);
|
||||||
|
break;
|
||||||
|
case KeyboardHotkeyState.CycleInputDevicePlayer6:
|
||||||
|
CycleInputDevice(HLE.HOS.Services.Hid.PlayerIndex.Player6);
|
||||||
|
break;
|
||||||
|
case KeyboardHotkeyState.CycleInputDevicePlayer7:
|
||||||
|
CycleInputDevice(HLE.HOS.Services.Hid.PlayerIndex.Player7);
|
||||||
|
break;
|
||||||
|
case KeyboardHotkeyState.CycleInputDevicePlayer8:
|
||||||
|
CycleInputDevice(HLE.HOS.Services.Hid.PlayerIndex.Player8);
|
||||||
|
break;
|
||||||
|
case KeyboardHotkeyState.CycleInputDeviceHandheld:
|
||||||
|
CycleInputDevice(HLE.HOS.Services.Hid.PlayerIndex.Handheld);
|
||||||
|
break;
|
||||||
case KeyboardHotkeyState.None:
|
case KeyboardHotkeyState.None:
|
||||||
(_keyboardInterface as AvaloniaKeyboard).Clear();
|
(_keyboardInterface as AvaloniaKeyboard).Clear();
|
||||||
break;
|
break;
|
||||||
@ -1333,6 +1404,118 @@ namespace Ryujinx.Ava.Systems
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void CycleInputDevice(HLE.HOS.Services.Hid.PlayerIndex playerIndex)
|
||||||
|
{
|
||||||
|
// Get current input configuration
|
||||||
|
List<InputConfig> currentConfig = new(ConfigurationState.Instance.Hid.InputConfig.Value);
|
||||||
|
|
||||||
|
// Find current configuration for this player
|
||||||
|
InputConfig playerConfig = currentConfig.FirstOrDefault(x => x.PlayerIndex == (PlayerIndex)playerIndex);
|
||||||
|
|
||||||
|
// Get available devices from InputManager
|
||||||
|
List<(DeviceType Type, string Id, string Name)> availableDevices = [];
|
||||||
|
|
||||||
|
// Add disabled option
|
||||||
|
availableDevices.Add((DeviceType.None, "disabled", "Disabled"));
|
||||||
|
|
||||||
|
// Add keyboard devices
|
||||||
|
foreach (string id in _inputManager.KeyboardDriver.GamepadsIds)
|
||||||
|
{
|
||||||
|
using var gamepad = _inputManager.KeyboardDriver.GetGamepad(id);
|
||||||
|
if (gamepad != null)
|
||||||
|
{
|
||||||
|
availableDevices.Add((DeviceType.Keyboard, id, gamepad.Name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add controller devices
|
||||||
|
int controllerNumber = 0;
|
||||||
|
foreach (string id in _inputManager.GamepadDriver.GamepadsIds)
|
||||||
|
{
|
||||||
|
using var gamepad = _inputManager.GamepadDriver.GetGamepad(id);
|
||||||
|
if (gamepad != null)
|
||||||
|
{
|
||||||
|
string name = $"{DefaultInputConfigurationProvider.GetShortGamepadName(gamepad.Name)} ({controllerNumber++})";
|
||||||
|
availableDevices.Add((DeviceType.Controller, id, name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find current device index
|
||||||
|
int currentIndex = 0;
|
||||||
|
if (playerConfig != null)
|
||||||
|
{
|
||||||
|
DeviceType currentType = DeviceType.None;
|
||||||
|
if (playerConfig is StandardKeyboardInputConfig)
|
||||||
|
currentType = DeviceType.Keyboard;
|
||||||
|
else if (playerConfig is StandardControllerInputConfig)
|
||||||
|
currentType = DeviceType.Controller;
|
||||||
|
|
||||||
|
currentIndex = availableDevices.FindIndex(x => x.Type == currentType && x.Id == playerConfig.Id);
|
||||||
|
if (currentIndex == -1) currentIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cycle to next device
|
||||||
|
int nextIndex = (currentIndex + 1) % availableDevices.Count;
|
||||||
|
var nextDevice = availableDevices[nextIndex];
|
||||||
|
|
||||||
|
// Remove existing configuration for this player
|
||||||
|
currentConfig.RemoveAll(x => x.PlayerIndex == (PlayerIndex)playerIndex);
|
||||||
|
|
||||||
|
// Add new configuration if not disabled
|
||||||
|
if (nextDevice.Type != DeviceType.None)
|
||||||
|
{
|
||||||
|
InputConfig newConfig = CreateDefaultInputConfig(nextDevice, (PlayerIndex)playerIndex);
|
||||||
|
if (newConfig != null)
|
||||||
|
{
|
||||||
|
currentConfig.Add(newConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply the new configuration
|
||||||
|
ConfigurationState.Instance.Hid.InputConfig.Value = currentConfig;
|
||||||
|
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
|
||||||
|
|
||||||
|
// Reload the input system
|
||||||
|
NpadManager.ReloadConfiguration(currentConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse);
|
||||||
|
|
||||||
|
// Show controller overlay
|
||||||
|
ShowControllerOverlay(currentConfig, ConfigurationState.Instance.ControllerOverlayInputCycleDuration.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private InputConfig CreateDefaultInputConfig((DeviceType Type, string Id, string Name) device, PlayerIndex playerIndex)
|
||||||
|
{
|
||||||
|
if (device.Type == DeviceType.Keyboard)
|
||||||
|
{
|
||||||
|
return DefaultInputConfigurationProvider.CreateDefaultKeyboardConfig(device.Id, device.Name, playerIndex);
|
||||||
|
}
|
||||||
|
else if (device.Type == DeviceType.Controller)
|
||||||
|
{
|
||||||
|
bool isNintendoStyle = DefaultInputConfigurationProvider.IsNintendoStyleController(device.Name);
|
||||||
|
return DefaultInputConfigurationProvider.CreateDefaultControllerConfig(device.Id, device.Name, playerIndex, isNintendoStyle);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ShowControllerOverlay(List<InputConfig> inputConfigs, int duration)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_controllerOverlay != null)
|
||||||
|
{
|
||||||
|
_controllerOverlay.ShowControllerBindings(inputConfigs, duration);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.Application, "AppHost: Cannot show overlay - ControllerOverlay is null");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.Error?.Print(LogClass.Application, $"Failed to show controller overlay: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private KeyboardHotkeyState GetHotkeyState()
|
private KeyboardHotkeyState GetHotkeyState()
|
||||||
{
|
{
|
||||||
KeyboardHotkeyState state = KeyboardHotkeyState.None;
|
KeyboardHotkeyState state = KeyboardHotkeyState.None;
|
||||||
@ -1385,6 +1568,42 @@ namespace Ryujinx.Ava.Systems
|
|||||||
{
|
{
|
||||||
state = KeyboardHotkeyState.TurboMode;
|
state = KeyboardHotkeyState.TurboMode;
|
||||||
}
|
}
|
||||||
|
else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.CycleInputDevicePlayer1))
|
||||||
|
{
|
||||||
|
state = KeyboardHotkeyState.CycleInputDevicePlayer1;
|
||||||
|
}
|
||||||
|
else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.CycleInputDevicePlayer2))
|
||||||
|
{
|
||||||
|
state = KeyboardHotkeyState.CycleInputDevicePlayer2;
|
||||||
|
}
|
||||||
|
else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.CycleInputDevicePlayer3))
|
||||||
|
{
|
||||||
|
state = KeyboardHotkeyState.CycleInputDevicePlayer3;
|
||||||
|
}
|
||||||
|
else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.CycleInputDevicePlayer4))
|
||||||
|
{
|
||||||
|
state = KeyboardHotkeyState.CycleInputDevicePlayer4;
|
||||||
|
}
|
||||||
|
else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.CycleInputDevicePlayer5))
|
||||||
|
{
|
||||||
|
state = KeyboardHotkeyState.CycleInputDevicePlayer5;
|
||||||
|
}
|
||||||
|
else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.CycleInputDevicePlayer6))
|
||||||
|
{
|
||||||
|
state = KeyboardHotkeyState.CycleInputDevicePlayer6;
|
||||||
|
}
|
||||||
|
else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.CycleInputDevicePlayer7))
|
||||||
|
{
|
||||||
|
state = KeyboardHotkeyState.CycleInputDevicePlayer7;
|
||||||
|
}
|
||||||
|
else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.CycleInputDevicePlayer8))
|
||||||
|
{
|
||||||
|
state = KeyboardHotkeyState.CycleInputDevicePlayer8;
|
||||||
|
}
|
||||||
|
else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.CycleInputDeviceHandheld))
|
||||||
|
{
|
||||||
|
state = KeyboardHotkeyState.CycleInputDeviceHandheld;
|
||||||
|
}
|
||||||
|
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
@ -556,7 +556,7 @@ namespace Ryujinx.Ava.Systems.AppLibrary
|
|||||||
data.Favorite = appMetadata.Favorite;
|
data.Favorite = appMetadata.Favorite;
|
||||||
data.TimePlayed = appMetadata.TimePlayed;
|
data.TimePlayed = appMetadata.TimePlayed;
|
||||||
data.LastPlayed = appMetadata.LastPlayed;
|
data.LastPlayed = appMetadata.LastPlayed;
|
||||||
data.HasIndependentConfiguration = File.Exists(Program.GetDirGameUserConfig(data.IdBaseString, false, false)); // Just check user config
|
data.HasIndependentConfiguration = File.Exists(Program.GetDirGameUserConfig(data.IdBaseString)); // Just check user config
|
||||||
}
|
}
|
||||||
|
|
||||||
data.FileExtension = Path.GetExtension(applicationPath).TrimStart('.').ToUpper();
|
data.FileExtension = Path.GetExtension(applicationPath).TrimStart('.').ToUpper();
|
||||||
|
@ -33,19 +33,11 @@ namespace Ryujinx.Ava.Systems.AppLibrary
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates <see cref="LastPlayed"/> and <see cref="TimePlayed"/>. Call this after a game ends.
|
/// Updates <see cref="LastPlayed"/> and <see cref="TimePlayed"/>. Call this after a game ends.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void UpdatePostGame()
|
/// <param name="playTime">The active gameplay time this past session.</param>
|
||||||
|
public void UpdatePostGame(TimeSpan playTime)
|
||||||
{
|
{
|
||||||
DateTime? prevLastPlayed = LastPlayed;
|
|
||||||
UpdatePreGame();
|
UpdatePreGame();
|
||||||
|
TimePlayed += playTime;
|
||||||
if (!prevLastPlayed.HasValue)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
TimeSpan diff = DateTime.UtcNow - prevLastPlayed.Value;
|
|
||||||
double newTotalSeconds = TimePlayed.Add(diff).TotalSeconds;
|
|
||||||
TimePlayed = TimeSpan.FromSeconds(Math.Round(newTotalSeconds, MidpointRounding.AwayFromZero));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ namespace Ryujinx.Ava.Systems.Configuration
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The current version of the file format
|
/// The current version of the file format
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const int CurrentVersion = 69;
|
public const int CurrentVersion = 72;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Version of the configuration file format
|
/// Version of the configuration file format
|
||||||
@ -152,6 +152,11 @@ namespace Ryujinx.Ava.Systems.Configuration
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool MatchSystemTime { get; set; }
|
public bool MatchSystemTime { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enable or disable use global input config (Independent from controllers binding)
|
||||||
|
/// </summary>
|
||||||
|
public bool UseInputGlobalConfig { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Enables or disables Docked Mode
|
/// Enables or disables Docked Mode
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -212,6 +217,16 @@ namespace Ryujinx.Ava.Systems.Configuration
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public HideCursorMode HideCursor { get; set; }
|
public HideCursorMode HideCursor { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Duration to show controller overlay when game starts (seconds, 0 = disabled)
|
||||||
|
/// </summary>
|
||||||
|
public int ControllerOverlayGameStartDuration { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Duration to show controller overlay when input is cycled (seconds, 0 = disabled)
|
||||||
|
/// </summary>
|
||||||
|
public int ControllerOverlayInputCycleDuration { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Enables or disables Vertical Sync
|
/// Enables or disables Vertical Sync
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -53,6 +53,8 @@ namespace Ryujinx.Ava.Systems.Configuration
|
|||||||
ShowOldUI.Value = shouldLoadFromFile ? cff.ShowTitleBar : ShowOldUI.Value; // Get from global config only
|
ShowOldUI.Value = shouldLoadFromFile ? cff.ShowTitleBar : ShowOldUI.Value; // Get from global config only
|
||||||
EnableHardwareAcceleration.Value = shouldLoadFromFile ? cff.EnableHardwareAcceleration : EnableHardwareAcceleration.Value; // Get from global config only
|
EnableHardwareAcceleration.Value = shouldLoadFromFile ? cff.EnableHardwareAcceleration : EnableHardwareAcceleration.Value; // Get from global config only
|
||||||
HideCursor.Value = cff.HideCursor;
|
HideCursor.Value = cff.HideCursor;
|
||||||
|
ControllerOverlayGameStartDuration.Value = cff.ControllerOverlayGameStartDuration;
|
||||||
|
ControllerOverlayInputCycleDuration.Value = cff.ControllerOverlayInputCycleDuration;
|
||||||
|
|
||||||
Logger.EnableFileLog.Value = cff.EnableFileLog;
|
Logger.EnableFileLog.Value = cff.EnableFileLog;
|
||||||
Logger.EnableDebug.Value = cff.LoggingEnableDebug;
|
Logger.EnableDebug.Value = cff.LoggingEnableDebug;
|
||||||
@ -90,6 +92,7 @@ namespace Ryujinx.Ava.Systems.Configuration
|
|||||||
System.TimeZone.Value = cff.SystemTimeZone;
|
System.TimeZone.Value = cff.SystemTimeZone;
|
||||||
System.SystemTimeOffset.Value = shouldLoadFromFile ? cff.SystemTimeOffset : System.SystemTimeOffset.Value; // Get from global config only
|
System.SystemTimeOffset.Value = shouldLoadFromFile ? cff.SystemTimeOffset : System.SystemTimeOffset.Value; // Get from global config only
|
||||||
System.MatchSystemTime.Value = shouldLoadFromFile ? cff.MatchSystemTime : System.MatchSystemTime.Value; // Get from global config only
|
System.MatchSystemTime.Value = shouldLoadFromFile ? cff.MatchSystemTime : System.MatchSystemTime.Value; // Get from global config only
|
||||||
|
System.UseInputGlobalConfig.Value = cff.UseInputGlobalConfig;
|
||||||
System.EnableDockedMode.Value = cff.DockedMode;
|
System.EnableDockedMode.Value = cff.DockedMode;
|
||||||
System.EnablePtc.Value = cff.EnablePtc;
|
System.EnablePtc.Value = cff.EnablePtc;
|
||||||
System.EnableLowPowerPtc.Value = cff.EnableLowPowerPtc;
|
System.EnableLowPowerPtc.Value = cff.EnableLowPowerPtc;
|
||||||
@ -146,7 +149,7 @@ namespace Ryujinx.Ava.Systems.Configuration
|
|||||||
Hid.EnableMouse.Value = cff.EnableMouse;
|
Hid.EnableMouse.Value = cff.EnableMouse;
|
||||||
Hid.DisableInputWhenOutOfFocus.Value = shouldLoadFromFile ? cff.DisableInputWhenOutOfFocus : Hid.DisableInputWhenOutOfFocus.Value; // Get from global config only
|
Hid.DisableInputWhenOutOfFocus.Value = shouldLoadFromFile ? cff.DisableInputWhenOutOfFocus : Hid.DisableInputWhenOutOfFocus.Value; // Get from global config only
|
||||||
Hid.Hotkeys.Value = shouldLoadFromFile ? cff.Hotkeys : Hid.Hotkeys.Value; // Get from global config only
|
Hid.Hotkeys.Value = shouldLoadFromFile ? cff.Hotkeys : Hid.Hotkeys.Value; // Get from global config only
|
||||||
Hid.InputConfig.Value = cff.InputConfig ?? [];
|
Hid.InputConfig.Value = cff.InputConfig ?? [] ;
|
||||||
Hid.RainbowSpeed.Value = cff.RainbowSpeed;
|
Hid.RainbowSpeed.Value = cff.RainbowSpeed;
|
||||||
|
|
||||||
Multiplayer.LanInterfaceId.Value = cff.MultiplayerLanInterfaceId;
|
Multiplayer.LanInterfaceId.Value = cff.MultiplayerLanInterfaceId;
|
||||||
@ -478,7 +481,40 @@ namespace Ryujinx.Ava.Systems.Configuration
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
(69, static cff => cff.SkipUserProfiles = false)
|
(69, static cff => cff.SkipUserProfiles = false),
|
||||||
|
(71, static cff =>
|
||||||
|
{
|
||||||
|
cff.ControllerOverlayGameStartDuration = 3;
|
||||||
|
cff.ControllerOverlayInputCycleDuration = 2;
|
||||||
|
}),
|
||||||
|
(72, static cff =>
|
||||||
|
{
|
||||||
|
cff.Hotkeys = new KeyboardHotkeys
|
||||||
|
{
|
||||||
|
ToggleVSyncMode = cff.Hotkeys.ToggleVSyncMode,
|
||||||
|
Screenshot = cff.Hotkeys.Screenshot,
|
||||||
|
ShowUI = cff.Hotkeys.ShowUI,
|
||||||
|
Pause = cff.Hotkeys.Pause,
|
||||||
|
ToggleMute = cff.Hotkeys.ToggleMute,
|
||||||
|
ResScaleUp = cff.Hotkeys.ResScaleUp,
|
||||||
|
ResScaleDown = cff.Hotkeys.ResScaleDown,
|
||||||
|
VolumeUp = cff.Hotkeys.VolumeUp,
|
||||||
|
VolumeDown = cff.Hotkeys.VolumeDown,
|
||||||
|
CustomVSyncIntervalIncrement = cff.Hotkeys.CustomVSyncIntervalIncrement,
|
||||||
|
CustomVSyncIntervalDecrement = cff.Hotkeys.CustomVSyncIntervalDecrement,
|
||||||
|
TurboMode = cff.Hotkeys.TurboMode,
|
||||||
|
TurboModeWhileHeld = cff.Hotkeys.TurboModeWhileHeld,
|
||||||
|
CycleInputDevicePlayer1 = Key.Unbound,
|
||||||
|
CycleInputDevicePlayer2 = Key.Unbound,
|
||||||
|
CycleInputDevicePlayer3 = Key.Unbound,
|
||||||
|
CycleInputDevicePlayer4 = Key.Unbound,
|
||||||
|
CycleInputDevicePlayer5 = Key.Unbound,
|
||||||
|
CycleInputDevicePlayer6 = Key.Unbound,
|
||||||
|
CycleInputDevicePlayer7 = Key.Unbound,
|
||||||
|
CycleInputDevicePlayer8 = Key.Unbound,
|
||||||
|
CycleInputDeviceHandheld = Key.Unbound
|
||||||
|
};
|
||||||
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -326,6 +326,12 @@ namespace Ryujinx.Ava.Systems.Configuration
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public ReactiveObject<bool> MatchSystemTime { get; private set; }
|
public ReactiveObject<bool> MatchSystemTime { get; private set; }
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enable or disable use global input config (Independent from controllers binding)
|
||||||
|
/// </summary>
|
||||||
|
public ReactiveObject<bool> UseInputGlobalConfig { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Enables or disables Docked Mode
|
/// Enables or disables Docked Mode
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -417,6 +423,8 @@ namespace Ryujinx.Ava.Systems.Configuration
|
|||||||
SystemTimeOffset.LogChangesToValue(nameof(SystemTimeOffset));
|
SystemTimeOffset.LogChangesToValue(nameof(SystemTimeOffset));
|
||||||
MatchSystemTime = new ReactiveObject<bool>();
|
MatchSystemTime = new ReactiveObject<bool>();
|
||||||
MatchSystemTime.LogChangesToValue(nameof(MatchSystemTime));
|
MatchSystemTime.LogChangesToValue(nameof(MatchSystemTime));
|
||||||
|
UseInputGlobalConfig = new ReactiveObject<bool>();
|
||||||
|
UseInputGlobalConfig.LogChangesToValue(nameof(UseInputGlobalConfig));
|
||||||
EnableDockedMode = new ReactiveObject<bool>();
|
EnableDockedMode = new ReactiveObject<bool>();
|
||||||
EnableDockedMode.LogChangesToValue(nameof(EnableDockedMode));
|
EnableDockedMode.LogChangesToValue(nameof(EnableDockedMode));
|
||||||
EnablePtc = new ReactiveObject<bool>();
|
EnablePtc = new ReactiveObject<bool>();
|
||||||
@ -761,6 +769,8 @@ namespace Ryujinx.Ava.Systems.Configuration
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static ConfigurationState Instance { get; private set; }
|
public static ConfigurationState Instance { get; private set; }
|
||||||
|
|
||||||
|
public static ConfigurationState InstanceExtra{ get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The UI section
|
/// The UI section
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -836,6 +846,16 @@ namespace Ryujinx.Ava.Systems.Configuration
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public ReactiveObject<HideCursorMode> HideCursor { get; private set; }
|
public ReactiveObject<HideCursorMode> HideCursor { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Duration to show controller overlay when game starts (seconds, 0 = disabled)
|
||||||
|
/// </summary>
|
||||||
|
public ReactiveObject<int> ControllerOverlayGameStartDuration { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Duration to show controller overlay when input is cycled (seconds, 0 = disabled)
|
||||||
|
/// </summary>
|
||||||
|
public ReactiveObject<int> ControllerOverlayInputCycleDuration { get; private set; }
|
||||||
|
|
||||||
private ConfigurationState()
|
private ConfigurationState()
|
||||||
{
|
{
|
||||||
UI = new UISection();
|
UI = new UISection();
|
||||||
@ -853,6 +873,8 @@ namespace Ryujinx.Ava.Systems.Configuration
|
|||||||
RememberWindowState = new ReactiveObject<bool>();
|
RememberWindowState = new ReactiveObject<bool>();
|
||||||
ShowOldUI = new ReactiveObject<bool>();
|
ShowOldUI = new ReactiveObject<bool>();
|
||||||
EnableHardwareAcceleration = new ReactiveObject<bool>();
|
EnableHardwareAcceleration = new ReactiveObject<bool>();
|
||||||
|
ControllerOverlayGameStartDuration = new ReactiveObject<int>();
|
||||||
|
ControllerOverlayInputCycleDuration = new ReactiveObject<int>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public HleConfiguration CreateHleConfiguration() =>
|
public HleConfiguration CreateHleConfiguration() =>
|
||||||
|
@ -15,12 +15,13 @@ namespace Ryujinx.Ava.Systems.Configuration
|
|||||||
{
|
{
|
||||||
public static void Initialize()
|
public static void Initialize()
|
||||||
{
|
{
|
||||||
if (Instance != null)
|
if (Instance != null || InstanceExtra!= null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Configuration is already initialized");
|
throw new InvalidOperationException("Configuration is already initialized");
|
||||||
}
|
}
|
||||||
|
|
||||||
Instance = new ConfigurationState();
|
Instance = new ConfigurationState();
|
||||||
|
InstanceExtra= new ConfigurationState();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ConfigurationFileFormat ToFileFormat()
|
public ConfigurationFileFormat ToFileFormat()
|
||||||
@ -54,6 +55,7 @@ namespace Ryujinx.Ava.Systems.Configuration
|
|||||||
SystemTimeZone = System.TimeZone,
|
SystemTimeZone = System.TimeZone,
|
||||||
SystemTimeOffset = System.SystemTimeOffset,
|
SystemTimeOffset = System.SystemTimeOffset,
|
||||||
MatchSystemTime = System.MatchSystemTime,
|
MatchSystemTime = System.MatchSystemTime,
|
||||||
|
UseInputGlobalConfig = System.UseInputGlobalConfig,
|
||||||
DockedMode = System.EnableDockedMode,
|
DockedMode = System.EnableDockedMode,
|
||||||
EnableDiscordIntegration = EnableDiscordIntegration,
|
EnableDiscordIntegration = EnableDiscordIntegration,
|
||||||
UpdateCheckerType = UpdateCheckerType,
|
UpdateCheckerType = UpdateCheckerType,
|
||||||
@ -63,6 +65,8 @@ namespace Ryujinx.Ava.Systems.Configuration
|
|||||||
ShowTitleBar = ShowOldUI,
|
ShowTitleBar = ShowOldUI,
|
||||||
EnableHardwareAcceleration = EnableHardwareAcceleration,
|
EnableHardwareAcceleration = EnableHardwareAcceleration,
|
||||||
HideCursor = HideCursor,
|
HideCursor = HideCursor,
|
||||||
|
ControllerOverlayGameStartDuration = ControllerOverlayGameStartDuration,
|
||||||
|
ControllerOverlayInputCycleDuration = ControllerOverlayInputCycleDuration,
|
||||||
VSyncMode = Graphics.VSyncMode,
|
VSyncMode = Graphics.VSyncMode,
|
||||||
EnableCustomVSyncInterval = Graphics.EnableCustomVSyncInterval,
|
EnableCustomVSyncInterval = Graphics.EnableCustomVSyncInterval,
|
||||||
CustomVSyncInterval = Graphics.CustomVSyncInterval,
|
CustomVSyncInterval = Graphics.CustomVSyncInterval,
|
||||||
@ -178,6 +182,7 @@ namespace Ryujinx.Ava.Systems.Configuration
|
|||||||
System.Region.Value = Region.USA;
|
System.Region.Value = Region.USA;
|
||||||
System.TimeZone.Value = "UTC";
|
System.TimeZone.Value = "UTC";
|
||||||
System.SystemTimeOffset.Value = 0;
|
System.SystemTimeOffset.Value = 0;
|
||||||
|
System.UseInputGlobalConfig.Value = false;
|
||||||
System.EnableDockedMode.Value = true;
|
System.EnableDockedMode.Value = true;
|
||||||
EnableDiscordIntegration.Value = true;
|
EnableDiscordIntegration.Value = true;
|
||||||
UpdateCheckerType.Value = UpdaterType.PromptAtStartup;
|
UpdateCheckerType.Value = UpdaterType.PromptAtStartup;
|
||||||
@ -187,6 +192,8 @@ namespace Ryujinx.Ava.Systems.Configuration
|
|||||||
ShowOldUI.Value = !OperatingSystem.IsWindows();
|
ShowOldUI.Value = !OperatingSystem.IsWindows();
|
||||||
EnableHardwareAcceleration.Value = true;
|
EnableHardwareAcceleration.Value = true;
|
||||||
HideCursor.Value = HideCursorMode.OnIdle;
|
HideCursor.Value = HideCursorMode.OnIdle;
|
||||||
|
ControllerOverlayGameStartDuration.Value = 3;
|
||||||
|
ControllerOverlayInputCycleDuration.Value = 2;
|
||||||
Graphics.VSyncMode.Value = VSyncMode.Switch;
|
Graphics.VSyncMode.Value = VSyncMode.Switch;
|
||||||
Graphics.CustomVSyncInterval.Value = 120;
|
Graphics.CustomVSyncInterval.Value = 120;
|
||||||
Graphics.EnableCustomVSyncInterval.Value = false;
|
Graphics.EnableCustomVSyncInterval.Value = false;
|
||||||
@ -266,7 +273,16 @@ namespace Ryujinx.Ava.Systems.Configuration
|
|||||||
CustomVSyncIntervalIncrement = Key.Unbound,
|
CustomVSyncIntervalIncrement = Key.Unbound,
|
||||||
CustomVSyncIntervalDecrement = Key.Unbound,
|
CustomVSyncIntervalDecrement = Key.Unbound,
|
||||||
TurboMode = Key.Unbound,
|
TurboMode = Key.Unbound,
|
||||||
TurboModeWhileHeld = false
|
TurboModeWhileHeld = false,
|
||||||
|
CycleInputDevicePlayer1 = Key.Unbound,
|
||||||
|
CycleInputDevicePlayer2 = Key.Unbound,
|
||||||
|
CycleInputDevicePlayer3 = Key.Unbound,
|
||||||
|
CycleInputDevicePlayer4 = Key.Unbound,
|
||||||
|
CycleInputDevicePlayer5 = Key.Unbound,
|
||||||
|
CycleInputDevicePlayer6 = Key.Unbound,
|
||||||
|
CycleInputDevicePlayer7 = Key.Unbound,
|
||||||
|
CycleInputDevicePlayer8 = Key.Unbound,
|
||||||
|
CycleInputDeviceHandheld = Key.Unbound
|
||||||
};
|
};
|
||||||
Hid.RainbowSpeed.Value = 1f;
|
Hid.RainbowSpeed.Value = 1f;
|
||||||
Hid.InputConfig.Value =
|
Hid.InputConfig.Value =
|
||||||
|
107
src/Ryujinx/Systems/Updater/Updater.GitLab.cs
Normal file
107
src/Ryujinx/Systems/Updater/Updater.GitLab.cs
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
using Gommon;
|
||||||
|
using Ryujinx.Ava.Common.Locale;
|
||||||
|
using Ryujinx.Ava.UI.Helpers;
|
||||||
|
using Ryujinx.Common;
|
||||||
|
using Ryujinx.Common.Helper;
|
||||||
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.Systems.Update.Client;
|
||||||
|
using Ryujinx.Systems.Update.Common;
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.Systems
|
||||||
|
{
|
||||||
|
internal static partial class Updater
|
||||||
|
{
|
||||||
|
private static VersionResponse _versionResponse;
|
||||||
|
|
||||||
|
private static UpdateClient CreateUpdateClient()
|
||||||
|
=> UpdateClient.Builder()
|
||||||
|
.WithServerEndpoint("https://update.ryujinx.app") // This is the default, and doesn't need to be provided; it's here for transparency.
|
||||||
|
.WithLogger((format, args, caller) =>
|
||||||
|
Logger.Info?.Print(
|
||||||
|
LogClass.Application,
|
||||||
|
args.Length is 0 ? format : format.Format(args),
|
||||||
|
caller: caller)
|
||||||
|
);
|
||||||
|
|
||||||
|
public static async Task<Optional<(Version Current, Version Incoming)>> CheckVersionAsync(bool showVersionUpToDate = false)
|
||||||
|
{
|
||||||
|
if (!Version.TryParse(Program.Version, out Version currentVersion))
|
||||||
|
{
|
||||||
|
Logger.Error?.Print(LogClass.Application,
|
||||||
|
$"Failed to convert the current {RyujinxApp.FullAppName} version!");
|
||||||
|
|
||||||
|
await ContentDialogHelper.CreateWarningDialog(
|
||||||
|
LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedMessage],
|
||||||
|
LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]);
|
||||||
|
|
||||||
|
_running = false;
|
||||||
|
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
using UpdateClient updateClient = CreateUpdateClient();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_versionResponse = await updateClient.QueryLatestAsync(ReleaseInformation.IsCanaryBuild
|
||||||
|
? ReleaseChannel.Canary
|
||||||
|
: ReleaseChannel.Stable);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.Error?.Print(LogClass.Application, $"An error occurred when requesting for updates ({e.GetType().AsFullNamePrettyString()}): {e.Message}");
|
||||||
|
|
||||||
|
_running = false;
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_versionResponse == null)
|
||||||
|
{
|
||||||
|
// logging is done via the UpdateClient library
|
||||||
|
_running = false;
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If build URL not found, assume no new update is available.
|
||||||
|
if (_versionResponse.ArtifactUrl is null or "")
|
||||||
|
{
|
||||||
|
if (showVersionUpToDate)
|
||||||
|
{
|
||||||
|
UserResult userResult = await ContentDialogHelper.CreateUpdaterUpToDateInfoDialog(
|
||||||
|
LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage],
|
||||||
|
string.Empty);
|
||||||
|
|
||||||
|
if (userResult is UserResult.Ok)
|
||||||
|
{
|
||||||
|
OpenHelper.OpenUrl(_versionResponse.ReleaseUrlFormat.Format(currentVersion));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.Info?.Print(LogClass.Application, "Up to date.");
|
||||||
|
|
||||||
|
_running = false;
|
||||||
|
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (!Version.TryParse(_versionResponse.Version, out Version newVersion))
|
||||||
|
{
|
||||||
|
Logger.Error?.Print(LogClass.Application,
|
||||||
|
$"Failed to convert the received {RyujinxApp.FullAppName} version from the update server!");
|
||||||
|
|
||||||
|
await ContentDialogHelper.CreateWarningDialog(
|
||||||
|
LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedServerMessage],
|
||||||
|
LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]);
|
||||||
|
|
||||||
|
_running = false;
|
||||||
|
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (currentVersion, newVersion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,13 +5,11 @@ using ICSharpCode.SharpZipLib.GZip;
|
|||||||
using ICSharpCode.SharpZipLib.Tar;
|
using ICSharpCode.SharpZipLib.Tar;
|
||||||
using ICSharpCode.SharpZipLib.Zip;
|
using ICSharpCode.SharpZipLib.Zip;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.Ava.Common.Models.Github;
|
|
||||||
using Ryujinx.Ava.UI.Helpers;
|
using Ryujinx.Ava.UI.Helpers;
|
||||||
using Ryujinx.Ava.Utilities;
|
using Ryujinx.Ava.Utilities;
|
||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Helper;
|
using Ryujinx.Common.Helper;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Common.Utilities;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
@ -29,147 +27,19 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace Ryujinx.Ava.Systems
|
namespace Ryujinx.Ava.Systems
|
||||||
{
|
{
|
||||||
internal static class Updater
|
internal static partial class Updater
|
||||||
{
|
{
|
||||||
private static ReleaseChannels.Channel? _currentReleaseChannel;
|
|
||||||
|
|
||||||
private const string GitHubApiUrl = "https://api.github.com";
|
|
||||||
|
|
||||||
private static readonly GithubReleasesJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
|
||||||
|
|
||||||
private static readonly string _homeDir = AppDomain.CurrentDomain.BaseDirectory;
|
private static readonly string _homeDir = AppDomain.CurrentDomain.BaseDirectory;
|
||||||
private static readonly string _updateDir = Path.Combine(Path.GetTempPath(), "Ryujinx", "update");
|
private static readonly string _updateDir = Path.Combine(Path.GetTempPath(), "Ryujinx", "update");
|
||||||
private static readonly string _updatePublishDir = Path.Combine(_updateDir, "publish");
|
private static readonly string _updatePublishDir = Path.Combine(_updateDir, "publish");
|
||||||
private const int ConnectionCount = 4;
|
private const int ConnectionCount = 4;
|
||||||
|
|
||||||
private static string _buildVer;
|
|
||||||
|
|
||||||
|
|
||||||
private static readonly string _platformExt = BuildPlatformExtension();
|
|
||||||
|
|
||||||
private static string _buildUrl;
|
|
||||||
private static long _buildSize;
|
private static long _buildSize;
|
||||||
private static bool _updateSuccessful;
|
private static bool _updateSuccessful;
|
||||||
private static bool _running;
|
private static bool _running;
|
||||||
|
|
||||||
private static readonly string[] _windowsDependencyDirs = [];
|
private static readonly string[] _windowsDependencyDirs = [];
|
||||||
|
|
||||||
public static async Task<Optional<(Version Current, Version Incoming)>> CheckVersionAsync(bool showVersionUpToDate = false)
|
|
||||||
{
|
|
||||||
if (!Version.TryParse(Program.Version, out Version currentVersion))
|
|
||||||
{
|
|
||||||
Logger.Error?.Print(LogClass.Application, $"Failed to convert the current {RyujinxApp.FullAppName} version!");
|
|
||||||
|
|
||||||
await ContentDialogHelper.CreateWarningDialog(
|
|
||||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedMessage],
|
|
||||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]);
|
|
||||||
|
|
||||||
_running = false;
|
|
||||||
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.Info?.Print(LogClass.Application, "Checking for updates.");
|
|
||||||
|
|
||||||
// Get latest version number from GitHub API
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using HttpClient jsonClient = ConstructHttpClient();
|
|
||||||
|
|
||||||
if (_currentReleaseChannel == null)
|
|
||||||
{
|
|
||||||
ReleaseChannels releaseChannels = await ReleaseInformation.GetReleaseChannelsAsync(jsonClient);
|
|
||||||
|
|
||||||
_currentReleaseChannel = ReleaseInformation.IsCanaryBuild
|
|
||||||
? releaseChannels.Canary
|
|
||||||
: releaseChannels.Stable;
|
|
||||||
}
|
|
||||||
|
|
||||||
string fetchedJson = await jsonClient.GetStringAsync(_currentReleaseChannel.Value.GetLatestReleaseApiUrl());
|
|
||||||
GithubReleasesJsonResponse fetched = JsonHelper.Deserialize(fetchedJson, _serializerContext.GithubReleasesJsonResponse);
|
|
||||||
_buildVer = fetched.TagName;
|
|
||||||
|
|
||||||
foreach (GithubReleaseAssetJsonResponse asset in fetched.Assets)
|
|
||||||
{
|
|
||||||
if (asset.Name.StartsWith("ryujinx") && asset.Name.EndsWith(_platformExt))
|
|
||||||
{
|
|
||||||
_buildUrl = asset.BrowserDownloadUrl;
|
|
||||||
|
|
||||||
if (asset.State != "uploaded")
|
|
||||||
{
|
|
||||||
if (showVersionUpToDate)
|
|
||||||
{
|
|
||||||
UserResult userResult = await ContentDialogHelper.CreateUpdaterUpToDateInfoDialog(
|
|
||||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage],
|
|
||||||
string.Empty);
|
|
||||||
|
|
||||||
if (userResult is UserResult.Ok)
|
|
||||||
{
|
|
||||||
OpenHelper.OpenUrl(ReleaseInformation.GetChangelogForVersion(currentVersion, _currentReleaseChannel.Value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.Info?.Print(LogClass.Application, "Up to date.");
|
|
||||||
|
|
||||||
_running = false;
|
|
||||||
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If build not done, assume no new update are available.
|
|
||||||
if (_buildUrl is null)
|
|
||||||
{
|
|
||||||
if (showVersionUpToDate)
|
|
||||||
{
|
|
||||||
UserResult userResult = await ContentDialogHelper.CreateUpdaterUpToDateInfoDialog(
|
|
||||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage],
|
|
||||||
string.Empty);
|
|
||||||
|
|
||||||
if (userResult is UserResult.Ok)
|
|
||||||
{
|
|
||||||
OpenHelper.OpenUrl(ReleaseInformation.GetChangelogForVersion(currentVersion, _currentReleaseChannel.Value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.Info?.Print(LogClass.Application, "Up to date.");
|
|
||||||
|
|
||||||
_running = false;
|
|
||||||
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
Logger.Error?.Print(LogClass.Application, exception.Message);
|
|
||||||
|
|
||||||
await ContentDialogHelper.CreateErrorDialog(
|
|
||||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterFailedToGetVersionMessage]);
|
|
||||||
|
|
||||||
_running = false;
|
|
||||||
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Version.TryParse(_buildVer, out Version newVersion))
|
|
||||||
{
|
|
||||||
Logger.Error?.Print(LogClass.Application, $"Failed to convert the received {RyujinxApp.FullAppName} version from GitHub!");
|
|
||||||
|
|
||||||
await ContentDialogHelper.CreateWarningDialog(
|
|
||||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedGithubMessage],
|
|
||||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]);
|
|
||||||
|
|
||||||
_running = false;
|
|
||||||
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (currentVersion, newVersion);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task BeginUpdateAsync(bool showVersionUpToDate = false)
|
public static async Task BeginUpdateAsync(bool showVersionUpToDate = false)
|
||||||
{
|
{
|
||||||
if (_running)
|
if (_running)
|
||||||
@ -196,7 +66,7 @@ namespace Ryujinx.Ava.Systems
|
|||||||
|
|
||||||
if (userResult is UserResult.Ok)
|
if (userResult is UserResult.Ok)
|
||||||
{
|
{
|
||||||
OpenHelper.OpenUrl(ReleaseInformation.GetChangelogForVersion(currentVersion, _currentReleaseChannel.Value));
|
OpenHelper.OpenUrl(_versionResponse.ReleaseUrlFormat.Format(currentVersion));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -212,8 +82,11 @@ namespace Ryujinx.Ava.Systems
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
buildSizeClient.DefaultRequestHeaders.Add("Range", "bytes=0-0");
|
buildSizeClient.DefaultRequestHeaders.Add("Range", "bytes=0-0");
|
||||||
|
|
||||||
|
// GitLab instance is located in Ukraine. Connection times will vary across the world.
|
||||||
|
buildSizeClient.Timeout = TimeSpan.FromSeconds(10);
|
||||||
|
|
||||||
HttpResponseMessage message = await buildSizeClient.GetAsync(new Uri(_buildUrl), HttpCompletionOption.ResponseHeadersRead);
|
HttpResponseMessage message = await buildSizeClient.GetAsync(new Uri(_versionResponse.ArtifactUrl), HttpCompletionOption.ResponseHeadersRead);
|
||||||
|
|
||||||
_buildSize = message.Content.Headers.ContentRange.Length.Value;
|
_buildSize = message.Content.Headers.ContentRange.Length.Value;
|
||||||
}
|
}
|
||||||
@ -243,11 +116,11 @@ namespace Ryujinx.Ava.Systems
|
|||||||
switch (shouldUpdate)
|
switch (shouldUpdate)
|
||||||
{
|
{
|
||||||
case UserResult.Yes:
|
case UserResult.Yes:
|
||||||
await UpdateRyujinx(_buildUrl);
|
await UpdateRyujinx(_versionResponse.ArtifactUrl);
|
||||||
break;
|
break;
|
||||||
// Secondary button maps to no, which in this case is the show changelog button.
|
// Secondary button maps to no, which in this case is the show changelog button.
|
||||||
case UserResult.No:
|
case UserResult.No:
|
||||||
OpenHelper.OpenUrl(ReleaseInformation.GetChangelogUrl(currentVersion, newVersion, _currentReleaseChannel.Value));
|
OpenHelper.OpenUrl(ReleaseInformation.GetChangelogUrl(currentVersion, newVersion));
|
||||||
goto RequestUserToUpdate;
|
goto RequestUserToUpdate;
|
||||||
default:
|
default:
|
||||||
_running = false;
|
_running = false;
|
||||||
@ -261,7 +134,7 @@ namespace Ryujinx.Ava.Systems
|
|||||||
HttpClient result = new();
|
HttpClient result = new();
|
||||||
|
|
||||||
// Required by GitHub to interact with APIs.
|
// Required by GitHub to interact with APIs.
|
||||||
result.DefaultRequestHeaders.Add("User-Agent", "Ryujinx-Updater/1.0.0");
|
result.DefaultRequestHeaders.Add("User-Agent", $"Ryujinx-Updater/{ReleaseInformation.Version}");
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
@ -2,29 +2,104 @@ using Avalonia.Media;
|
|||||||
using Ryujinx.Ava.UI.Windows;
|
using Ryujinx.Ava.UI.Windows;
|
||||||
using Ryujinx.HLE.UI;
|
using Ryujinx.HLE.UI;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Applet
|
namespace Ryujinx.Ava.UI.Applet
|
||||||
{
|
{
|
||||||
class AvaloniaHostUITheme(MainWindow parent) : IHostUITheme
|
class AvaloniaHostUITheme : IHostUITheme
|
||||||
{
|
{
|
||||||
public string FontFamily { get; } = OperatingSystem.IsWindows() && OperatingSystem.IsWindowsVersionAtLeast(10, 0, 22000) ? "Segoe UI Variable" : parent.FontFamily.Name;
|
private readonly MainWindow _parent;
|
||||||
|
|
||||||
public ThemeColor DefaultBackgroundColor { get; } = BrushToThemeColor(parent.Background);
|
public string FontFamily { get; }
|
||||||
public ThemeColor DefaultForegroundColor { get; } = BrushToThemeColor(parent.Foreground);
|
public ThemeColor DefaultBackgroundColor { get; }
|
||||||
public ThemeColor DefaultBorderColor { get; } = BrushToThemeColor(parent.BorderBrush);
|
public ThemeColor DefaultForegroundColor { get; }
|
||||||
public ThemeColor SelectionBackgroundColor { get; } = BrushToThemeColor(parent.ViewControls.SearchBox.SelectionBrush);
|
public ThemeColor DefaultBorderColor { get; }
|
||||||
public ThemeColor SelectionForegroundColor { get; } = BrushToThemeColor(parent.ViewControls.SearchBox.SelectionForegroundBrush);
|
public ThemeColor SelectionBackgroundColor { get; }
|
||||||
|
public ThemeColor SelectionForegroundColor { get; }
|
||||||
|
|
||||||
|
public AvaloniaHostUITheme(MainWindow parent)
|
||||||
|
{
|
||||||
|
_parent = parent;
|
||||||
|
|
||||||
|
// Initialize font property
|
||||||
|
FontFamily = GetSystemFontFamily();
|
||||||
|
|
||||||
|
// Initialize all properties that depend on parent
|
||||||
|
DefaultBackgroundColor = BrushToThemeColor(parent.Background);
|
||||||
|
DefaultForegroundColor = BrushToThemeColor(parent.Foreground);
|
||||||
|
DefaultBorderColor = BrushToThemeColor(parent.BorderBrush);
|
||||||
|
SelectionBackgroundColor = BrushToThemeColor(parent.ViewControls.SearchBox.SelectionBrush);
|
||||||
|
SelectionForegroundColor = BrushToThemeColor(parent.ViewControls.SearchBox.SelectionForegroundBrush);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetSystemFontFamily()
|
||||||
|
{
|
||||||
|
if (OperatingSystem.IsWindows())
|
||||||
|
{
|
||||||
|
return GetWindowsFontByLanguage();
|
||||||
|
}
|
||||||
|
else if (OperatingSystem.IsMacOS())
|
||||||
|
{
|
||||||
|
return GetMacOSFontByLanguage();
|
||||||
|
}
|
||||||
|
else // Linux and other platforms
|
||||||
|
{
|
||||||
|
return GetLinuxFontByLanguage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetWindowsFontByLanguage()
|
||||||
|
{
|
||||||
|
var culture = CultureInfo.CurrentUICulture;
|
||||||
|
string langCode = culture.Name;
|
||||||
|
|
||||||
|
return culture.TwoLetterISOLanguageName switch
|
||||||
|
{
|
||||||
|
"zh" => langCode == "zh-CN" || langCode == "zh-Hans" || langCode == "zh-SG"
|
||||||
|
? "Microsoft YaHei UI" // Simplified Chinese
|
||||||
|
: "Microsoft JhengHei UI", // Traditional Chinese
|
||||||
|
|
||||||
|
"ja" => "Yu Gothic UI", // Japanese
|
||||||
|
"ko" => "Malgun Gothic", // Korean
|
||||||
|
_ => OperatingSystem.IsWindowsVersionAtLeast(10, 0, 22000)
|
||||||
|
? "Segoe UI Variable" // Other languages - Windows 11+
|
||||||
|
: _parent.FontFamily.Name // Fallback to parent window font
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetMacOSFontByLanguage()
|
||||||
|
{
|
||||||
|
return CultureInfo.CurrentUICulture.TwoLetterISOLanguageName switch
|
||||||
|
{
|
||||||
|
"zh" => "PingFang SC", // Chinese (both simplified and traditional)
|
||||||
|
"ja" => "Hiragino Sans", // Japanese
|
||||||
|
"ko" => "Apple SD Gothic Neo", // Korean
|
||||||
|
_ => _parent.FontFamily.Name // Fallback to parent window font
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetLinuxFontByLanguage()
|
||||||
|
{
|
||||||
|
return CultureInfo.CurrentUICulture.TwoLetterISOLanguageName switch
|
||||||
|
{
|
||||||
|
"zh" => "Noto Sans CJK SC", // Chinese
|
||||||
|
"ja" => "Noto Sans CJK JP", // Japanese
|
||||||
|
"ko" => "Noto Sans CJK KR", // Korean
|
||||||
|
_ => _parent.FontFamily.Name // Fallback to parent window font
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private static ThemeColor BrushToThemeColor(IBrush brush)
|
private static ThemeColor BrushToThemeColor(IBrush brush)
|
||||||
{
|
{
|
||||||
if (brush is SolidColorBrush solidColor)
|
if (brush is SolidColorBrush solidColor)
|
||||||
{
|
{
|
||||||
return new ThemeColor((float)solidColor.Color.A / 255,
|
return new ThemeColor(
|
||||||
|
(float)solidColor.Color.A / 255,
|
||||||
(float)solidColor.Color.R / 255,
|
(float)solidColor.Color.R / 255,
|
||||||
(float)solidColor.Color.G / 255,
|
(float)solidColor.Color.G / 255,
|
||||||
(float)solidColor.Color.B / 255);
|
(float)solidColor.Color.B / 255
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ThemeColor();
|
return new ThemeColor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,28 @@
|
|||||||
|
using Avalonia.Data.Converters;
|
||||||
|
using Ryujinx.Ava.Common.Locale;
|
||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.UI.Helpers
|
||||||
|
{
|
||||||
|
internal class PlayerHotkeyLabelConverter : IValueConverter
|
||||||
|
{
|
||||||
|
public static readonly PlayerHotkeyLabelConverter Instance = new();
|
||||||
|
|
||||||
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
if (value is string playerName && !string.IsNullOrEmpty(playerName))
|
||||||
|
{
|
||||||
|
string baseText = LocaleManager.Instance[LocaleKeys.SettingsTabHotkeysCycleInputDevicePlayerX];
|
||||||
|
return string.Format(baseText, playerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -110,5 +110,8 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
|
|
||||||
[LibraryImport("user32.dll", SetLastError = true)]
|
[LibraryImport("user32.dll", SetLastError = true)]
|
||||||
public static partial nint SetWindowLongPtrW(nint hWnd, int nIndex, nint value);
|
public static partial nint SetWindowLongPtrW(nint hWnd, int nIndex, nint value);
|
||||||
|
|
||||||
|
[LibraryImport("user32.dll", SetLastError = true)]
|
||||||
|
public static partial ushort GetAsyncKeyState(int nVirtKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,24 @@ namespace Ryujinx.Ava.UI.Models.Input
|
|||||||
|
|
||||||
[ObservableProperty] private bool _turboModeWhileHeld;
|
[ObservableProperty] private bool _turboModeWhileHeld;
|
||||||
|
|
||||||
|
[ObservableProperty] private Key _cycleInputDevicePlayer1;
|
||||||
|
|
||||||
|
[ObservableProperty] private Key _cycleInputDevicePlayer2;
|
||||||
|
|
||||||
|
[ObservableProperty] private Key _cycleInputDevicePlayer3;
|
||||||
|
|
||||||
|
[ObservableProperty] private Key _cycleInputDevicePlayer4;
|
||||||
|
|
||||||
|
[ObservableProperty] private Key _cycleInputDevicePlayer5;
|
||||||
|
|
||||||
|
[ObservableProperty] private Key _cycleInputDevicePlayer6;
|
||||||
|
|
||||||
|
[ObservableProperty] private Key _cycleInputDevicePlayer7;
|
||||||
|
|
||||||
|
[ObservableProperty] private Key _cycleInputDevicePlayer8;
|
||||||
|
|
||||||
|
[ObservableProperty] private Key _cycleInputDeviceHandheld;
|
||||||
|
|
||||||
public HotkeyConfig(KeyboardHotkeys config)
|
public HotkeyConfig(KeyboardHotkeys config)
|
||||||
{
|
{
|
||||||
if (config == null)
|
if (config == null)
|
||||||
@ -50,6 +68,15 @@ namespace Ryujinx.Ava.UI.Models.Input
|
|||||||
CustomVSyncIntervalDecrement = config.CustomVSyncIntervalDecrement;
|
CustomVSyncIntervalDecrement = config.CustomVSyncIntervalDecrement;
|
||||||
TurboMode = config.TurboMode;
|
TurboMode = config.TurboMode;
|
||||||
TurboModeWhileHeld = config.TurboModeWhileHeld;
|
TurboModeWhileHeld = config.TurboModeWhileHeld;
|
||||||
|
CycleInputDevicePlayer1 = config.CycleInputDevicePlayer1;
|
||||||
|
CycleInputDevicePlayer2 = config.CycleInputDevicePlayer2;
|
||||||
|
CycleInputDevicePlayer3 = config.CycleInputDevicePlayer3;
|
||||||
|
CycleInputDevicePlayer4 = config.CycleInputDevicePlayer4;
|
||||||
|
CycleInputDevicePlayer5 = config.CycleInputDevicePlayer5;
|
||||||
|
CycleInputDevicePlayer6 = config.CycleInputDevicePlayer6;
|
||||||
|
CycleInputDevicePlayer7 = config.CycleInputDevicePlayer7;
|
||||||
|
CycleInputDevicePlayer8 = config.CycleInputDevicePlayer8;
|
||||||
|
CycleInputDeviceHandheld = config.CycleInputDeviceHandheld;
|
||||||
}
|
}
|
||||||
|
|
||||||
public KeyboardHotkeys GetConfig() =>
|
public KeyboardHotkeys GetConfig() =>
|
||||||
@ -67,7 +94,16 @@ namespace Ryujinx.Ava.UI.Models.Input
|
|||||||
CustomVSyncIntervalIncrement = CustomVSyncIntervalIncrement,
|
CustomVSyncIntervalIncrement = CustomVSyncIntervalIncrement,
|
||||||
CustomVSyncIntervalDecrement = CustomVSyncIntervalDecrement,
|
CustomVSyncIntervalDecrement = CustomVSyncIntervalDecrement,
|
||||||
TurboMode = TurboMode,
|
TurboMode = TurboMode,
|
||||||
TurboModeWhileHeld = TurboModeWhileHeld
|
TurboModeWhileHeld = TurboModeWhileHeld,
|
||||||
|
CycleInputDevicePlayer1 = CycleInputDevicePlayer1,
|
||||||
|
CycleInputDevicePlayer2 = CycleInputDevicePlayer2,
|
||||||
|
CycleInputDevicePlayer3 = CycleInputDevicePlayer3,
|
||||||
|
CycleInputDevicePlayer4 = CycleInputDevicePlayer4,
|
||||||
|
CycleInputDevicePlayer5 = CycleInputDevicePlayer5,
|
||||||
|
CycleInputDevicePlayer6 = CycleInputDevicePlayer6,
|
||||||
|
CycleInputDevicePlayer7 = CycleInputDevicePlayer7,
|
||||||
|
CycleInputDevicePlayer8 = CycleInputDevicePlayer8,
|
||||||
|
CycleInputDeviceHandheld = CycleInputDeviceHandheld
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
292
src/Ryujinx/UI/Overlay/ControllerOverlay.cs
Normal file
292
src/Ryujinx/UI/Overlay/ControllerOverlay.cs
Normal file
@ -0,0 +1,292 @@
|
|||||||
|
using SkiaSharp;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using OriginalInputConfig = Ryujinx.Common.Configuration.Hid.InputConfig;
|
||||||
|
using OriginalPlayerIndex = Ryujinx.Common.Configuration.Hid.PlayerIndex;
|
||||||
|
using OriginalInputBackendType = Ryujinx.Common.Configuration.Hid.InputBackendType;
|
||||||
|
using Ryujinx.Ava.Common.Locale;
|
||||||
|
|
||||||
|
namespace Ryujinx.UI.Overlay
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Controller overlay that shows controller bindings matching the original AXAML design
|
||||||
|
/// </summary>
|
||||||
|
public class ControllerOverlay : Overlay
|
||||||
|
{
|
||||||
|
private const float OverlayWidth = 400;
|
||||||
|
private const float Padding = 24;
|
||||||
|
private const float PlayerSpacing = 12;
|
||||||
|
private const float PlayerRowHeight = 32;
|
||||||
|
|
||||||
|
private const float TitleTextSize = 25;
|
||||||
|
private const float PlayerTextSize = 22;
|
||||||
|
|
||||||
|
private float _lifespan = 0f;
|
||||||
|
|
||||||
|
public ControllerOverlay() : base("ControllerOverlay")
|
||||||
|
{
|
||||||
|
CreateBaseElements();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreateBaseElements()
|
||||||
|
{
|
||||||
|
// Main background container
|
||||||
|
var background = new RectangleElement(0, 0, OverlayWidth, 200, // Dynamic height will be set later
|
||||||
|
new SKColor(0, 0, 0, 224)) // #E0000000
|
||||||
|
{
|
||||||
|
Name = "Background",
|
||||||
|
CornerRadius = 12,
|
||||||
|
BorderColor = new SKColor(255, 255, 255, 64), // #40FFFFFF
|
||||||
|
BorderWidth = 1
|
||||||
|
};
|
||||||
|
AddElement(background);
|
||||||
|
|
||||||
|
// Title text (will be updated with localized text)
|
||||||
|
var titleText = new TextElement(Padding + 30, Padding, LocaleManager.Instance[LocaleKeys.ControllerOverlayTitle], TitleTextSize, SKColors.White)
|
||||||
|
{
|
||||||
|
Name = "TitleText",
|
||||||
|
FontStyle = SKFontStyle.Bold
|
||||||
|
};
|
||||||
|
AddElement(titleText);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Show controller bindings with localized strings
|
||||||
|
/// </summary>
|
||||||
|
public void ShowControllerBindings(List<OriginalInputConfig> inputConfigs, int durationSeconds)
|
||||||
|
{
|
||||||
|
// Update title text
|
||||||
|
var titleElement = FindElement<TextElement>("TitleText");
|
||||||
|
if (titleElement != null)
|
||||||
|
{
|
||||||
|
titleElement.Text = LocaleManager.Instance[LocaleKeys.ControllerOverlayTitle];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset lifespan and opacity
|
||||||
|
_lifespan = durationSeconds;
|
||||||
|
|
||||||
|
// Clear existing player bindings
|
||||||
|
ClearPlayerBindings();
|
||||||
|
|
||||||
|
// Group controllers by player index (support all players + handheld)
|
||||||
|
var playerBindings = new Dictionary<OriginalPlayerIndex, List<OriginalInputConfig>>();
|
||||||
|
|
||||||
|
foreach (var config in inputConfigs.Where(c => c.PlayerIndex <= OriginalPlayerIndex.Handheld))
|
||||||
|
{
|
||||||
|
if (!playerBindings.ContainsKey(config.PlayerIndex))
|
||||||
|
{
|
||||||
|
playerBindings[config.PlayerIndex] = new List<OriginalInputConfig>();
|
||||||
|
}
|
||||||
|
playerBindings[config.PlayerIndex].Add(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
float currentY = Padding + 40; // After title
|
||||||
|
|
||||||
|
// Add player bindings to UI (support 8 players + handheld)
|
||||||
|
var playerIndices = new[]
|
||||||
|
{
|
||||||
|
OriginalPlayerIndex.Player1, OriginalPlayerIndex.Player2, OriginalPlayerIndex.Player3, OriginalPlayerIndex.Player4,
|
||||||
|
OriginalPlayerIndex.Player5, OriginalPlayerIndex.Player6, OriginalPlayerIndex.Player7, OriginalPlayerIndex.Player8,
|
||||||
|
OriginalPlayerIndex.Handheld
|
||||||
|
};
|
||||||
|
|
||||||
|
for (int i = 0; i < playerIndices.Length; i++)
|
||||||
|
{
|
||||||
|
var playerIndex = playerIndices[i];
|
||||||
|
float rowY = currentY + (i * (PlayerRowHeight + PlayerSpacing));
|
||||||
|
|
||||||
|
// Player number with colored background (circular badge)
|
||||||
|
var playerColor = GetPlayerColor(i);
|
||||||
|
var playerBadge = new RectangleElement(Padding, rowY, 24, 20, playerColor)
|
||||||
|
{
|
||||||
|
Name = $"PlayerBadge_{i}",
|
||||||
|
CornerRadius = 12
|
||||||
|
};
|
||||||
|
AddElement(playerBadge);
|
||||||
|
|
||||||
|
// Player number text
|
||||||
|
string playerLabel = playerIndex == OriginalPlayerIndex.Handheld ? "H" : $"P{(int)playerIndex + 1}";
|
||||||
|
var playerLabelElement = new TextElement(Padding + 12, rowY + 2, playerLabel, PlayerTextSize, SKColors.White)
|
||||||
|
{
|
||||||
|
Name = $"PlayerLabel_{i}",
|
||||||
|
FontStyle = SKFontStyle.Bold,
|
||||||
|
TextAlign = SKTextAlign.Center
|
||||||
|
};
|
||||||
|
AddElement(playerLabelElement);
|
||||||
|
|
||||||
|
// Controller info
|
||||||
|
if (playerBindings.ContainsKey(playerIndex))
|
||||||
|
{
|
||||||
|
var controllers = playerBindings[playerIndex];
|
||||||
|
var controllerNames = GetUniqueControllerDisplayNames(controllers);
|
||||||
|
|
||||||
|
var controllerTextElement = new TextElement(Padding + 56, rowY + 2, string.Join(", ", controllerNames), PlayerTextSize, new SKColor(144, 238, 144)) // LightGreen
|
||||||
|
{
|
||||||
|
Name = $"ControllerText_{i}",
|
||||||
|
FontStyle = SKFontStyle.Bold
|
||||||
|
};
|
||||||
|
AddElement(controllerTextElement);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var noControllerTextElement = new TextElement(Padding + 56, rowY + 2, LocaleManager.Instance[LocaleKeys.ControllerOverlayNoController], PlayerTextSize, new SKColor(128, 128, 128)) // Gray
|
||||||
|
{
|
||||||
|
Name = $"NoControllerText_{i}",
|
||||||
|
FontStyle = SKFontStyle.Italic
|
||||||
|
};
|
||||||
|
AddElement(noControllerTextElement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate total height and update background
|
||||||
|
float totalHeight = Padding + 40 + (playerIndices.Length * (PlayerRowHeight + PlayerSpacing)) + Padding + 20;
|
||||||
|
var background = FindElement<RectangleElement>("Background");
|
||||||
|
if (background != null)
|
||||||
|
{
|
||||||
|
background.Height = totalHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show the overlay (position will be set by Window class with actual dimensions)
|
||||||
|
IsVisible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SKColor GetPlayerColor(int playerIndex)
|
||||||
|
{
|
||||||
|
return playerIndex switch
|
||||||
|
{
|
||||||
|
0 => new SKColor(255, 92, 92), // Red for Player 1
|
||||||
|
1 => new SKColor(54, 162, 235), // Blue for Player 2
|
||||||
|
2 => new SKColor(255, 206, 84), // Yellow for Player 3
|
||||||
|
3 => new SKColor(75, 192, 192), // Green for Player 4
|
||||||
|
4 => new SKColor(153, 102, 255), // Purple for Player 5
|
||||||
|
5 => new SKColor(255, 159, 64), // Orange for Player 6
|
||||||
|
6 => new SKColor(199, 199, 199), // Light Gray for Player 7
|
||||||
|
7 => new SKColor(83, 102, 255), // Indigo for Player 8
|
||||||
|
8 => new SKColor(255, 99, 132), // Pink for Handheld
|
||||||
|
_ => new SKColor(128, 128, 128) // Gray fallback
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<string> GetUniqueControllerDisplayNames(List<OriginalInputConfig> controllers)
|
||||||
|
{
|
||||||
|
var nameGroups = new Dictionary<string, List<int>>();
|
||||||
|
var displayNames = new List<string>();
|
||||||
|
|
||||||
|
// First pass: get base names and group them
|
||||||
|
for (int i = 0; i < controllers.Count; i++)
|
||||||
|
{
|
||||||
|
string baseName = GetControllerDisplayName(controllers[i]);
|
||||||
|
|
||||||
|
if (!nameGroups.ContainsKey(baseName))
|
||||||
|
{
|
||||||
|
nameGroups[baseName] = new List<int>();
|
||||||
|
}
|
||||||
|
nameGroups[baseName].Add(i);
|
||||||
|
displayNames.Add(baseName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second pass: add numbering for duplicates
|
||||||
|
foreach (var group in nameGroups.Where(g => g.Value.Count > 1))
|
||||||
|
{
|
||||||
|
for (int i = 0; i < group.Value.Count; i++)
|
||||||
|
{
|
||||||
|
int index = group.Value[i];
|
||||||
|
displayNames[index] = $"{group.Key} #{i + 1}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return displayNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetControllerDisplayName(OriginalInputConfig config)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(config.Name))
|
||||||
|
{
|
||||||
|
return config.Backend switch
|
||||||
|
{
|
||||||
|
OriginalInputBackendType.WindowKeyboard => LocaleManager.Instance[LocaleKeys.ControllerOverlayKeyboard],
|
||||||
|
OriginalInputBackendType.GamepadSDL2 => LocaleManager.Instance[LocaleKeys.ControllerOverlayController],
|
||||||
|
_ => LocaleManager.Instance[LocaleKeys.ControllerOverlayUnknown]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Truncate long controller names from the middle
|
||||||
|
string name = config.Name;
|
||||||
|
if (name.Length > 25)
|
||||||
|
{
|
||||||
|
int keepLength = 22; // Total characters to keep (excluding "...")
|
||||||
|
int leftLength = (keepLength + 1) / 2; // Round up for left side
|
||||||
|
int rightLength = keepLength / 2; // Round down for right side
|
||||||
|
|
||||||
|
name = name.Substring(0, leftLength) + "..." + name.Substring(name.Length - rightLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clear all player bindings
|
||||||
|
/// </summary>
|
||||||
|
private void ClearPlayerBindings()
|
||||||
|
{
|
||||||
|
var elementsToRemove = new List<OverlayElement>();
|
||||||
|
|
||||||
|
foreach (var element in GetElements())
|
||||||
|
{
|
||||||
|
if (element.Name.StartsWith("PlayerBadge_") ||
|
||||||
|
element.Name.StartsWith("PlayerLabel_") ||
|
||||||
|
element.Name.StartsWith("ControllerText_") ||
|
||||||
|
element.Name.StartsWith("NoControllerText_"))
|
||||||
|
{
|
||||||
|
elementsToRemove.Add(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var element in elementsToRemove)
|
||||||
|
{
|
||||||
|
RemoveElement(element);
|
||||||
|
element.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update overlay
|
||||||
|
/// </summary>
|
||||||
|
public override void Update(float deltaTime, SKSize screenSize)
|
||||||
|
{
|
||||||
|
_lifespan -= deltaTime;
|
||||||
|
|
||||||
|
if (_lifespan <= 0)
|
||||||
|
{
|
||||||
|
IsVisible = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_lifespan <= 0.5f)
|
||||||
|
{
|
||||||
|
// Fade out during the last 0.5 seconds
|
||||||
|
Opacity = _lifespan / 0.5f;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Opacity = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update position if screen size is provided
|
||||||
|
if (screenSize.Width > 0 && screenSize.Height > 0)
|
||||||
|
{
|
||||||
|
SetPositionToTopRight(screenSize.Width, screenSize.Height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Position overlay to top-right matching original AXAML positioning
|
||||||
|
/// </summary>
|
||||||
|
public void SetPositionToTopRight(float screenWidth, float screenHeight)
|
||||||
|
{
|
||||||
|
X = screenWidth - OverlayWidth - 20; // 20px margin from right
|
||||||
|
Y = 50; // 50px margin from top
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
146
src/Ryujinx/UI/Overlay/ImageElement.cs
Normal file
146
src/Ryujinx/UI/Overlay/ImageElement.cs
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
using Ryujinx.Common.Logging;
|
||||||
|
using SkiaSharp;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.UI.Overlay
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Image overlay element
|
||||||
|
/// </summary>
|
||||||
|
public class ImageElement : OverlayElement
|
||||||
|
{
|
||||||
|
private SKBitmap _bitmap;
|
||||||
|
private byte[] _imageData;
|
||||||
|
private string _imagePath;
|
||||||
|
|
||||||
|
public SKFilterQuality FilterQuality { get; set; } = SKFilterQuality.Medium;
|
||||||
|
public bool MaintainAspectRatio { get; set; } = true;
|
||||||
|
|
||||||
|
public ImageElement()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImageElement(float x, float y, float width, float height, byte[] imageData)
|
||||||
|
{
|
||||||
|
X = x;
|
||||||
|
Y = y;
|
||||||
|
Width = width;
|
||||||
|
Height = height;
|
||||||
|
SetImageData(imageData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImageElement(float x, float y, float width, float height, string imagePath)
|
||||||
|
{
|
||||||
|
X = x;
|
||||||
|
Y = y;
|
||||||
|
Width = width;
|
||||||
|
Height = height;
|
||||||
|
SetImagePath(imagePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set image from byte array
|
||||||
|
/// </summary>
|
||||||
|
public void SetImageData(byte[] imageData)
|
||||||
|
{
|
||||||
|
_imageData = imageData;
|
||||||
|
_imagePath = null;
|
||||||
|
LoadBitmap();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set image from file path
|
||||||
|
/// </summary>
|
||||||
|
public void SetImagePath(string imagePath)
|
||||||
|
{
|
||||||
|
_imagePath = imagePath;
|
||||||
|
_imageData = null;
|
||||||
|
LoadBitmap();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set image from existing SKBitmap
|
||||||
|
/// </summary>
|
||||||
|
public void SetBitmap(SKBitmap bitmap)
|
||||||
|
{
|
||||||
|
_bitmap?.Dispose();
|
||||||
|
_bitmap = bitmap;
|
||||||
|
_imageData = null;
|
||||||
|
_imagePath = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadBitmap()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_bitmap?.Dispose();
|
||||||
|
_bitmap = null;
|
||||||
|
|
||||||
|
if (_imageData != null)
|
||||||
|
{
|
||||||
|
_bitmap = SKBitmap.Decode(_imageData);
|
||||||
|
}
|
||||||
|
else if (!string.IsNullOrEmpty(_imagePath))
|
||||||
|
{
|
||||||
|
_bitmap = SKBitmap.Decode(_imagePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.Error?.Print(LogClass.Gpu, $"Failed to load image: {ex.Message}");
|
||||||
|
_bitmap = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Render(SKCanvas canvas, float globalOpacity = 1.0f)
|
||||||
|
{
|
||||||
|
if (!IsVisible || _bitmap == null || Width <= 0 || Height <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
float effectiveOpacity = Opacity * globalOpacity;
|
||||||
|
|
||||||
|
using var paint = new SKPaint
|
||||||
|
{
|
||||||
|
FilterQuality = FilterQuality,
|
||||||
|
Color = SKColors.White.WithAlpha((byte)(255 * effectiveOpacity))
|
||||||
|
};
|
||||||
|
|
||||||
|
var sourceRect = new SKRect(0, 0, _bitmap.Width, _bitmap.Height);
|
||||||
|
var destRect = new SKRect(X, Y, X + Width, Y + Height);
|
||||||
|
|
||||||
|
if (MaintainAspectRatio)
|
||||||
|
{
|
||||||
|
// Calculate aspect ratio preserving destination rectangle
|
||||||
|
float sourceAspect = (float)_bitmap.Width / _bitmap.Height;
|
||||||
|
float destAspect = Width / Height;
|
||||||
|
|
||||||
|
if (sourceAspect > destAspect)
|
||||||
|
{
|
||||||
|
// Source is wider, fit to width
|
||||||
|
float newHeight = Width / sourceAspect;
|
||||||
|
float yOffset = (Height - newHeight) / 2;
|
||||||
|
destRect = new SKRect(X, Y + yOffset, X + Width, Y + yOffset + newHeight);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Source is taller, fit to height
|
||||||
|
float newWidth = Height * sourceAspect;
|
||||||
|
float xOffset = (Width - newWidth) / 2;
|
||||||
|
destRect = new SKRect(X + xOffset, Y, X + xOffset + newWidth, Y + Height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.DrawBitmap(_bitmap, sourceRect, destRect, paint);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
_bitmap?.Dispose();
|
||||||
|
_bitmap = null;
|
||||||
|
}
|
||||||
|
base.Dispose(disposing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
116
src/Ryujinx/UI/Overlay/Overlay.cs
Normal file
116
src/Ryujinx/UI/Overlay/Overlay.cs
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
using SkiaSharp;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Ryujinx.Graphics.Gpu;
|
||||||
|
|
||||||
|
namespace Ryujinx.UI.Overlay
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Base overlay class containing multiple elements
|
||||||
|
/// </summary>
|
||||||
|
public abstract class Overlay : IOverlay
|
||||||
|
{
|
||||||
|
private readonly List<OverlayElement> _elements = new();
|
||||||
|
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
public bool IsVisible { get; set; } = true;
|
||||||
|
public float Opacity { get; set; } = 1.0f;
|
||||||
|
public float X { get; set; }
|
||||||
|
public float Y { get; set; }
|
||||||
|
public int ZIndex { get; set; } = 0;
|
||||||
|
|
||||||
|
public Overlay()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public Overlay(string name)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add an element to this overlay
|
||||||
|
/// </summary>
|
||||||
|
public void AddElement(OverlayElement element)
|
||||||
|
{
|
||||||
|
_elements.Add(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Remove an element from this overlay
|
||||||
|
/// </summary>
|
||||||
|
public void RemoveElement(OverlayElement element)
|
||||||
|
{
|
||||||
|
_elements.Remove(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get all elements
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyList<OverlayElement> GetElements()
|
||||||
|
{
|
||||||
|
return _elements.AsReadOnly();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Find element by name
|
||||||
|
/// </summary>
|
||||||
|
public T FindElement<T>(string name) where T : OverlayElement
|
||||||
|
{
|
||||||
|
return _elements.OfType<T>().FirstOrDefault(e => e.Name == name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clear all elements
|
||||||
|
/// </summary>
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
foreach (var element in _elements)
|
||||||
|
{
|
||||||
|
element.Dispose();
|
||||||
|
}
|
||||||
|
_elements.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update overlay
|
||||||
|
/// </summary>
|
||||||
|
public abstract void Update(float deltaTime, SKSize screenSize = default);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Render this overlay
|
||||||
|
/// </summary>
|
||||||
|
public void Render(SKCanvas canvas)
|
||||||
|
{
|
||||||
|
if (!IsVisible || Opacity <= 0.0f)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Save canvas state
|
||||||
|
canvas.Save();
|
||||||
|
|
||||||
|
// Apply overlay position offset
|
||||||
|
if (X != 0 || Y != 0)
|
||||||
|
{
|
||||||
|
canvas.Translate(X, Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render all elements
|
||||||
|
foreach (var element in _elements)
|
||||||
|
{
|
||||||
|
if (element.IsVisible)
|
||||||
|
{
|
||||||
|
element.Render(canvas, Opacity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore canvas state
|
||||||
|
canvas.Restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
66
src/Ryujinx/UI/Overlay/OverlayElement.cs
Normal file
66
src/Ryujinx/UI/Overlay/OverlayElement.cs
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
using SkiaSharp;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.UI.Overlay
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Base class for all overlay elements
|
||||||
|
/// </summary>
|
||||||
|
public abstract class OverlayElement : IDisposable
|
||||||
|
{
|
||||||
|
public float X { get; set; }
|
||||||
|
public float Y { get; set; }
|
||||||
|
public float Width { get; set; }
|
||||||
|
public float Height { get; set; }
|
||||||
|
public bool IsVisible { get; set; } = true;
|
||||||
|
public float Opacity { get; set; } = 1.0f;
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Render this element to the canvas
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="canvas">The canvas to draw on</param>
|
||||||
|
/// <param name="globalOpacity">Global opacity multiplier</param>
|
||||||
|
public abstract void Render(SKCanvas canvas, float globalOpacity = 1.0f);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if a point is within this element's bounds
|
||||||
|
/// </summary>
|
||||||
|
public virtual bool Contains(float x, float y)
|
||||||
|
{
|
||||||
|
return x >= X && x <= X + Width && y >= Y && y <= Y + Height;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the bounds of this element
|
||||||
|
/// </summary>
|
||||||
|
public SKRect GetBounds()
|
||||||
|
{
|
||||||
|
return new SKRect(X, Y, X + Width, Y + Height);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Apply opacity to a color
|
||||||
|
/// </summary>
|
||||||
|
protected SKColor ApplyOpacity(SKColor color, float opacity)
|
||||||
|
{
|
||||||
|
return color.WithAlpha((byte)(color.Alpha * opacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dispose of resources
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dispose of resources
|
||||||
|
/// </summary>
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
161
src/Ryujinx/UI/Overlay/OverlayManager.cs
Normal file
161
src/Ryujinx/UI/Overlay/OverlayManager.cs
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
using SkiaSharp;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Ryujinx.Graphics.Gpu.Image;
|
||||||
|
using Ryujinx.Graphics.Gpu;
|
||||||
|
|
||||||
|
namespace Ryujinx.UI.Overlay
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Manages multiple overlays and handles rendering
|
||||||
|
/// </summary>
|
||||||
|
public class OverlayManager : IOverlayManager
|
||||||
|
{
|
||||||
|
private readonly List<IOverlay> _overlays = new();
|
||||||
|
private readonly object _lock = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add an overlay to the manager
|
||||||
|
/// </summary>
|
||||||
|
public void AddOverlay(IOverlay overlay)
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
_overlays.Add(overlay);
|
||||||
|
SortOverlays();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Remove an overlay from the manager
|
||||||
|
/// </summary>
|
||||||
|
public void RemoveOverlay(Overlay overlay)
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
_overlays.Remove(overlay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Remove overlay by name
|
||||||
|
/// </summary>
|
||||||
|
public void RemoveOverlay(string name)
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
var overlay = _overlays.FirstOrDefault(o => o.Name == name);
|
||||||
|
if (overlay != null)
|
||||||
|
{
|
||||||
|
_overlays.Remove(overlay);
|
||||||
|
overlay.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Find overlay by name
|
||||||
|
/// </summary>
|
||||||
|
public IOverlay FindOverlay(string name)
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
return _overlays.FirstOrDefault(o => o.Name == name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get all overlays
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyList<IOverlay> GetOverlays()
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
return _overlays.AsReadOnly();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clear all overlays
|
||||||
|
/// </summary>
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
foreach (var overlay in _overlays)
|
||||||
|
{
|
||||||
|
overlay.Dispose();
|
||||||
|
}
|
||||||
|
_overlays.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update all overlays (for animations)
|
||||||
|
/// </summary>
|
||||||
|
public void Update(float deltaTime, SKSize screenSize = default)
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
foreach (var overlay in _overlays.Where(o => o.IsVisible))
|
||||||
|
{
|
||||||
|
overlay.Update(deltaTime, screenSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Render all visible overlays
|
||||||
|
/// </summary>
|
||||||
|
public void Render(SKCanvas canvas)
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
foreach (var overlay in _overlays.Where(o => o.IsVisible && o.Opacity > 0.0f))
|
||||||
|
{
|
||||||
|
overlay.Render(canvas);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sort overlays by Z-index
|
||||||
|
/// </summary>
|
||||||
|
private void SortOverlays()
|
||||||
|
{
|
||||||
|
_overlays.Sort((a, b) => a.ZIndex.CompareTo(b.ZIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Show overlay
|
||||||
|
/// </summary>
|
||||||
|
public void ShowOverlay(string name)
|
||||||
|
{
|
||||||
|
var overlay = FindOverlay(name);
|
||||||
|
if (overlay != null)
|
||||||
|
{
|
||||||
|
overlay.IsVisible = true;
|
||||||
|
overlay.Opacity = 1.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Hide overlay
|
||||||
|
/// </summary>
|
||||||
|
public void HideOverlay(string name)
|
||||||
|
{
|
||||||
|
var overlay = FindOverlay(name);
|
||||||
|
if (overlay != null)
|
||||||
|
{
|
||||||
|
overlay.IsVisible = false;
|
||||||
|
overlay.Opacity = 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
78
src/Ryujinx/UI/Overlay/RectangleElement.cs
Normal file
78
src/Ryujinx/UI/Overlay/RectangleElement.cs
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
using SkiaSharp;
|
||||||
|
|
||||||
|
namespace Ryujinx.UI.Overlay
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Rectangle overlay element
|
||||||
|
/// </summary>
|
||||||
|
public class RectangleElement : OverlayElement
|
||||||
|
{
|
||||||
|
public SKColor BackgroundColor { get; set; } = SKColors.Transparent;
|
||||||
|
public SKColor BorderColor { get; set; } = SKColors.Transparent;
|
||||||
|
public float BorderWidth { get; set; } = 0;
|
||||||
|
public float CornerRadius { get; set; } = 0;
|
||||||
|
|
||||||
|
public RectangleElement()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public RectangleElement(float x, float y, float width, float height, SKColor backgroundColor)
|
||||||
|
{
|
||||||
|
X = x;
|
||||||
|
Y = y;
|
||||||
|
Width = width;
|
||||||
|
Height = height;
|
||||||
|
BackgroundColor = backgroundColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Render(SKCanvas canvas, float globalOpacity = 1.0f)
|
||||||
|
{
|
||||||
|
if (!IsVisible || Width <= 0 || Height <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
float effectiveOpacity = Opacity * globalOpacity;
|
||||||
|
var bounds = new SKRect(X, Y, X + Width, Y + Height);
|
||||||
|
|
||||||
|
// Draw background
|
||||||
|
if (BackgroundColor.Alpha > 0)
|
||||||
|
{
|
||||||
|
using var backgroundPaint = new SKPaint
|
||||||
|
{
|
||||||
|
Color = ApplyOpacity(BackgroundColor, effectiveOpacity),
|
||||||
|
Style = SKPaintStyle.Fill,
|
||||||
|
IsAntialias = true
|
||||||
|
};
|
||||||
|
|
||||||
|
if (CornerRadius > 0)
|
||||||
|
{
|
||||||
|
canvas.DrawRoundRect(bounds, CornerRadius, CornerRadius, backgroundPaint);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
canvas.DrawRect(bounds, backgroundPaint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw border
|
||||||
|
if (BorderWidth > 0 && BorderColor.Alpha > 0)
|
||||||
|
{
|
||||||
|
using var borderPaint = new SKPaint
|
||||||
|
{
|
||||||
|
Color = ApplyOpacity(BorderColor, effectiveOpacity),
|
||||||
|
Style = SKPaintStyle.Stroke,
|
||||||
|
StrokeWidth = BorderWidth,
|
||||||
|
IsAntialias = true
|
||||||
|
};
|
||||||
|
|
||||||
|
if (CornerRadius > 0)
|
||||||
|
{
|
||||||
|
canvas.DrawRoundRect(bounds, CornerRadius, CornerRadius, borderPaint);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
canvas.DrawRect(bounds, borderPaint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
121
src/Ryujinx/UI/Overlay/TextElement.cs
Normal file
121
src/Ryujinx/UI/Overlay/TextElement.cs
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
using SkiaSharp;
|
||||||
|
|
||||||
|
namespace Ryujinx.UI.Overlay
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Text overlay element
|
||||||
|
/// </summary>
|
||||||
|
public class TextElement : OverlayElement
|
||||||
|
{
|
||||||
|
public string Text { get; set; } = string.Empty;
|
||||||
|
public SKColor TextColor { get; set; } = SKColors.White;
|
||||||
|
public float FontSize { get; set; } = 16;
|
||||||
|
public string FontFamily { get; set; } = "Arial";
|
||||||
|
public SKFontStyle FontStyle { get; set; } = SKFontStyle.Normal;
|
||||||
|
public SKTextAlign TextAlign { get; set; } = SKTextAlign.Left;
|
||||||
|
public bool IsAntialias { get; set; } = true;
|
||||||
|
|
||||||
|
// Shadow properties
|
||||||
|
public bool HasShadow { get; set; } = false;
|
||||||
|
public SKColor ShadowColor { get; set; } = SKColors.Black;
|
||||||
|
public float ShadowOffsetX { get; set; } = 1;
|
||||||
|
public float ShadowOffsetY { get; set; } = 1;
|
||||||
|
public float ShadowBlur { get; set; } = 0;
|
||||||
|
|
||||||
|
public TextElement()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextElement(float x, float y, string text, float fontSize = 16, SKColor? color = null)
|
||||||
|
{
|
||||||
|
X = x;
|
||||||
|
Y = y;
|
||||||
|
Text = text;
|
||||||
|
FontSize = fontSize;
|
||||||
|
TextColor = color ?? SKColors.White;
|
||||||
|
|
||||||
|
// Auto-calculate width and height based on text
|
||||||
|
UpdateDimensions();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Render(SKCanvas canvas, float globalOpacity = 1.0f)
|
||||||
|
{
|
||||||
|
if (!IsVisible || string.IsNullOrEmpty(Text))
|
||||||
|
return;
|
||||||
|
|
||||||
|
float effectiveOpacity = Opacity * globalOpacity;
|
||||||
|
|
||||||
|
using var typeface = SKTypeface.FromFamilyName(FontFamily, FontStyle);
|
||||||
|
using var paint = new SKPaint
|
||||||
|
{
|
||||||
|
Color = ApplyOpacity(TextColor, effectiveOpacity),
|
||||||
|
TextSize = FontSize,
|
||||||
|
Typeface = typeface,
|
||||||
|
TextAlign = TextAlign,
|
||||||
|
IsAntialias = IsAntialias
|
||||||
|
};
|
||||||
|
|
||||||
|
float textX = X;
|
||||||
|
float textY = Y + FontSize; // Baseline adjustment
|
||||||
|
|
||||||
|
// Adjust X position based on alignment
|
||||||
|
if (TextAlign == SKTextAlign.Center)
|
||||||
|
{
|
||||||
|
textX += Width / 2;
|
||||||
|
}
|
||||||
|
else if (TextAlign == SKTextAlign.Right)
|
||||||
|
{
|
||||||
|
textX += Width;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw shadow if enabled
|
||||||
|
if (HasShadow)
|
||||||
|
{
|
||||||
|
using var shadowPaint = new SKPaint
|
||||||
|
{
|
||||||
|
Color = ApplyOpacity(ShadowColor, effectiveOpacity),
|
||||||
|
TextSize = FontSize,
|
||||||
|
Typeface = typeface,
|
||||||
|
TextAlign = TextAlign,
|
||||||
|
IsAntialias = IsAntialias
|
||||||
|
};
|
||||||
|
|
||||||
|
if (ShadowBlur > 0)
|
||||||
|
{
|
||||||
|
shadowPaint.MaskFilter = SKMaskFilter.CreateBlur(SKBlurStyle.Normal, ShadowBlur);
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.DrawText(Text, textX + ShadowOffsetX, textY + ShadowOffsetY, shadowPaint);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw main text
|
||||||
|
canvas.DrawText(Text, textX, textY, paint);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update width and height based on current text and font settings
|
||||||
|
/// </summary>
|
||||||
|
public void UpdateDimensions()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(Text))
|
||||||
|
{
|
||||||
|
Width = 0;
|
||||||
|
Height = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
using var typeface = SKTypeface.FromFamilyName(FontFamily, FontStyle);
|
||||||
|
using var paint = new SKPaint
|
||||||
|
{
|
||||||
|
TextSize = FontSize,
|
||||||
|
Typeface = typeface
|
||||||
|
};
|
||||||
|
|
||||||
|
var bounds = new SKRect();
|
||||||
|
paint.MeasureText(Text, ref bounds);
|
||||||
|
|
||||||
|
Width = bounds.Width;
|
||||||
|
Height = FontSize; // Use font size as height for consistency
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,7 @@ using Ryujinx.Ava.Systems.AppLibrary;
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Ryujinx.Ava.Common.Locale;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.ViewModels
|
namespace Ryujinx.Ava.UI.ViewModels
|
||||||
{
|
{
|
||||||
@ -11,15 +12,37 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
{
|
{
|
||||||
private readonly ApplicationLibrary _appLibrary;
|
private readonly ApplicationLibrary _appLibrary;
|
||||||
|
|
||||||
|
private (int Status, int Name) _sorting;
|
||||||
|
|
||||||
|
public bool IsSortedByTitle => true;
|
||||||
|
public bool IsSortedByStatus => true;
|
||||||
|
|
||||||
|
// Avalonia takes names of status from these variables
|
||||||
|
public LocaleKeys IsStringPlayable => LocaleKeys.CompatibilityListPlayable;
|
||||||
|
public LocaleKeys IsStringInGame => LocaleKeys.CompatibilityListIngame;
|
||||||
|
public LocaleKeys IsStringMenus => LocaleKeys.CompatibilityListMenus;
|
||||||
|
public LocaleKeys IsStringBoots => LocaleKeys.CompatibilityListBoots;
|
||||||
|
public LocaleKeys IsStringNothing => LocaleKeys.CompatibilityListNothing;
|
||||||
|
|
||||||
|
public string PlayableInfoText { get; set; }
|
||||||
|
public string InGameInfoText { get; set; }
|
||||||
|
public string MenusInfoText { get; set; }
|
||||||
|
public string BootsInfoText { get; set; }
|
||||||
|
public string NothingInfoText { get; set; }
|
||||||
|
|
||||||
|
|
||||||
private IEnumerable<CompatibilityEntry> _currentEntries = CompatibilityDatabase.Entries;
|
private IEnumerable<CompatibilityEntry> _currentEntries = CompatibilityDatabase.Entries;
|
||||||
|
|
||||||
private string[] _ownedGameTitleIds = [];
|
private string[] _ownedGameTitleIds = [];
|
||||||
|
|
||||||
|
private Func<CompatibilityEntry, object> _sortKeySelector = x => x.GameName; // Default sort by GameName
|
||||||
|
|
||||||
public IEnumerable<CompatibilityEntry> CurrentEntries => OnlyShowOwnedGames
|
public IEnumerable<CompatibilityEntry> CurrentEntries => OnlyShowOwnedGames
|
||||||
? _currentEntries.Where(x =>
|
? _currentEntries.Where(x =>
|
||||||
x.TitleId.Check(tid => _ownedGameTitleIds.ContainsIgnoreCase(tid)))
|
x.TitleId.Check(tid => _ownedGameTitleIds.ContainsIgnoreCase(tid)))
|
||||||
: _currentEntries;
|
: _currentEntries;
|
||||||
|
|
||||||
public CompatibilityViewModel() { }
|
public CompatibilityViewModel() {}
|
||||||
|
|
||||||
private void AppCountUpdated(object _, ApplicationCountUpdatedEventArgs __)
|
private void AppCountUpdated(object _, ApplicationCountUpdatedEventArgs __)
|
||||||
=> _ownedGameTitleIds = _appLibrary.Applications.Keys.Select(x => x.ToString("X16")).ToArray();
|
=> _ownedGameTitleIds = _appLibrary.Applications.Keys.Select(x => x.ToString("X16")).ToArray();
|
||||||
@ -27,19 +50,29 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
public CompatibilityViewModel(ApplicationLibrary appLibrary)
|
public CompatibilityViewModel(ApplicationLibrary appLibrary)
|
||||||
{
|
{
|
||||||
_appLibrary = appLibrary;
|
_appLibrary = appLibrary;
|
||||||
|
|
||||||
AppCountUpdated(null, null);
|
AppCountUpdated(null, null);
|
||||||
|
CountByStatus();
|
||||||
_appLibrary.ApplicationCountUpdated += AppCountUpdated;
|
_appLibrary.ApplicationCountUpdated += AppCountUpdated;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void CountByStatus()
|
||||||
|
{
|
||||||
|
PlayableInfoText = LocaleManager.Instance[LocaleKeys.CompatibilityListPlayable] + ": " + CurrentEntries.Count(x => x.Status == LocaleKeys.CompatibilityListPlayable);
|
||||||
|
InGameInfoText = LocaleManager.Instance[LocaleKeys.CompatibilityListIngame] + ": " + CurrentEntries.Count(x => x.Status == LocaleKeys.CompatibilityListIngame);
|
||||||
|
MenusInfoText = LocaleManager.Instance[LocaleKeys.CompatibilityListMenus] + ": " + CurrentEntries.Count(x => x.Status == LocaleKeys.CompatibilityListMenus);
|
||||||
|
BootsInfoText = LocaleManager.Instance[LocaleKeys.CompatibilityListBoots] + ": " + CurrentEntries.Count(x => x.Status == LocaleKeys.CompatibilityListBoots);
|
||||||
|
NothingInfoText = LocaleManager.Instance[LocaleKeys.CompatibilityListNothing] + ": " + CurrentEntries.Count(x => x.Status == LocaleKeys.CompatibilityListNothing);
|
||||||
|
|
||||||
|
_onlyShowOwnedGames = true;
|
||||||
|
}
|
||||||
|
|
||||||
void IDisposable.Dispose()
|
void IDisposable.Dispose()
|
||||||
{
|
{
|
||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
_appLibrary.ApplicationCountUpdated -= AppCountUpdated;
|
_appLibrary.ApplicationCountUpdated -= AppCountUpdated;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool _onlyShowOwnedGames = true;
|
private bool _onlyShowOwnedGames;
|
||||||
|
|
||||||
public bool OnlyShowOwnedGames
|
public bool OnlyShowOwnedGames
|
||||||
{
|
{
|
||||||
@ -54,17 +87,37 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void NameSorting(int nameSort = 0)
|
||||||
|
{
|
||||||
|
_sorting.Name = nameSort;
|
||||||
|
SortApply();
|
||||||
|
OnPropertyChanged();
|
||||||
|
OnPropertyChanged(nameof(SortName));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StatusSorting(int statusSort = 0)
|
||||||
|
{
|
||||||
|
_sorting.Status = statusSort;
|
||||||
|
SortApply();
|
||||||
|
OnPropertyChanged();
|
||||||
|
OnPropertyChanged(nameof(SortName));
|
||||||
|
}
|
||||||
|
|
||||||
public void Search(string searchTerm)
|
public void Search(string searchTerm)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(searchTerm))
|
if (string.IsNullOrEmpty(searchTerm))
|
||||||
{
|
{
|
||||||
SetEntries(CompatibilityDatabase.Entries);
|
SetEntries(CompatibilityDatabase.Entries);
|
||||||
|
SortApply();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SetEntries(CompatibilityDatabase.Entries.Where(x =>
|
SetEntries(CompatibilityDatabase.Entries.Where(x =>
|
||||||
x.GameName.ContainsIgnoreCase(searchTerm)
|
x.GameName.ContainsIgnoreCase(searchTerm)
|
||||||
|| x.TitleId.Check(tid => tid.ContainsIgnoreCase(searchTerm))));
|
|| x.TitleId.Check(tid => tid.ContainsIgnoreCase(searchTerm))));
|
||||||
|
|
||||||
|
SortApply();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetEntries(IEnumerable<CompatibilityEntry> entries)
|
private void SetEntries(IEnumerable<CompatibilityEntry> entries)
|
||||||
@ -72,5 +125,43 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
_currentEntries = entries.ToList();
|
_currentEntries = entries.ToList();
|
||||||
OnPropertyChanged(nameof(CurrentEntries));
|
OnPropertyChanged(nameof(CurrentEntries));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void SortApply()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_currentEntries = (_sorting switch
|
||||||
|
{
|
||||||
|
(0, 0) => _currentEntries.OrderBy(x => _sortKeySelector(x) ?? string.Empty), // A - Z
|
||||||
|
(0, 1) => _currentEntries.OrderByDescending(x => _sortKeySelector(x) ?? string.Empty), // Z - A
|
||||||
|
(1, 0) => _currentEntries.OrderBy(x => x.Status).ThenBy(x => x.GameName, StringComparer.OrdinalIgnoreCase), // Status Playable - Nothing, then A - Z
|
||||||
|
(1, 1) => _currentEntries.OrderBy(x => x.Status).ThenByDescending(x => x.GameName, StringComparer.OrdinalIgnoreCase), // Status Nothing - Playable, then A - Z
|
||||||
|
(2, 0) => _currentEntries.OrderByDescending(x => x.Status).ThenBy(x => x.GameName, StringComparer.OrdinalIgnoreCase), // Status Playable - Nothing, then Z - A
|
||||||
|
(2, 1) => _currentEntries.OrderByDescending(x => x.Status).ThenByDescending(x => x.GameName, StringComparer.OrdinalIgnoreCase), // Status Nothing - Playable, then Z - A
|
||||||
|
_ => _currentEntries.OrderBy(x => x.Status)
|
||||||
|
}).ToList();
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
OnPropertyChanged();
|
||||||
|
OnPropertyChanged(nameof(CurrentEntries));
|
||||||
|
}
|
||||||
|
|
||||||
|
public string SortName
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return (_sorting.Name) switch
|
||||||
|
{
|
||||||
|
(0) => LocaleManager.Instance[LocaleKeys.GameListSortStatusNameAscending],
|
||||||
|
(1) => LocaleManager.Instance[LocaleKeys.GameListSortStatusNameDescending],
|
||||||
|
_ => string.Empty,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,6 +50,9 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
|||||||
private string _controllerImage;
|
private string _controllerImage;
|
||||||
private int _device;
|
private int _device;
|
||||||
private object _configViewModel;
|
private object _configViewModel;
|
||||||
|
private bool _isChangeTrackingActive;
|
||||||
|
private string _chosenProfile;
|
||||||
|
[ObservableProperty] private bool _isModified;
|
||||||
[ObservableProperty] private string _profileName;
|
[ObservableProperty] private string _profileName;
|
||||||
[ObservableProperty] private bool _notificationIsVisible; // Automatically call the NotificationView property with OnPropertyChanged()
|
[ObservableProperty] private bool _notificationIsVisible; // Automatically call the NotificationView property with OnPropertyChanged()
|
||||||
[ObservableProperty] private string _notificationText; // Automatically call the NotificationText property with OnPropertyChanged()
|
[ObservableProperty] private string _notificationText; // Automatically call the NotificationText property with OnPropertyChanged()
|
||||||
@ -84,6 +87,8 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
|||||||
public AvaloniaList<string> ProfilesList { get; set; }
|
public AvaloniaList<string> ProfilesList { get; set; }
|
||||||
public AvaloniaList<string> DeviceList { get; set; }
|
public AvaloniaList<string> DeviceList { get; set; }
|
||||||
|
|
||||||
|
public bool UseGlobalConfig;
|
||||||
|
|
||||||
// XAML Flags
|
// XAML Flags
|
||||||
public bool ShowSettings => _device > 0;
|
public bool ShowSettings => _device > 0;
|
||||||
public bool IsController => _device > 1;
|
public bool IsController => _device > 1;
|
||||||
@ -94,31 +99,16 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
|||||||
public bool HasLed => SelectedGamepad.Features.HasFlag(GamepadFeaturesFlag.Led);
|
public bool HasLed => SelectedGamepad.Features.HasFlag(GamepadFeaturesFlag.Led);
|
||||||
public bool CanClearLed => SelectedGamepad.Name.ContainsIgnoreCase("DualSense");
|
public bool CanClearLed => SelectedGamepad.Name.ContainsIgnoreCase("DualSense");
|
||||||
|
|
||||||
public bool _isChangeTrackingActive;
|
|
||||||
|
|
||||||
public bool _isModified;
|
|
||||||
|
|
||||||
public bool IsModified
|
|
||||||
{
|
|
||||||
get => _isModified;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_isModified = value;
|
|
||||||
OnPropertyChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public event Action NotifyChangesEvent;
|
public event Action NotifyChangesEvent;
|
||||||
|
|
||||||
public string _profileChoose;
|
public string ChosenProfile
|
||||||
public string ProfileChoose
|
|
||||||
{
|
{
|
||||||
get => _profileChoose;
|
get => _chosenProfile;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
// When you select a profile, the settings from the profile will be applied.
|
// When you select a profile, the settings from the profile will be applied.
|
||||||
// To save the settings, you still need to click the apply button
|
// To save the settings, you still need to click the apply button
|
||||||
_profileChoose = value;
|
_chosenProfile = value;
|
||||||
LoadProfile();
|
LoadProfile();
|
||||||
OnPropertyChanged();
|
OnPropertyChanged();
|
||||||
}
|
}
|
||||||
@ -290,7 +280,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
|||||||
|
|
||||||
public InputConfig Config { get; set; }
|
public InputConfig Config { get; set; }
|
||||||
|
|
||||||
public InputViewModel(UserControl owner) : this()
|
public InputViewModel(UserControl owner, bool useGlobal = false) : this()
|
||||||
{
|
{
|
||||||
if (Program.PreviewerDetached)
|
if (Program.PreviewerDetached)
|
||||||
{
|
{
|
||||||
@ -303,6 +293,8 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
|||||||
|
|
||||||
_mainWindow.ViewModel.AppHost?.NpadManager.BlockInputUpdates();
|
_mainWindow.ViewModel.AppHost?.NpadManager.BlockInputUpdates();
|
||||||
|
|
||||||
|
UseGlobalConfig = useGlobal;
|
||||||
|
|
||||||
_isLoaded = false;
|
_isLoaded = false;
|
||||||
|
|
||||||
LoadDevices();
|
LoadDevices();
|
||||||
@ -335,9 +327,18 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
|||||||
PlayerIndexes.Add(new(PlayerIndex.Handheld, LocaleManager.Instance[LocaleKeys.ControllerSettingsHandheld]));
|
PlayerIndexes.Add(new(PlayerIndex.Handheld, LocaleManager.Instance[LocaleKeys.ControllerSettingsHandheld]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void LoadConfiguration(InputConfig inputConfig = null)
|
private void LoadConfiguration(InputConfig inputConfig = null)
|
||||||
{
|
{
|
||||||
Config = inputConfig ?? ConfigurationState.Instance.Hid.InputConfig.Value.FirstOrDefault(inputConfig => inputConfig.PlayerIndex == _playerId);
|
if (UseGlobalConfig && Program.UseExtraConfig)
|
||||||
|
{
|
||||||
|
Config = inputConfig ?? ConfigurationState.InstanceExtra.Hid.InputConfig.Value.FirstOrDefault(inputConfig => inputConfig.PlayerIndex == _playerId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Config = inputConfig ?? ConfigurationState.Instance.Hid.InputConfig.Value.FirstOrDefault(inputConfig => inputConfig.PlayerIndex == _playerId);
|
||||||
|
}
|
||||||
|
|
||||||
if (Config is StandardKeyboardInputConfig keyboardInputConfig)
|
if (Config is StandardKeyboardInputConfig keyboardInputConfig)
|
||||||
{
|
{
|
||||||
@ -525,18 +526,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetShortGamepadName(string str)
|
|
||||||
{
|
|
||||||
const string Ellipsis = "...";
|
|
||||||
const int MaxSize = 50;
|
|
||||||
|
|
||||||
if (str.Length > MaxSize)
|
|
||||||
{
|
|
||||||
return $"{str.AsSpan(0, MaxSize - Ellipsis.Length)}{Ellipsis}";
|
|
||||||
}
|
|
||||||
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string GetShortGamepadId(string str)
|
private static string GetShortGamepadId(string str)
|
||||||
{
|
{
|
||||||
@ -550,7 +540,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
|||||||
{
|
{
|
||||||
string GetGamepadName(IGamepad gamepad, int controllerNumber)
|
string GetGamepadName(IGamepad gamepad, int controllerNumber)
|
||||||
{
|
{
|
||||||
return $"{GetShortGamepadName(gamepad.Name)} ({controllerNumber})";
|
return $"{DefaultInputConfigurationProvider.GetShortGamepadName(gamepad.Name)} ({controllerNumber})";
|
||||||
}
|
}
|
||||||
|
|
||||||
string GetUniqueGamepadName(IGamepad gamepad, ref int controllerNumber)
|
string GetUniqueGamepadName(IGamepad gamepad, ref int controllerNumber)
|
||||||
@ -578,7 +568,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
|||||||
|
|
||||||
if (gamepad != null)
|
if (gamepad != null)
|
||||||
{
|
{
|
||||||
Devices.Add((DeviceType.Keyboard, id, $"{GetShortGamepadName(gamepad.Name)}"));
|
Devices.Add((DeviceType.Keyboard, id, $"{DefaultInputConfigurationProvider.GetShortGamepadName(gamepad.Name)}"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -651,138 +641,21 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
|||||||
InputConfig config;
|
InputConfig config;
|
||||||
if (activeDevice.Type == DeviceType.Keyboard)
|
if (activeDevice.Type == DeviceType.Keyboard)
|
||||||
{
|
{
|
||||||
string id = activeDevice.Id;
|
config = DefaultInputConfigurationProvider.CreateDefaultKeyboardConfig(activeDevice.Id, activeDevice.Name, _playerId);
|
||||||
string name = activeDevice.Name;
|
|
||||||
|
|
||||||
config = new StandardKeyboardInputConfig
|
|
||||||
{
|
|
||||||
Version = InputConfig.CurrentVersion,
|
|
||||||
Backend = InputBackendType.WindowKeyboard,
|
|
||||||
Id = id,
|
|
||||||
Name = name,
|
|
||||||
ControllerType = ControllerType.ProController,
|
|
||||||
LeftJoycon = new LeftJoyconCommonConfig<Key>
|
|
||||||
{
|
|
||||||
DpadUp = Key.Up,
|
|
||||||
DpadDown = Key.Down,
|
|
||||||
DpadLeft = Key.Left,
|
|
||||||
DpadRight = Key.Right,
|
|
||||||
ButtonMinus = Key.Minus,
|
|
||||||
ButtonL = Key.E,
|
|
||||||
ButtonZl = Key.Q,
|
|
||||||
ButtonSl = Key.Unbound,
|
|
||||||
ButtonSr = Key.Unbound,
|
|
||||||
},
|
|
||||||
LeftJoyconStick =
|
|
||||||
new JoyconConfigKeyboardStick<Key>
|
|
||||||
{
|
|
||||||
StickUp = Key.W,
|
|
||||||
StickDown = Key.S,
|
|
||||||
StickLeft = Key.A,
|
|
||||||
StickRight = Key.D,
|
|
||||||
StickButton = Key.F,
|
|
||||||
},
|
|
||||||
RightJoycon = new RightJoyconCommonConfig<Key>
|
|
||||||
{
|
|
||||||
ButtonA = Key.Z,
|
|
||||||
ButtonB = Key.X,
|
|
||||||
ButtonX = Key.C,
|
|
||||||
ButtonY = Key.V,
|
|
||||||
ButtonPlus = Key.Plus,
|
|
||||||
ButtonR = Key.U,
|
|
||||||
ButtonZr = Key.O,
|
|
||||||
ButtonSl = Key.Unbound,
|
|
||||||
ButtonSr = Key.Unbound,
|
|
||||||
},
|
|
||||||
RightJoyconStick = new JoyconConfigKeyboardStick<Key>
|
|
||||||
{
|
|
||||||
StickUp = Key.I,
|
|
||||||
StickDown = Key.K,
|
|
||||||
StickLeft = Key.J,
|
|
||||||
StickRight = Key.L,
|
|
||||||
StickButton = Key.H,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
else if (activeDevice.Type == DeviceType.Controller)
|
else if (activeDevice.Type == DeviceType.Controller)
|
||||||
{
|
{
|
||||||
bool isNintendoStyle = Devices.ToList().FirstOrDefault(x => x.Id == activeDevice.Id).Name.Contains("Nintendo");
|
bool isNintendoStyle = DefaultInputConfigurationProvider.IsNintendoStyleController(activeDevice.Name);
|
||||||
|
config = DefaultInputConfigurationProvider.CreateDefaultControllerConfig(activeDevice.Id, activeDevice.Name, _playerId, isNintendoStyle);
|
||||||
string id = activeDevice.Id.Split(" ")[0];
|
|
||||||
string name = activeDevice.Name;
|
|
||||||
|
|
||||||
config = new StandardControllerInputConfig
|
|
||||||
{
|
|
||||||
Version = InputConfig.CurrentVersion,
|
|
||||||
Backend = InputBackendType.GamepadSDL2,
|
|
||||||
Id = id,
|
|
||||||
Name = name,
|
|
||||||
ControllerType = ControllerType.ProController,
|
|
||||||
DeadzoneLeft = 0.1f,
|
|
||||||
DeadzoneRight = 0.1f,
|
|
||||||
RangeLeft = 1.0f,
|
|
||||||
RangeRight = 1.0f,
|
|
||||||
TriggerThreshold = 0.5f,
|
|
||||||
LeftJoycon = new LeftJoyconCommonConfig<ConfigGamepadInputId>
|
|
||||||
{
|
|
||||||
DpadUp = ConfigGamepadInputId.DpadUp,
|
|
||||||
DpadDown = ConfigGamepadInputId.DpadDown,
|
|
||||||
DpadLeft = ConfigGamepadInputId.DpadLeft,
|
|
||||||
DpadRight = ConfigGamepadInputId.DpadRight,
|
|
||||||
ButtonMinus = ConfigGamepadInputId.Minus,
|
|
||||||
ButtonL = ConfigGamepadInputId.LeftShoulder,
|
|
||||||
ButtonZl = ConfigGamepadInputId.LeftTrigger,
|
|
||||||
ButtonSl = ConfigGamepadInputId.Unbound,
|
|
||||||
ButtonSr = ConfigGamepadInputId.Unbound,
|
|
||||||
},
|
|
||||||
LeftJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId>
|
|
||||||
{
|
|
||||||
Joystick = ConfigStickInputId.Left,
|
|
||||||
StickButton = ConfigGamepadInputId.LeftStick,
|
|
||||||
InvertStickX = false,
|
|
||||||
InvertStickY = false,
|
|
||||||
},
|
|
||||||
RightJoycon = new RightJoyconCommonConfig<ConfigGamepadInputId>
|
|
||||||
{
|
|
||||||
ButtonA = isNintendoStyle ? ConfigGamepadInputId.A : ConfigGamepadInputId.B,
|
|
||||||
ButtonB = isNintendoStyle ? ConfigGamepadInputId.B : ConfigGamepadInputId.A,
|
|
||||||
ButtonX = isNintendoStyle ? ConfigGamepadInputId.X : ConfigGamepadInputId.Y,
|
|
||||||
ButtonY = isNintendoStyle ? ConfigGamepadInputId.Y : ConfigGamepadInputId.X,
|
|
||||||
ButtonPlus = ConfigGamepadInputId.Plus,
|
|
||||||
ButtonR = ConfigGamepadInputId.RightShoulder,
|
|
||||||
ButtonZr = ConfigGamepadInputId.RightTrigger,
|
|
||||||
ButtonSl = ConfigGamepadInputId.Unbound,
|
|
||||||
ButtonSr = ConfigGamepadInputId.Unbound,
|
|
||||||
},
|
|
||||||
RightJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId>
|
|
||||||
{
|
|
||||||
Joystick = ConfigStickInputId.Right,
|
|
||||||
StickButton = ConfigGamepadInputId.RightStick,
|
|
||||||
InvertStickX = false,
|
|
||||||
InvertStickY = false,
|
|
||||||
},
|
|
||||||
Motion = new StandardMotionConfigController
|
|
||||||
{
|
|
||||||
MotionBackend = MotionInputBackendType.GamepadDriver,
|
|
||||||
EnableMotion = true,
|
|
||||||
Sensitivity = 100,
|
|
||||||
GyroDeadzone = 1,
|
|
||||||
},
|
|
||||||
Rumble = new RumbleConfigController
|
|
||||||
{
|
|
||||||
StrongRumble = 1f,
|
|
||||||
WeakRumble = 1f,
|
|
||||||
EnableRumble = false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
config = new InputConfig();
|
config = new InputConfig
|
||||||
|
{
|
||||||
|
PlayerIndex = _playerId
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
config.PlayerIndex = _playerId;
|
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -902,7 +775,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
|||||||
|
|
||||||
LoadProfiles();
|
LoadProfiles();
|
||||||
|
|
||||||
ProfileChoose = ProfileName; // Show new profile
|
ChosenProfile = ProfileName; // Show new profile
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -936,7 +809,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
|||||||
|
|
||||||
LoadProfiles();
|
LoadProfiles();
|
||||||
|
|
||||||
ProfileChoose = ProfilesList[0].ToString(); // Show default profile
|
ChosenProfile = ProfilesList[0].ToString(); // Show default profile
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -966,7 +839,14 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
|||||||
|
|
||||||
List<InputConfig> newConfig = [];
|
List<InputConfig> newConfig = [];
|
||||||
|
|
||||||
newConfig.AddRange(ConfigurationState.Instance.Hid.InputConfig.Value);
|
if (UseGlobalConfig && Program.UseExtraConfig)
|
||||||
|
{
|
||||||
|
newConfig.AddRange(ConfigurationState.InstanceExtra.Hid.InputConfig.Value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
newConfig.AddRange(ConfigurationState.Instance.Hid.InputConfig.Value);
|
||||||
|
}
|
||||||
|
|
||||||
newConfig.Remove(newConfig.FirstOrDefault(x => x == null));
|
newConfig.Remove(newConfig.FirstOrDefault(x => x == null));
|
||||||
|
|
||||||
@ -1007,18 +887,21 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_mainWindow.ViewModel.AppHost?.NpadManager.ReloadConfiguration(newConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse);
|
|
||||||
|
|
||||||
// Atomically replace and signal input change.
|
// Atomically replace and signal input change.
|
||||||
// NOTE: Do not modify InputConfig.Value directly as other code depends on the on-change event.
|
// NOTE: Do not modify InputConfig.Value directly as other code depends on the on-change event.
|
||||||
ConfigurationState.Instance.Hid.InputConfig.Value = newConfig;
|
_mainWindow.ViewModel.AppHost?.NpadManager.ReloadConfiguration(newConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse);
|
||||||
|
|
||||||
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
|
if (UseGlobalConfig && Program.UseExtraConfig)
|
||||||
}
|
{
|
||||||
|
// In User Settings when "Use Global Input" is enabled, it saves global input to global setting
|
||||||
public void NotifyChange(string property)
|
ConfigurationState.InstanceExtra.Hid.InputConfig.Value = newConfig;
|
||||||
{
|
ConfigurationState.InstanceExtra.ToFileFormat().SaveConfig(Program.GlobalConfigurationPath);
|
||||||
OnPropertyChanged(property);
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ConfigurationState.Instance.Hid.InputConfig.Value = newConfig;
|
||||||
|
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void NotifyChanges()
|
public void NotifyChanges()
|
||||||
|
@ -32,6 +32,7 @@ using Ryujinx.Ava.UI.Windows;
|
|||||||
using Ryujinx.Ava.Utilities;
|
using Ryujinx.Ava.Utilities;
|
||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Common.Configuration;
|
||||||
|
using Ryujinx.Common.Configuration.Hid;
|
||||||
using Ryujinx.Common.Helper;
|
using Ryujinx.Common.Helper;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Common.UI;
|
using Ryujinx.Common.UI;
|
||||||
@ -310,10 +311,15 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
private void TotalTimePlayed_Recalculated(Optional<TimeSpan> ts)
|
private void TotalTimePlayed_Recalculated(Optional<TimeSpan> ts)
|
||||||
{
|
{
|
||||||
ShowTotalTimePlayed = ts.HasValue;
|
|
||||||
|
|
||||||
if (ts.HasValue)
|
if (ts.HasValue)
|
||||||
LocaleManager.Instance.SetDynamicValues(LocaleKeys.GameListLabelTotalTimePlayed, ValueFormatUtils.FormatTimeSpan(ts.Value));
|
{
|
||||||
|
var formattedPlayTime = ValueFormatUtils.FormatTimeSpan(ts.Value);
|
||||||
|
LocaleManager.Instance.SetDynamicValues(LocaleKeys.GameListLabelTotalTimePlayed, formattedPlayTime);
|
||||||
|
ShowTotalTimePlayed = formattedPlayTime != string.Empty;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ShowTotalTimePlayed = ts.HasValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ShowTotalTimePlayed
|
public bool ShowTotalTimePlayed
|
||||||
@ -334,7 +340,6 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
_listSelectedApplication = value;
|
_listSelectedApplication = value;
|
||||||
|
|
||||||
if (_listSelectedApplication != null && ListAppContextMenu == null)
|
if (_listSelectedApplication != null && ListAppContextMenu == null)
|
||||||
|
|
||||||
ListAppContextMenu = new ApplicationContextMenu();
|
ListAppContextMenu = new ApplicationContextMenu();
|
||||||
else if (_listSelectedApplication == null && ListAppContextMenu != null)
|
else if (_listSelectedApplication == null && ListAppContextMenu != null)
|
||||||
ListAppContextMenu = null!;
|
ListAppContextMenu = null!;
|
||||||
@ -1575,28 +1580,31 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
public bool InitializeUserConfig(ApplicationData application)
|
public bool InitializeUserConfig(ApplicationData application)
|
||||||
{
|
{
|
||||||
// Code where conditions will be met before loading the user configuration (Global Config)
|
// Code where conditions will be met before loading the user configuration (Global Config)
|
||||||
string BackendThreadingInit = Program.BackendThreadingArg;
|
string backendThreadingInit = Program.BackendThreadingArg ?? ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString();
|
||||||
|
|
||||||
BackendThreadingInit ??= ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString();
|
|
||||||
|
|
||||||
// If a configuration is found in the "/games/xxxxxxxxxxxxxx" folder, the program will load the user setting.
|
// If a configuration is found in the "/games/xxxxxxxxxxxxxx" folder, the program will load the user setting.
|
||||||
string idGame = application.IdBaseString;
|
string idGame = application.IdBaseString;
|
||||||
if (ConfigurationFileFormat.TryLoad(Program.GetDirGameUserConfig(idGame), out ConfigurationFileFormat configurationFileFormat))
|
if (ConfigurationFileFormat.TryLoad(Program.GetDirGameUserConfig(idGame), out ConfigurationFileFormat configurationFileFormat))
|
||||||
{
|
{
|
||||||
// Loads the user configuration, having previously changed the global configuration to the user configuration
|
// Loads the user configuration, having previously changed the global configuration to the user configuration
|
||||||
ConfigurationState.Instance.Load(configurationFileFormat, Program.GetDirGameUserConfig(idGame, true, true), idGame);
|
ConfigurationState.Instance.Load(configurationFileFormat, Program.GetDirGameUserConfig(idGame, true), idGame);
|
||||||
|
|
||||||
|
if (ConfigurationFileFormat.TryLoad(Program.GlobalConfigurationPath, out ConfigurationFileFormat configurationFileFormatExtra))
|
||||||
|
{
|
||||||
|
//This is where the global configuration will be stored.
|
||||||
|
//This allows you to change the global configuration settings during the game (for example, the global input setting)
|
||||||
|
ConfigurationState.InstanceExtra.Load(configurationFileFormatExtra, Program.GlobalConfigurationPath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Code where conditions will be executed after loading user configuration
|
// Code where conditions will be executed after loading user configuration
|
||||||
if (ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString() != BackendThreadingInit)
|
if (ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString() != backendThreadingInit)
|
||||||
{
|
{
|
||||||
|
Rebooter.RebootAppWithGame(application.Path,
|
||||||
List<string> Arguments = new()
|
[
|
||||||
{
|
"--bt",
|
||||||
"--bt", ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString() // BackendThreading
|
ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString()
|
||||||
};
|
]);
|
||||||
|
|
||||||
Rebooter.RebootAppWithGame(application.Path, Arguments);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -1683,10 +1691,33 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
SetMainContent(RendererHostControl);
|
SetMainContent(RendererHostControl);
|
||||||
|
|
||||||
RendererHostControl.Focus();
|
RendererHostControl.Focus();
|
||||||
|
|
||||||
|
// Show controller overlay
|
||||||
|
ShowControllerOverlay();
|
||||||
});
|
});
|
||||||
|
|
||||||
public static void UpdateGameMetadata(string titleId)
|
public static void UpdateGameMetadata(string titleId, TimeSpan playTime)
|
||||||
=> ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata => appMetadata.UpdatePostGame());
|
=> ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata => appMetadata.UpdatePostGame(playTime));
|
||||||
|
|
||||||
|
private void ShowControllerOverlay()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var inputConfigs = ConfigurationState.Instance.System.UseInputGlobalConfig.Value && Program.UseExtraConfig
|
||||||
|
? ConfigurationState.InstanceExtra.Hid.InputConfig.Value
|
||||||
|
: ConfigurationState.Instance.Hid.InputConfig.Value;
|
||||||
|
|
||||||
|
// Always show overlay - if no configs, it will show test data
|
||||||
|
int duration = ConfigurationState.Instance.ControllerOverlayGameStartDuration.Value;
|
||||||
|
// Show overlay through the GPU context window directly
|
||||||
|
AppHost.ShowControllerOverlay(inputConfigs, duration);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// Log the error but don't let it crash the game launch
|
||||||
|
Logger.Error?.Print(LogClass.UI, $"Failed to show controller overlay: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void RefreshFirmwareStatus()
|
public void RefreshFirmwareStatus()
|
||||||
{
|
{
|
||||||
@ -1991,7 +2022,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
// just checking for file presence
|
// just checking for file presence
|
||||||
viewModel.SelectedApplication.HasIndependentConfiguration = File.Exists(
|
viewModel.SelectedApplication.HasIndependentConfiguration = File.Exists(
|
||||||
Program.GetDirGameUserConfig(viewModel.SelectedApplication.IdString, false, false));
|
Program.GetDirGameUserConfig(viewModel.SelectedApplication.IdString));
|
||||||
|
|
||||||
viewModel.RefreshView();
|
viewModel.RefreshView();
|
||||||
});
|
});
|
||||||
|
@ -53,6 +53,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
[ObservableProperty] private bool _isVulkanAvailable = true;
|
[ObservableProperty] private bool _isVulkanAvailable = true;
|
||||||
[ObservableProperty] private bool _gameListNeedsRefresh;
|
[ObservableProperty] private bool _gameListNeedsRefresh;
|
||||||
private readonly List<string> _gpuIds = [];
|
private readonly List<string> _gpuIds = [];
|
||||||
|
public bool _useInputGlobalConfig;
|
||||||
private int _graphicsBackendIndex;
|
private int _graphicsBackendIndex;
|
||||||
private int _scalingFilter;
|
private int _scalingFilter;
|
||||||
private int _scalingFilterLevel;
|
private int _scalingFilterLevel;
|
||||||
@ -64,6 +65,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
public event Action CloseWindow;
|
public event Action CloseWindow;
|
||||||
public event Action SaveSettingsEvent;
|
public event Action SaveSettingsEvent;
|
||||||
|
public event Action<bool> LocalGlobalInputSwitchEvent;
|
||||||
private int _networkInterfaceIndex;
|
private int _networkInterfaceIndex;
|
||||||
private int _multiplayerModeIndex;
|
private int _multiplayerModeIndex;
|
||||||
private string _ldnPassphrase;
|
private string _ldnPassphrase;
|
||||||
@ -84,6 +86,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
public bool IsGameTitleNotNull => !string.IsNullOrEmpty(GameTitle);
|
public bool IsGameTitleNotNull => !string.IsNullOrEmpty(GameTitle);
|
||||||
public double PanelOpacity => IsGameTitleNotNull ? 0.5 : 1;
|
public double PanelOpacity => IsGameTitleNotNull ? 0.5 : 1;
|
||||||
|
|
||||||
|
|
||||||
public int ResolutionScale
|
public int ResolutionScale
|
||||||
{
|
{
|
||||||
get => _resolutionScale;
|
get => _resolutionScale;
|
||||||
@ -141,13 +144,28 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
public bool EnableDockedMode { get; set; }
|
public bool EnableDockedMode { get; set; }
|
||||||
public bool EnableKeyboard { get; set; }
|
public bool EnableKeyboard { get; set; }
|
||||||
public bool EnableMouse { get; set; }
|
public bool EnableMouse { get; set; }
|
||||||
public bool DisableInputWhenOutOfFocus { get; set; }
|
public bool DisableInputWhenOutOfFocus { get; set; }
|
||||||
|
|
||||||
public int FocusLostActionType { get; set; }
|
public int FocusLostActionType { get; set; }
|
||||||
|
public int ControllerOverlayGameStartDuration { get; set; }
|
||||||
|
public int ControllerOverlayInputCycleDuration { get; set; }
|
||||||
|
|
||||||
|
public bool UseGlobalInputConfig
|
||||||
|
{
|
||||||
|
get => _useInputGlobalConfig;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_useInputGlobalConfig = value;
|
||||||
|
LocalGlobalInputSwitchEvent?.Invoke(_useInputGlobalConfig);
|
||||||
|
OnPropertyChanged(nameof(InputPanelOpacity));
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public double InputPanelOpacity => UseGlobalInputConfig ? 0.5 : 1;
|
||||||
|
|
||||||
public VSyncMode VSyncMode
|
public VSyncMode VSyncMode
|
||||||
{
|
{
|
||||||
get => _vSyncMode;
|
get => _vSyncMode;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (value is VSyncMode.Custom or VSyncMode.Switch or VSyncMode.Unbounded)
|
if (value is VSyncMode.Custom or VSyncMode.Switch or VSyncMode.Unbounded)
|
||||||
@ -371,7 +389,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
public bool IsInvalidLdnPassphraseVisible { get; set; }
|
public bool IsInvalidLdnPassphraseVisible { get; set; }
|
||||||
|
|
||||||
public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this(false)
|
public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this()
|
||||||
{
|
{
|
||||||
_virtualFileSystem = virtualFileSystem;
|
_virtualFileSystem = virtualFileSystem;
|
||||||
_contentManager = contentManager;
|
_contentManager = contentManager;
|
||||||
@ -392,7 +410,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
string gameName,
|
string gameName,
|
||||||
string gameId,
|
string gameId,
|
||||||
byte[] gameIconData,
|
byte[] gameIconData,
|
||||||
bool enableToLoadCustomConfig) : this(enableToLoadCustomConfig)
|
bool customConfig) : this()
|
||||||
{
|
{
|
||||||
_virtualFileSystem = virtualFileSystem;
|
_virtualFileSystem = virtualFileSystem;
|
||||||
_contentManager = contentManager;
|
_contentManager = contentManager;
|
||||||
@ -408,9 +426,18 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
_gameTitle = gameName;
|
_gameTitle = gameName;
|
||||||
_gameId = gameId;
|
_gameId = gameId;
|
||||||
|
|
||||||
if (enableToLoadCustomConfig) // During the game. If there is no user config, then load the global config window
|
if (customConfig) // During the game. If there is no user config, then load the global config window
|
||||||
{
|
{
|
||||||
string gameDir = Program.GetDirGameUserConfig(gameId, false, true);
|
string gameDir = Program.GetDirGameUserConfig(gameId, true);
|
||||||
|
|
||||||
|
Program.UseExtraConfig = true;
|
||||||
|
|
||||||
|
if (ConfigurationFileFormat.TryLoad(Program.GlobalConfigurationPath, out ConfigurationFileFormat configurationFileFormatExtra))
|
||||||
|
{
|
||||||
|
// Extra load global configuration for input setting and save global input setting with other global config
|
||||||
|
ConfigurationState.InstanceExtra.Load(configurationFileFormatExtra, Program.GlobalConfigurationPath);
|
||||||
|
}
|
||||||
|
|
||||||
if (ConfigurationFileFormat.TryLoad(gameDir, out ConfigurationFileFormat configurationFileFormat))
|
if (ConfigurationFileFormat.TryLoad(gameDir, out ConfigurationFileFormat configurationFileFormat))
|
||||||
{
|
{
|
||||||
ConfigurationState.Instance.Load(configurationFileFormat, gameDir, gameId);
|
ConfigurationState.Instance.Load(configurationFileFormat, gameDir, gameId);
|
||||||
@ -426,7 +453,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public SettingsViewModel(bool noLoadGlobalConfig = false)
|
public SettingsViewModel()
|
||||||
{
|
{
|
||||||
GameDirectories = [];
|
GameDirectories = [];
|
||||||
AutoloadDirectories = [];
|
AutoloadDirectories = [];
|
||||||
@ -550,9 +577,9 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LoadCurrentConfiguration()
|
public void LoadCurrentConfiguration(bool global = false)
|
||||||
{
|
{
|
||||||
ConfigurationState config = ConfigurationState.Instance;
|
ConfigurationState config = global ? ConfigurationState.InstanceExtra: ConfigurationState.Instance;
|
||||||
|
|
||||||
// User Interface
|
// User Interface
|
||||||
EnableDiscordIntegration = config.EnableDiscordIntegration;
|
EnableDiscordIntegration = config.EnableDiscordIntegration;
|
||||||
@ -562,6 +589,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
HideCursor = (int)config.HideCursor.Value;
|
HideCursor = (int)config.HideCursor.Value;
|
||||||
UpdateCheckerType = (int)config.UpdateCheckerType.Value;
|
UpdateCheckerType = (int)config.UpdateCheckerType.Value;
|
||||||
FocusLostActionType = (int)config.FocusLostActionType.Value;
|
FocusLostActionType = (int)config.FocusLostActionType.Value;
|
||||||
|
ControllerOverlayGameStartDuration = config.ControllerOverlayGameStartDuration.Value;
|
||||||
|
ControllerOverlayInputCycleDuration = config.ControllerOverlayInputCycleDuration.Value;
|
||||||
|
|
||||||
GameDirectories.Clear();
|
GameDirectories.Clear();
|
||||||
GameDirectories.AddRange(config.UI.GameDirs.Value);
|
GameDirectories.AddRange(config.UI.GameDirs.Value);
|
||||||
@ -578,6 +607,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Input
|
// Input
|
||||||
|
UseGlobalInputConfig = config.System.UseInputGlobalConfig;
|
||||||
EnableDockedMode = config.System.EnableDockedMode;
|
EnableDockedMode = config.System.EnableDockedMode;
|
||||||
EnableKeyboard = config.Hid.EnableKeyboard;
|
EnableKeyboard = config.Hid.EnableKeyboard;
|
||||||
EnableMouse = config.Hid.EnableMouse;
|
EnableMouse = config.Hid.EnableMouse;
|
||||||
@ -660,9 +690,9 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
LdnServer = config.Multiplayer.LdnServer;
|
LdnServer = config.Multiplayer.LdnServer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SaveSettings()
|
public void SaveSettings(bool global = false)
|
||||||
{
|
{
|
||||||
ConfigurationState config = ConfigurationState.Instance;
|
ConfigurationState config = global ? ConfigurationState.InstanceExtra: ConfigurationState.Instance;
|
||||||
|
|
||||||
// User Interface
|
// User Interface
|
||||||
config.EnableDiscordIntegration.Value = EnableDiscordIntegration;
|
config.EnableDiscordIntegration.Value = EnableDiscordIntegration;
|
||||||
@ -672,6 +702,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
config.HideCursor.Value = (HideCursorMode)HideCursor;
|
config.HideCursor.Value = (HideCursorMode)HideCursor;
|
||||||
config.UpdateCheckerType.Value = (UpdaterType)UpdateCheckerType;
|
config.UpdateCheckerType.Value = (UpdaterType)UpdateCheckerType;
|
||||||
config.FocusLostActionType.Value = (FocusLostType)FocusLostActionType;
|
config.FocusLostActionType.Value = (FocusLostType)FocusLostActionType;
|
||||||
|
config.ControllerOverlayGameStartDuration.Value = ControllerOverlayGameStartDuration;
|
||||||
|
config.ControllerOverlayInputCycleDuration.Value = ControllerOverlayInputCycleDuration;
|
||||||
config.UI.GameDirs.Value = [.. GameDirectories];
|
config.UI.GameDirs.Value = [.. GameDirectories];
|
||||||
config.UI.AutoloadDirs.Value = [.. AutoloadDirectories];
|
config.UI.AutoloadDirs.Value = [.. AutoloadDirectories];
|
||||||
|
|
||||||
@ -684,6 +716,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Input
|
// Input
|
||||||
|
config.System.UseInputGlobalConfig.Value = UseGlobalInputConfig;
|
||||||
config.System.EnableDockedMode.Value = EnableDockedMode;
|
config.System.EnableDockedMode.Value = EnableDockedMode;
|
||||||
config.Hid.EnableKeyboard.Value = EnableKeyboard;
|
config.Hid.EnableKeyboard.Value = EnableKeyboard;
|
||||||
config.Hid.EnableMouse.Value = EnableMouse;
|
config.Hid.EnableMouse.Value = EnableMouse;
|
||||||
@ -796,11 +829,14 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
private static void RevertIfNotSaved()
|
private static void RevertIfNotSaved()
|
||||||
{
|
{
|
||||||
// maybe this is an unnecessary check(all options need to be tested)
|
/*
|
||||||
|
maybe this is an unnecessary check(all options need to be tested)
|
||||||
if (string.IsNullOrEmpty(Program.GlobalConfigurationPath))
|
if (string.IsNullOrEmpty(Program.GlobalConfigurationPath))
|
||||||
{
|
{
|
||||||
Program.ReloadConfig();
|
Program.ReloadConfig();
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
Program.ReloadConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ApplyButton()
|
public void ApplyButton()
|
||||||
@ -810,7 +846,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
public void DeleteConfigGame()
|
public void DeleteConfigGame()
|
||||||
{
|
{
|
||||||
string gameDir = Program.GetDirGameUserConfig(GameId, false, false);
|
string gameDir = Program.GetDirGameUserConfig(GameId);
|
||||||
|
|
||||||
if (File.Exists(gameDir))
|
if (File.Exists(gameDir))
|
||||||
{
|
{
|
||||||
|
@ -100,7 +100,7 @@
|
|||||||
Name="ProfileBox"
|
Name="ProfileBox"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
SelectedItem="{Binding ProfileChoose, Mode=TwoWay}"
|
SelectedItem="{Binding ChosenProfile, Mode=TwoWay}"
|
||||||
SelectionChanged="ComboBox_SelectionChanged"
|
SelectionChanged="ComboBox_SelectionChanged"
|
||||||
ItemsSource="{Binding ProfilesList}"
|
ItemsSource="{Binding ProfilesList}"
|
||||||
Text="{Binding ProfileName, Mode=TwoWay}" />
|
Text="{Binding ProfileName, Mode=TwoWay}" />
|
||||||
@ -203,7 +203,6 @@
|
|||||||
</StackPanel>
|
</StackPanel>
|
||||||
<ContentControl IsVisible="{Binding NotificationIsVisible}">
|
<ContentControl IsVisible="{Binding NotificationIsVisible}">
|
||||||
<ContentControl.Content>
|
<ContentControl.Content>
|
||||||
|
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Margin="5,20,0,0"
|
Margin="5,20,0,0"
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using FluentAvalonia.UI.Controls;
|
using FluentAvalonia.UI.Controls;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
|
using Ryujinx.Ava.Systems.Configuration;
|
||||||
using Ryujinx.Ava.UI.Controls;
|
using Ryujinx.Ava.UI.Controls;
|
||||||
using Ryujinx.Ava.UI.Helpers;
|
using Ryujinx.Ava.UI.Helpers;
|
||||||
using Ryujinx.Ava.UI.Models;
|
using Ryujinx.Ava.UI.Models;
|
||||||
@ -14,7 +15,7 @@ namespace Ryujinx.Ava.UI.Views.Input
|
|||||||
|
|
||||||
public InputView()
|
public InputView()
|
||||||
{
|
{
|
||||||
ViewModel = new InputViewModel(this);
|
ViewModel = new InputViewModel(this, ConfigurationState.Instance.System.UseInputGlobalConfig);
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
@ -24,6 +25,13 @@ namespace Ryujinx.Ava.UI.Views.Input
|
|||||||
ViewModel.Save();
|
ViewModel.Save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ToggleLocalGlobalInput(bool enableConfigGlobal)
|
||||||
|
{
|
||||||
|
Dispose();
|
||||||
|
ViewModel = new InputViewModel(this, enableConfigGlobal); // Create new Input Page with global input configs
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
private async void PlayerIndexBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
|
private async void PlayerIndexBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||||
{
|
{
|
||||||
if (PlayerIndexBox != null)
|
if (PlayerIndexBox != null)
|
||||||
|
@ -64,6 +64,7 @@
|
|||||||
MinWidth="200"
|
MinWidth="200"
|
||||||
Height="6"
|
Height="6"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
|
Margin="0, 0, 5, 0"
|
||||||
Foreground="{DynamicResource SystemAccentColorLight2}"
|
Foreground="{DynamicResource SystemAccentColorLight2}"
|
||||||
IsVisible="{Binding StatusBarVisible}"
|
IsVisible="{Binding StatusBarVisible}"
|
||||||
Maximum="{Binding StatusBarProgressMaximum}"
|
Maximum="{Binding StatusBarProgressMaximum}"
|
||||||
|
@ -56,7 +56,27 @@
|
|||||||
SelectedIndex="{Binding PreferredGpuIndex}"
|
SelectedIndex="{Binding PreferredGpuIndex}"
|
||||||
ItemsSource="{Binding AvailableGpus}"/>
|
ItemsSource="{Binding AvailableGpus}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</StackPanel>
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<TextBlock VerticalAlignment="Center"
|
||||||
|
ToolTip.Tip="{ext:Locale GraphicsBackendThreadingTooltip}"
|
||||||
|
Text="{ext:Locale SettingsTabGraphicsBackendMultithreading}"
|
||||||
|
Width="250" />
|
||||||
|
<ComboBox Width="350"
|
||||||
|
HorizontalContentAlignment="Left"
|
||||||
|
ToolTip.Tip="{ext:Locale GalThreadingTooltip}"
|
||||||
|
SelectedIndex="{Binding GraphicsBackendMultithreadingIndex}">
|
||||||
|
<ComboBoxItem>
|
||||||
|
<TextBlock Text="{ext:Locale CommonAuto}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
<ComboBoxItem>
|
||||||
|
<TextBlock Text="{ext:Locale CommonOff}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
<ComboBoxItem>
|
||||||
|
<TextBlock Text="{ext:Locale CommonOn}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
</ComboBox>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
<Separator Height="1" />
|
<Separator Height="1" />
|
||||||
<TextBlock Classes="h1" Text="{ext:Locale SettingsTabGraphicsFeatures}" />
|
<TextBlock Classes="h1" Text="{ext:Locale SettingsTabGraphicsFeatures}" />
|
||||||
<StackPanel Margin="10,0,0,0" Orientation="Vertical" Spacing="10">
|
<StackPanel Margin="10,0,0,0" Orientation="Vertical" Spacing="10">
|
||||||
@ -255,32 +275,7 @@
|
|||||||
</ComboBox>
|
</ComboBox>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel
|
|
||||||
Margin="10,0,0,0"
|
|
||||||
HorizontalAlignment="Stretch"
|
|
||||||
Orientation="Vertical"
|
|
||||||
Spacing="10">
|
|
||||||
<StackPanel Orientation="Horizontal">
|
|
||||||
<TextBlock VerticalAlignment="Center"
|
|
||||||
ToolTip.Tip="{ext:Locale GraphicsBackendThreadingTooltip}"
|
|
||||||
Text="{ext:Locale SettingsTabGraphicsBackendMultithreading}"
|
|
||||||
Width="250" />
|
|
||||||
<ComboBox Width="350"
|
|
||||||
HorizontalContentAlignment="Left"
|
|
||||||
ToolTip.Tip="{ext:Locale GalThreadingTooltip}"
|
|
||||||
SelectedIndex="{Binding GraphicsBackendMultithreadingIndex}">
|
|
||||||
<ComboBoxItem>
|
|
||||||
<TextBlock Text="{ext:Locale CommonAuto}" />
|
|
||||||
</ComboBoxItem>
|
|
||||||
<ComboBoxItem>
|
|
||||||
<TextBlock Text="{ext:Locale CommonOff}" />
|
|
||||||
</ComboBoxItem>
|
|
||||||
<ComboBoxItem>
|
|
||||||
<TextBlock Text="{ext:Locale CommonOn}" />
|
|
||||||
</ComboBoxItem>
|
|
||||||
</ComboBox>
|
|
||||||
</StackPanel>
|
|
||||||
</StackPanel>
|
|
||||||
<Separator Height="1" />
|
<Separator Height="1" />
|
||||||
<TextBlock Classes="h1" Text="{ext:Locale SettingsTabGraphicsDeveloperOptions}" />
|
<TextBlock Classes="h1" Text="{ext:Locale SettingsTabGraphicsDeveloperOptions}" />
|
||||||
<StackPanel
|
<StackPanel
|
||||||
|
@ -120,6 +120,60 @@
|
|||||||
<TextBlock Text="{ext:Locale SettingsTabHotkeysOnlyWhilePressed}" Margin="10,0" />
|
<TextBlock Text="{ext:Locale SettingsTabHotkeysOnlyWhilePressed}" Margin="10,0" />
|
||||||
<CheckBox IsChecked="{Binding KeyboardHotkey.TurboModeWhileHeld}" />
|
<CheckBox IsChecked="{Binding KeyboardHotkey.TurboModeWhileHeld}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Text="{ext:Locale ControllerSettingsPlayer1, Converter={x:Static helpers:PlayerHotkeyLabelConverter.Instance}}" Classes="settingHeader" />
|
||||||
|
<ToggleButton Name="CycleInputDevicePlayer1">
|
||||||
|
<TextBlock Text="{Binding KeyboardHotkey.CycleInputDevicePlayer1, Converter={x:Static helpers:KeyValueConverter.Instance}}" />
|
||||||
|
</ToggleButton>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Text="{ext:Locale ControllerSettingsPlayer2, Converter={x:Static helpers:PlayerHotkeyLabelConverter.Instance}}" Classes="settingHeader" />
|
||||||
|
<ToggleButton Name="CycleInputDevicePlayer2">
|
||||||
|
<TextBlock Text="{Binding KeyboardHotkey.CycleInputDevicePlayer2, Converter={x:Static helpers:KeyValueConverter.Instance}}" />
|
||||||
|
</ToggleButton>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Text="{ext:Locale ControllerSettingsPlayer3, Converter={x:Static helpers:PlayerHotkeyLabelConverter.Instance}}" Classes="settingHeader" />
|
||||||
|
<ToggleButton Name="CycleInputDevicePlayer3">
|
||||||
|
<TextBlock Text="{Binding KeyboardHotkey.CycleInputDevicePlayer3, Converter={x:Static helpers:KeyValueConverter.Instance}}" />
|
||||||
|
</ToggleButton>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Text="{ext:Locale ControllerSettingsPlayer4, Converter={x:Static helpers:PlayerHotkeyLabelConverter.Instance}}" Classes="settingHeader" />
|
||||||
|
<ToggleButton Name="CycleInputDevicePlayer4">
|
||||||
|
<TextBlock Text="{Binding KeyboardHotkey.CycleInputDevicePlayer4, Converter={x:Static helpers:KeyValueConverter.Instance}}" />
|
||||||
|
</ToggleButton>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Text="{ext:Locale ControllerSettingsPlayer5, Converter={x:Static helpers:PlayerHotkeyLabelConverter.Instance}}" Classes="settingHeader" />
|
||||||
|
<ToggleButton Name="CycleInputDevicePlayer5">
|
||||||
|
<TextBlock Text="{Binding KeyboardHotkey.CycleInputDevicePlayer5, Converter={x:Static helpers:KeyValueConverter.Instance}}" />
|
||||||
|
</ToggleButton>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Text="{ext:Locale ControllerSettingsPlayer6, Converter={x:Static helpers:PlayerHotkeyLabelConverter.Instance}}" Classes="settingHeader" />
|
||||||
|
<ToggleButton Name="CycleInputDevicePlayer6">
|
||||||
|
<TextBlock Text="{Binding KeyboardHotkey.CycleInputDevicePlayer6, Converter={x:Static helpers:KeyValueConverter.Instance}}" />
|
||||||
|
</ToggleButton>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Text="{ext:Locale ControllerSettingsPlayer7, Converter={x:Static helpers:PlayerHotkeyLabelConverter.Instance}}" Classes="settingHeader" />
|
||||||
|
<ToggleButton Name="CycleInputDevicePlayer7">
|
||||||
|
<TextBlock Text="{Binding KeyboardHotkey.CycleInputDevicePlayer7, Converter={x:Static helpers:KeyValueConverter.Instance}}" />
|
||||||
|
</ToggleButton>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Text="{ext:Locale ControllerSettingsPlayer8, Converter={x:Static helpers:PlayerHotkeyLabelConverter.Instance}}" Classes="settingHeader" />
|
||||||
|
<ToggleButton Name="CycleInputDevicePlayer8">
|
||||||
|
<TextBlock Text="{Binding KeyboardHotkey.CycleInputDevicePlayer8, Converter={x:Static helpers:KeyValueConverter.Instance}}" />
|
||||||
|
</ToggleButton>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Text="{ext:Locale ControllerSettingsHandheld, Converter={x:Static helpers:PlayerHotkeyLabelConverter.Instance}}" Classes="settingHeader" />
|
||||||
|
<ToggleButton Name="CycleInputDeviceHandheld">
|
||||||
|
<TextBlock Text="{Binding KeyboardHotkey.CycleInputDeviceHandheld, Converter={x:Static helpers:KeyValueConverter.Instance}}" />
|
||||||
|
</ToggleButton>
|
||||||
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
|
@ -82,7 +82,16 @@ namespace Ryujinx.Ava.UI.Views.Settings
|
|||||||
{ "VolumeDown", () => viewModel.KeyboardHotkey.VolumeDown = Key.Unbound },
|
{ "VolumeDown", () => viewModel.KeyboardHotkey.VolumeDown = Key.Unbound },
|
||||||
{ "CustomVSyncIntervalIncrement", () => viewModel.KeyboardHotkey.CustomVSyncIntervalIncrement = Key.Unbound },
|
{ "CustomVSyncIntervalIncrement", () => viewModel.KeyboardHotkey.CustomVSyncIntervalIncrement = Key.Unbound },
|
||||||
{ "CustomVSyncIntervalDecrement", () => viewModel.KeyboardHotkey.CustomVSyncIntervalDecrement = Key.Unbound },
|
{ "CustomVSyncIntervalDecrement", () => viewModel.KeyboardHotkey.CustomVSyncIntervalDecrement = Key.Unbound },
|
||||||
{ "TurboMode", () => viewModel.KeyboardHotkey.TurboMode = Key.Unbound }
|
{ "TurboMode", () => viewModel.KeyboardHotkey.TurboMode = Key.Unbound },
|
||||||
|
{ "CycleInputDevicePlayer1", () => viewModel.KeyboardHotkey.CycleInputDevicePlayer1 = Key.Unbound },
|
||||||
|
{ "CycleInputDevicePlayer2", () => viewModel.KeyboardHotkey.CycleInputDevicePlayer2 = Key.Unbound },
|
||||||
|
{ "CycleInputDevicePlayer3", () => viewModel.KeyboardHotkey.CycleInputDevicePlayer3 = Key.Unbound },
|
||||||
|
{ "CycleInputDevicePlayer4", () => viewModel.KeyboardHotkey.CycleInputDevicePlayer4 = Key.Unbound },
|
||||||
|
{ "CycleInputDevicePlayer5", () => viewModel.KeyboardHotkey.CycleInputDevicePlayer5 = Key.Unbound },
|
||||||
|
{ "CycleInputDevicePlayer6", () => viewModel.KeyboardHotkey.CycleInputDevicePlayer6 = Key.Unbound },
|
||||||
|
{ "CycleInputDevicePlayer7", () => viewModel.KeyboardHotkey.CycleInputDevicePlayer7 = Key.Unbound },
|
||||||
|
{ "CycleInputDevicePlayer8", () => viewModel.KeyboardHotkey.CycleInputDevicePlayer8 = Key.Unbound },
|
||||||
|
{ "CycleInputDeviceHandheld", () => viewModel.KeyboardHotkey.CycleInputDeviceHandheld = Key.Unbound }
|
||||||
};
|
};
|
||||||
|
|
||||||
if (buttonActions.TryGetValue(_currentAssigner.ToggledButton.Name, out Action action))
|
if (buttonActions.TryGetValue(_currentAssigner.ToggledButton.Name, out Action action))
|
||||||
@ -162,6 +171,33 @@ namespace Ryujinx.Ava.UI.Views.Settings
|
|||||||
case "TurboMode":
|
case "TurboMode":
|
||||||
ViewModel.KeyboardHotkey.TurboMode = buttonValue.AsHidType<Key>();
|
ViewModel.KeyboardHotkey.TurboMode = buttonValue.AsHidType<Key>();
|
||||||
break;
|
break;
|
||||||
|
case "CycleInputDevicePlayer1":
|
||||||
|
ViewModel.KeyboardHotkey.CycleInputDevicePlayer1 = buttonValue.AsHidType<Key>();
|
||||||
|
break;
|
||||||
|
case "CycleInputDevicePlayer2":
|
||||||
|
ViewModel.KeyboardHotkey.CycleInputDevicePlayer2 = buttonValue.AsHidType<Key>();
|
||||||
|
break;
|
||||||
|
case "CycleInputDevicePlayer3":
|
||||||
|
ViewModel.KeyboardHotkey.CycleInputDevicePlayer3 = buttonValue.AsHidType<Key>();
|
||||||
|
break;
|
||||||
|
case "CycleInputDevicePlayer4":
|
||||||
|
ViewModel.KeyboardHotkey.CycleInputDevicePlayer4 = buttonValue.AsHidType<Key>();
|
||||||
|
break;
|
||||||
|
case "CycleInputDevicePlayer5":
|
||||||
|
ViewModel.KeyboardHotkey.CycleInputDevicePlayer5 = buttonValue.AsHidType<Key>();
|
||||||
|
break;
|
||||||
|
case "CycleInputDevicePlayer6":
|
||||||
|
ViewModel.KeyboardHotkey.CycleInputDevicePlayer6 = buttonValue.AsHidType<Key>();
|
||||||
|
break;
|
||||||
|
case "CycleInputDevicePlayer7":
|
||||||
|
ViewModel.KeyboardHotkey.CycleInputDevicePlayer7 = buttonValue.AsHidType<Key>();
|
||||||
|
break;
|
||||||
|
case "CycleInputDevicePlayer8":
|
||||||
|
ViewModel.KeyboardHotkey.CycleInputDevicePlayer8 = buttonValue.AsHidType<Key>();
|
||||||
|
break;
|
||||||
|
case "CycleInputDeviceHandheld":
|
||||||
|
ViewModel.KeyboardHotkey.CycleInputDeviceHandheld = buttonValue.AsHidType<Key>();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<UserControl
|
<UserControl
|
||||||
x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsInputView"
|
x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsInputView"
|
||||||
xmlns="https://github.com/avaloniaui"
|
xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
@ -22,9 +22,16 @@
|
|||||||
<Panel
|
<Panel
|
||||||
Margin="10">
|
Margin="10">
|
||||||
<Grid RowDefinitions="Auto,*,Auto">
|
<Grid RowDefinitions="Auto,*,Auto">
|
||||||
<views:InputView
|
<StackPanel>
|
||||||
Grid.Row="0"
|
<!--
|
||||||
Name="InputView" />
|
Opacity="{Binding PanelOpacityInput}">
|
||||||
|
|
||||||
|
IsEnabled="{Binding !EnableConfigGlobal}">
|
||||||
|
-->
|
||||||
|
<views:InputView
|
||||||
|
Grid.Row="0"
|
||||||
|
Name="InputView" />
|
||||||
|
</StackPanel>
|
||||||
<StackPanel
|
<StackPanel
|
||||||
Orientation="Vertical"
|
Orientation="Vertical"
|
||||||
Grid.Row="2">
|
Grid.Row="2">
|
||||||
@ -34,6 +41,13 @@
|
|||||||
<StackPanel
|
<StackPanel
|
||||||
Orientation="Horizontal"
|
Orientation="Horizontal"
|
||||||
Spacing="10">
|
Spacing="10">
|
||||||
|
<CheckBox
|
||||||
|
ToolTip.Tip="{ext:Locale UseGlobalInputTooltip}"
|
||||||
|
MinWidth="0"
|
||||||
|
IsChecked="{Binding UseGlobalInputConfig}">
|
||||||
|
<TextBlock
|
||||||
|
Text="{ext:Locale SettingsTabInputUseGlobalInput}" />
|
||||||
|
</CheckBox>
|
||||||
<CheckBox
|
<CheckBox
|
||||||
ToolTip.Tip="{ext:Locale DockModeToggleTooltip}"
|
ToolTip.Tip="{ext:Locale DockModeToggleTooltip}"
|
||||||
MinWidth="0"
|
MinWidth="0"
|
||||||
|
@ -157,6 +157,32 @@
|
|||||||
</ComboBox>
|
</ComboBox>
|
||||||
<TextBlock Classes="globalConfigMarker" IsVisible="{Binding IsGameTitleNotNull}" />
|
<TextBlock Classes="globalConfigMarker" IsVisible="{Binding IsGameTitleNotNull}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
<Separator Height="1" Margin="0,15,0,15" />
|
||||||
|
<TextBlock Classes="h1" Text="{ext:Locale SettingsTabUIControllerOverlay}" />
|
||||||
|
<StackPanel Margin="10,0,0,0" Orientation="Vertical" Spacing="10">
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<TextBlock VerticalAlignment="Center"
|
||||||
|
Text="{ext:Locale SettingsTabUIControllerOverlayGameStartDuration}"
|
||||||
|
Width="200" />
|
||||||
|
<NumericUpDown Value="{Binding ControllerOverlayGameStartDuration}"
|
||||||
|
Minimum="0"
|
||||||
|
Maximum="30"
|
||||||
|
Increment="1"
|
||||||
|
FormatString="0"
|
||||||
|
ToolTip.Tip="{ext:Locale SettingsTabUIControllerOverlayGameStartDurationTooltip}" />
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<TextBlock VerticalAlignment="Center"
|
||||||
|
Text="{ext:Locale SettingsTabUIControllerOverlayInputCycleDuration}"
|
||||||
|
Width="200" />
|
||||||
|
<NumericUpDown Value="{Binding ControllerOverlayInputCycleDuration}"
|
||||||
|
Minimum="0"
|
||||||
|
Maximum="30"
|
||||||
|
Increment="1"
|
||||||
|
FormatString="0"
|
||||||
|
ToolTip.Tip="{ext:Locale SettingsTabUIControllerOverlayInputCycleDurationTooltip}" />
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<Border Grid.Column="1"
|
<Border Grid.Column="1"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<window:StyleableAppWindow xmlns="https://github.com/avaloniaui"
|
<window:StyleableAppWindow xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:helpers="using:Ryujinx.Ava.UI.Helpers"
|
xmlns:helpers="using:Ryujinx.Ava.UI.Helpers"
|
||||||
@ -17,65 +17,277 @@
|
|||||||
<window:StyleableAppWindow.DataContext>
|
<window:StyleableAppWindow.DataContext>
|
||||||
<viewModels:CompatibilityViewModel />
|
<viewModels:CompatibilityViewModel />
|
||||||
</window:StyleableAppWindow.DataContext>
|
</window:StyleableAppWindow.DataContext>
|
||||||
<Grid RowDefinitions="Auto,*">
|
<Grid RowDefinitions="Auto,Auto,*">
|
||||||
<Grid Grid.Row="0" ColumnDefinitions="Auto,*,Auto,Auto" Name="FlushControls">
|
|
||||||
<controls:RyujinxLogo
|
<!-- UI FlushControls -->
|
||||||
|
<Grid Grid.Row="0" ColumnDefinitions="Auto,*,Auto,Auto,Auto" Name="FlushControls">
|
||||||
|
<controls:RyujinxLogo
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Margin="15, 0, 7, 0"
|
Margin="15, 0, 7, 0"
|
||||||
ToolTip.Tip="{ext:WindowTitle CompatibilityListTitle, False}"/>
|
ToolTip.Tip="{ext:WindowTitle CompatibilityListTitle, False}"/>
|
||||||
<TextBox Name="SearchBoxFlush" Grid.Column="1" Margin="0, 5, 0, 5" HorizontalAlignment="Stretch" Watermark="{ext:Locale CompatibilityListSearchBoxWatermarkWithCount}" TextChanged="TextBox_OnTextChanged" />
|
<TextBox Name="SearchBoxFlush" Grid.Column="1" Margin="0, 5, 0, 5" HorizontalAlignment="Stretch" Watermark="{ext:Locale CompatibilityListSearchBoxWatermarkWithCount}" TextChanged="TextBox_OnTextChanged" />
|
||||||
|
<StackPanel Grid.Column="2" Orientation="Horizontal" Margin="10, 5, 0, 5">
|
||||||
|
<TextBlock
|
||||||
|
Margin="10,0"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
DockPanel.Dock="Right"
|
||||||
|
Text="{ext:Locale CommonSort}" />
|
||||||
|
<DropDownButton
|
||||||
|
Width="150"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Content="{Binding SortName}"
|
||||||
|
DockPanel.Dock="Right">
|
||||||
|
<DropDownButton.Flyout>
|
||||||
|
<Flyout Placement="Bottom">
|
||||||
|
<StackPanel
|
||||||
|
Margin="0"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
Orientation="Vertical">
|
||||||
|
<StackPanel>
|
||||||
|
<RadioButton
|
||||||
|
Checked="Sort_Name_Checked"
|
||||||
|
Content="{ext:Locale GameListSortStatusNameAscending}"
|
||||||
|
GroupName="Sort"
|
||||||
|
IsChecked="{Binding IsSortedByTitle, Mode=OneTime}"
|
||||||
|
Tag="0" />
|
||||||
|
<RadioButton
|
||||||
|
Checked="Sort_Name_Checked"
|
||||||
|
Content="{ext:Locale GameListSortStatusNameDescending}"
|
||||||
|
GroupName="Sort"
|
||||||
|
Tag="1" />
|
||||||
|
</StackPanel>
|
||||||
|
<Border
|
||||||
|
Width="60"
|
||||||
|
Height="2"
|
||||||
|
Margin="5"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
BorderBrush="White"
|
||||||
|
BorderThickness="0,1,0,0">
|
||||||
|
<Separator Height="0" HorizontalAlignment="Stretch" />
|
||||||
|
</Border>
|
||||||
|
<RadioButton
|
||||||
|
Checked="Sort_Status_Checked"
|
||||||
|
Content="{ext:Locale GameListSortStatusDisable}"
|
||||||
|
GroupName="Order"
|
||||||
|
IsChecked="{Binding IsSortedByStatus, Mode=OneTime}"
|
||||||
|
Tag="0" />
|
||||||
|
<RadioButton
|
||||||
|
Checked="Sort_Status_Checked"
|
||||||
|
Content="{ext:Locale GameListSortStatusAscending}"
|
||||||
|
GroupName="Order"
|
||||||
|
Tag="1" />
|
||||||
|
<RadioButton
|
||||||
|
Checked="Sort_Status_Checked"
|
||||||
|
Content="{ext:Locale GameListSortStatusDescending}"
|
||||||
|
GroupName="Order"
|
||||||
|
Tag="2" />
|
||||||
|
</StackPanel>
|
||||||
|
</Flyout>
|
||||||
|
</DropDownButton.Flyout>
|
||||||
|
</DropDownButton>
|
||||||
|
</StackPanel>
|
||||||
|
<CheckBox Grid.Column="3" Margin="7, 0, 0, 0" IsChecked="{Binding OnlyShowOwnedGames}" />
|
||||||
|
<TextBlock Grid.Column="4" Padding="0, 0, 138, 0" Margin="-10, 0, 18, 0" Text="{ext:Locale CompatibilityListOnlyShowOwnedGames}" />
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<!-- UI NormalControls -->
|
||||||
|
<Grid Grid.Row="0" ColumnDefinitions="*,Auto,Auto,Auto" Name="NormalControls">
|
||||||
|
<TextBox Name="SearchBoxNormal" Grid.Column="0" Margin="15, 0, 0, 5" HorizontalAlignment="Stretch" Watermark="{ext:Locale CompatibilityListSearchBoxWatermarkWithCount}" TextChanged="TextBox_OnTextChanged" />
|
||||||
|
<StackPanel Grid.Column="1" Orientation="Horizontal" Margin="10, 0, 5, 5">
|
||||||
|
<TextBlock
|
||||||
|
Margin="10,0"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
DockPanel.Dock="Right"
|
||||||
|
Text="{ext:Locale CommonSort}" />
|
||||||
|
<DropDownButton
|
||||||
|
Width="150"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Content="{Binding SortName}"
|
||||||
|
DockPanel.Dock="Right">
|
||||||
|
<DropDownButton.Flyout>
|
||||||
|
<Flyout Placement="Bottom">
|
||||||
|
<StackPanel
|
||||||
|
Margin="0"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
Orientation="Vertical">
|
||||||
|
<StackPanel>
|
||||||
|
<RadioButton
|
||||||
|
Checked="Sort_Name_Checked"
|
||||||
|
Content="{ext:Locale GameListSortStatusNameAscending}"
|
||||||
|
GroupName="Sort"
|
||||||
|
IsChecked="{Binding IsSortedByTitle, Mode=OneTime}"
|
||||||
|
Tag="0" />
|
||||||
|
<RadioButton
|
||||||
|
Checked="Sort_Name_Checked"
|
||||||
|
Content="{ext:Locale GameListSortStatusNameDescending}"
|
||||||
|
GroupName="Sort"
|
||||||
|
Tag="1" />
|
||||||
|
</StackPanel>
|
||||||
|
<Border
|
||||||
|
Width="60"
|
||||||
|
Height="2"
|
||||||
|
Margin="5"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
BorderBrush="White"
|
||||||
|
BorderThickness="0,1,0,0">
|
||||||
|
<Separator Height="0" HorizontalAlignment="Stretch" />
|
||||||
|
</Border>
|
||||||
|
<RadioButton
|
||||||
|
Checked="Sort_Status_Checked"
|
||||||
|
Content="{ext:Locale GameListSortStatusDisable}"
|
||||||
|
GroupName="Order"
|
||||||
|
IsChecked="{Binding IsSortedByStatus, Mode=OneTime}"
|
||||||
|
Tag="0" />
|
||||||
|
<RadioButton
|
||||||
|
Checked="Sort_Status_Checked"
|
||||||
|
Content="{ext:Locale GameListSortStatusAscending}"
|
||||||
|
GroupName="Order"
|
||||||
|
Tag="1" />
|
||||||
|
<RadioButton
|
||||||
|
Checked="Sort_Status_Checked"
|
||||||
|
Content="{ext:Locale GameListSortStatusDescending}"
|
||||||
|
GroupName="Order"
|
||||||
|
Tag="2" />
|
||||||
|
</StackPanel>
|
||||||
|
</Flyout>
|
||||||
|
</DropDownButton.Flyout>
|
||||||
|
</DropDownButton>
|
||||||
|
</StackPanel>
|
||||||
<CheckBox Grid.Column="2" Margin="7, 0, 0, 0" IsChecked="{Binding OnlyShowOwnedGames}" />
|
<CheckBox Grid.Column="2" Margin="7, 0, 0, 0" IsChecked="{Binding OnlyShowOwnedGames}" />
|
||||||
<TextBlock Grid.Column="3" Padding="0, 0, 138, 0" Margin="-10, 0, 18, 0" Text="{ext:Locale CompatibilityListOnlyShowOwnedGames}" />
|
<TextBlock Grid.Column="3" Padding="0, 0, 1, 0" Margin="-10, 0, 18, 0" Text="{ext:Locale CompatibilityListOnlyShowOwnedGames}" />
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid Grid.Row="0" ColumnDefinitions="*,Auto,Auto" Name="NormalControls">
|
|
||||||
<TextBox Name="SearchBoxNormal" Grid.Column="0" Margin="15, 0, 0, 5" HorizontalAlignment="Stretch" Watermark="{ext:Locale CompatibilityListSearchBoxWatermark}" TextChanged="TextBox_OnTextChanged" />
|
<!-- Description Field Above ScrollViewer -->
|
||||||
<CheckBox Grid.Column="1" Margin="7, 0, 0, 0" IsChecked="{Binding OnlyShowOwnedGames}" />
|
<Grid Grid.Row="1" ColumnDefinitions="*,Auto" Margin="10, 5, 10, 5">
|
||||||
<TextBlock Grid.Column="2" Padding="0, 0, 1, 0" Margin="-10, 0, 18, 0" Text="{ext:Locale CompatibilityListOnlyShowOwnedGames}" />
|
<Grid Grid.Column="0">
|
||||||
|
<Border Classes="listbox-item-style">
|
||||||
|
<Grid MinWidth="800"
|
||||||
|
ColumnDefinitions="Auto,Auto,Auto,*,Auto"
|
||||||
|
Background="Transparent">
|
||||||
|
<TextBlock Grid.Column="0"
|
||||||
|
Text="{ext:Locale CompatibilityListGamesAndApplications}"
|
||||||
|
FontWeight="Bold"
|
||||||
|
Width="525"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
<TextBlock Grid.Column="1"
|
||||||
|
Text="ID"
|
||||||
|
FontWeight="Bold"
|
||||||
|
Width="135"
|
||||||
|
Padding="7, 0, 0, 0"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
<TextBlock Grid.Column="2"
|
||||||
|
Padding="7, 0"
|
||||||
|
Text="{ext:Locale CompatibilityListStatus}"
|
||||||
|
FontWeight="Bold"
|
||||||
|
Width="100"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
TextWrapping="NoWrap" />
|
||||||
|
<TextBlock Grid.Column="3"
|
||||||
|
Text="{ext:Locale CompatibilityListDescription}"
|
||||||
|
FontWeight="Bold"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
TextWrapping="WrapWithOverflow" />
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
<Grid Grid.Column="1">
|
||||||
|
<DropDownButton
|
||||||
|
Width="80"
|
||||||
|
Height="35"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Content="Info"
|
||||||
|
DockPanel.Dock="Right">
|
||||||
|
<DropDownButton.Flyout>
|
||||||
|
<Flyout Placement="Bottom">
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
Padding="0,5"
|
||||||
|
Text="Compatibility verified:" />
|
||||||
|
<TextBlock
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
Foreground="{Binding IsStringPlayable, Converter={x:Static helpers:PlayabilityStatusConverter.Shared}}"
|
||||||
|
Text="{Binding PlayableInfoText }" />
|
||||||
|
<TextBlock
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
Foreground="{Binding IsStringInGame, Converter={x:Static helpers:PlayabilityStatusConverter.Shared}}"
|
||||||
|
Text="{Binding InGameInfoText }" />
|
||||||
|
<TextBlock
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
Foreground="{Binding IsStringMenus, Converter={x:Static helpers:PlayabilityStatusConverter.Shared}}"
|
||||||
|
Text="{Binding MenusInfoText }" />
|
||||||
|
<TextBlock
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
Foreground="{Binding IsStringBoots, Converter={x:Static helpers:PlayabilityStatusConverter.Shared}}"
|
||||||
|
Text="{Binding BootsInfoText }" />
|
||||||
|
<TextBlock
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
Foreground="{Binding IsStringNothing, Converter={x:Static helpers:PlayabilityStatusConverter.Shared}}"
|
||||||
|
Text="{Binding NothingInfoText }" />
|
||||||
|
</StackPanel>
|
||||||
|
</Flyout>
|
||||||
|
</DropDownButton.Flyout>
|
||||||
|
</DropDownButton>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
</Grid>
|
</Grid>
|
||||||
<ScrollViewer Grid.Row="1">
|
|
||||||
<ListBox Margin="12, 0, 13, 0"
|
<!-- List of compatible games -->
|
||||||
Background="Transparent"
|
<ScrollViewer Grid.Row="2">
|
||||||
ItemsSource="{Binding CurrentEntries}">
|
<ListBox Margin="12, 0, 13, 0"
|
||||||
<ListBox.ItemTemplate>
|
Background="Transparent"
|
||||||
<DataTemplate DataType="{x:Type systems:CompatibilityEntry}">
|
ItemsSource="{Binding CurrentEntries}">
|
||||||
<Grid MinWidth="800"
|
<ListBox.ItemTemplate>
|
||||||
Margin="10"
|
<DataTemplate DataType="{x:Type systems:CompatibilityEntry}">
|
||||||
ColumnDefinitions="Auto,Auto,Auto,*"
|
<Grid MinWidth="800"
|
||||||
Background="Transparent"
|
Margin="10"
|
||||||
ToolTip.Tip="{Binding LocalizedLastUpdated}">
|
ColumnDefinitions="Auto,Auto,Auto,*"
|
||||||
<TextBlock Grid.Column="0"
|
Background="Transparent"
|
||||||
Text="{Binding GameName}"
|
ToolTip.Tip="{Binding LocalizedLastUpdated}">
|
||||||
Width="525"
|
<TextBlock Grid.Column="0"
|
||||||
VerticalAlignment="Center"
|
Text="{Binding GameName}"
|
||||||
HorizontalAlignment="Center"
|
Width="525"
|
||||||
TextWrapping="Wrap" />
|
VerticalAlignment="Center"
|
||||||
<TextBlock Grid.Column="1"
|
HorizontalAlignment="Center"
|
||||||
Width="135"
|
TextWrapping="Wrap" />
|
||||||
Padding="7, 0, 0, 0"
|
<TextBlock Grid.Column="1"
|
||||||
FontFamily="{StaticResource JetBrainsMono}"
|
Width="135"
|
||||||
Text="{Binding FormattedTitleId}"
|
Padding="7, 0, 0, 0"
|
||||||
VerticalAlignment="Center"
|
FontFamily="{StaticResource JetBrainsMono}"
|
||||||
HorizontalAlignment="Center"
|
Text="{Binding FormattedTitleId}"
|
||||||
TextWrapping="Wrap" />
|
VerticalAlignment="Center"
|
||||||
<TextBlock Grid.Column="2"
|
HorizontalAlignment="Center"
|
||||||
Padding="7, 0"
|
TextWrapping="Wrap" />
|
||||||
Text="{Binding LocalizedStatus}"
|
<TextBlock Grid.Column="2"
|
||||||
Width="90"
|
Padding="7, 0"
|
||||||
Background="Transparent"
|
Text="{Binding LocalizedStatus}"
|
||||||
ToolTip.Tip="{Binding LocalizedStatusDescription}"
|
Width="100"
|
||||||
Foreground="{Binding Status, Converter={x:Static helpers:PlayabilityStatusConverter.Shared}}"
|
Background="Transparent"
|
||||||
VerticalAlignment="Center"
|
ToolTip.Tip="{Binding LocalizedStatusDescription}"
|
||||||
HorizontalAlignment="Center"
|
Foreground="{Binding Status, Converter={x:Static helpers:PlayabilityStatusConverter.Shared}}"
|
||||||
TextWrapping="NoWrap" />
|
VerticalAlignment="Center"
|
||||||
<TextBlock Grid.Column="3"
|
HorizontalAlignment="Center"
|
||||||
Text="{Binding FormattedIssueLabels}"
|
TextWrapping="NoWrap" />
|
||||||
VerticalAlignment="Center"
|
<TextBlock Grid.Column="3"
|
||||||
HorizontalAlignment="Left"
|
Text="{Binding FormattedIssueLabels}"
|
||||||
TextWrapping="WrapWithOverflow" />
|
VerticalAlignment="Center"
|
||||||
</Grid>
|
HorizontalAlignment="Left"
|
||||||
</DataTemplate>
|
TextWrapping="WrapWithOverflow" />
|
||||||
</ListBox.ItemTemplate>
|
</Grid>
|
||||||
</ListBox>
|
</DataTemplate>
|
||||||
|
</ListBox.ItemTemplate>
|
||||||
|
</ListBox>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
<Grid></Grid>
|
<Grid></Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
using Ryujinx.Ava.Systems.Configuration;
|
using Ryujinx.Ava.Systems.Configuration;
|
||||||
using Ryujinx.Ava.UI.ViewModels;
|
using Ryujinx.Ava.UI.ViewModels;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@ -42,5 +43,28 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
|
|
||||||
cvm.Search(searchBox.Text);
|
cvm.Search(searchBox.Text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Sort_Name_Checked(object sender, RoutedEventArgs args)
|
||||||
|
{
|
||||||
|
if (sender is RadioButton { Tag: string sortStrategy })
|
||||||
|
{
|
||||||
|
if (DataContext is not CompatibilityViewModel cvm)
|
||||||
|
return;
|
||||||
|
|
||||||
|
cvm.NameSorting(int.Parse(sortStrategy));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Sort_Status_Checked(object sender, RoutedEventArgs args)
|
||||||
|
{
|
||||||
|
if (sender is RadioButton { Tag: string sortStrategy })
|
||||||
|
{
|
||||||
|
if (DataContext is not CompatibilityViewModel cvm)
|
||||||
|
return;
|
||||||
|
|
||||||
|
cvm.StatusSorting(int.Parse(sortStrategy));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -120,30 +120,34 @@
|
|||||||
IconSource="Code" />
|
IconSource="Code" />
|
||||||
</ui:NavigationView.MenuItems>
|
</ui:NavigationView.MenuItems>
|
||||||
</ui:NavigationView>
|
</ui:NavigationView>
|
||||||
|
<Grid Grid.Row="3"
|
||||||
<ReversibleStackPanel
|
ColumnDefinitions="Auto,*,Auto">
|
||||||
Grid.Row="2"
|
<StackPanel Grid.Column="0" Orientation="Horizontal" Margin="10" Spacing="10">
|
||||||
Margin="10"
|
<Button
|
||||||
Spacing="10"
|
IsVisible="{Binding !IsGameRunning}"
|
||||||
Orientation="Horizontal"
|
Content="{ext:Locale UserProfilesDelete}"
|
||||||
HorizontalAlignment="Right"
|
Command="{Binding DeleteConfigGame}"
|
||||||
ReverseOrder="{x:Static helper:RunningPlatform.IsMacOS}">
|
Classes="red"/>
|
||||||
<Button
|
</StackPanel>
|
||||||
Content="{ext:Locale SettingsButtonSave}"
|
<ReversibleStackPanel
|
||||||
Command="{Binding SaveUserConfig}" />
|
Grid.Column="2"
|
||||||
<Button
|
Margin="10"
|
||||||
HotKey="Escape"
|
Spacing="10"
|
||||||
Content="{ext:Locale SettingsButtonClose}"
|
Orientation="Horizontal"
|
||||||
Command="{Binding CancelButton}" />
|
HorizontalAlignment="Right"
|
||||||
<Button
|
ReverseOrder="{x:Static helper:RunningPlatform.IsMacOS}">
|
||||||
IsVisible="{Binding IsGameRunning}"
|
<Button
|
||||||
Content="{ext:Locale SettingsButtonApply}"
|
Classes="accent"
|
||||||
Command="{Binding ApplyButton}" />
|
Content="{ext:Locale SettingsButtonSave}"
|
||||||
<Button
|
Command="{Binding SaveUserConfig}" />
|
||||||
IsVisible="{Binding !IsGameRunning}"
|
<Button
|
||||||
Content="{ext:Locale UserProfilesDelete}"
|
HotKey="Escape"
|
||||||
Command="{Binding DeleteConfigGame}"
|
Content="{ext:Locale SettingsButtonClose}"
|
||||||
Classes="red"/>
|
Command="{Binding CancelButton}" />
|
||||||
</ReversibleStackPanel>
|
<Button
|
||||||
|
Content="{ext:Locale SettingsButtonApply}"
|
||||||
|
Command="{Binding ApplyButton}" />
|
||||||
|
</ReversibleStackPanel>
|
||||||
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</window:StyleableAppWindow>
|
</window:StyleableAppWindow>
|
||||||
|
@ -28,6 +28,8 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
ViewModel.CloseWindow += Close;
|
ViewModel.CloseWindow += Close;
|
||||||
ViewModel.SaveSettingsEvent += SaveSettings;
|
ViewModel.SaveSettingsEvent += SaveSettings;
|
||||||
|
|
||||||
|
ViewModel.LocalGlobalInputSwitchEvent += ToggleLocalGlobalInput;
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
Load();
|
Load();
|
||||||
}
|
}
|
||||||
@ -37,6 +39,11 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
InputPage.InputView?.SaveCurrentProfile();
|
InputPage.InputView?.SaveCurrentProfile();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ToggleLocalGlobalInput(bool enableConfigGlobal)
|
||||||
|
{
|
||||||
|
InputPage.InputView?.ToggleLocalGlobalInput(enableConfigGlobal);
|
||||||
|
}
|
||||||
|
|
||||||
private void Load()
|
private void Load()
|
||||||
{
|
{
|
||||||
Pages.Children.Clear();
|
Pages.Children.Clear();
|
||||||
@ -90,6 +97,7 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
|
|
||||||
protected override void OnClosing(WindowClosingEventArgs e)
|
protected override void OnClosing(WindowClosingEventArgs e)
|
||||||
{
|
{
|
||||||
|
Program.UseExtraConfig = false;
|
||||||
InputPage.Dispose(); // You need to unload the gamepad settings, otherwise the controls will be blocked
|
InputPage.Dispose(); // You need to unload the gamepad settings, otherwise the controls will be blocked
|
||||||
base.OnClosing(e);
|
base.OnClosing(e);
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls"
|
xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls"
|
||||||
xmlns:main="clr-namespace:Ryujinx.Ava.UI.Views.Main"
|
xmlns:main="clr-namespace:Ryujinx.Ava.UI.Views.Main"
|
||||||
xmlns:viewsMisc="clr-namespace:Ryujinx.Ava.UI.Views.Misc"
|
xmlns:viewsMisc="clr-namespace:Ryujinx.Ava.UI.Views.Misc"
|
||||||
|
xmlns:overlayControls="clr-namespace:Ryujinx.Ava.UI.Controls"
|
||||||
Cursor="{Binding Cursor}"
|
Cursor="{Binding Cursor}"
|
||||||
Title="{Binding Title}"
|
Title="{Binding Title}"
|
||||||
WindowState="{Binding WindowState}"
|
WindowState="{Binding WindowState}"
|
||||||
@ -178,5 +179,7 @@
|
|||||||
Name="StatusBarView"
|
Name="StatusBarView"
|
||||||
Grid.Row="2" />
|
Grid.Row="2" />
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</window:StyleableAppWindow>
|
</window:StyleableAppWindow>
|
||||||
|
@ -213,13 +213,13 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Application_Opened(object sender, ApplicationOpenedEventArgs args)
|
public async void Application_Opened(object sender, ApplicationOpenedEventArgs args)
|
||||||
{
|
{
|
||||||
if (args.Application != null)
|
if (args.Application != null)
|
||||||
{
|
{
|
||||||
ViewModel.SelectedIcon = args.Application.Icon;
|
ViewModel.SelectedIcon = args.Application.Icon;
|
||||||
|
|
||||||
ViewModel.LoadApplication(args.Application).Wait();
|
await ViewModel.LoadApplication(args.Application);
|
||||||
}
|
}
|
||||||
|
|
||||||
args.Handled = true;
|
args.Handled = true;
|
||||||
|
Reference in New Issue
Block a user