Compare commits

...

18 Commits

Author SHA1 Message Date
9d83dfd19c misc: [ci skip] Missed the property part of _chosenProfile 2025-06-09 17:59:40 -05:00
ce31a47934 misc: Code styling changes & cleanups 2025-06-09 17:57:26 -05:00
d31d1f91cf Added the ability to switch between local and global input in the user configuration
See merge request ryubing/ryujinx!8
2025-06-09 17:24:24 -05:00
ef02194a77 Update: Compatibility list
See merge request ryubing/ryujinx!29
2025-06-09 02:54:45 -05:00
a16764d191 Moved "Graphics Backend Multitreading" item to Graphics API & Optimization section
See merge request ryubing/ryujinx!13
2025-06-09 02:37:49 -05:00
5108ab790f UI: RPC: [ci skip] Add BL2, BLTPS, and Minecraft Dungeons RPC images 2025-06-09 01:47:57 -05:00
71dc71fee8 infra: [ci skip] Remove duplicate GLI install in canary CI 2025-06-08 22:37:21 -05:00
c95bf748b2 infra: Update to Ryujinx.LibHac 0.20.0
This is identical to the previous version, it's just on NuGet.org so we can comment out the LibHacAlpha source in nuget.config.
2025-06-08 22:31:32 -05:00
b5e9acc50b misc: [ci skip] Cause GitHub fallback properly 2025-06-08 21:06:34 -05:00
e3fba4e32f docs: compat: further clarify the issue with 'FANTASY LIFE i: The Girl Who Steals Time' with 'crash' and 'vulkan-backend-bug' labels. 2025-06-08 20:44:01 -05:00
efa25d471e docs: compat: ingame: FANTASY LIFE i: The Girl Who Steals Time 2025-06-08 20:41:51 -05:00
b37aa61e47 infra: Remove GitHub uploading from Canary CI workflows 2025-06-08 17:55:36 -05:00
8feeb977b7 infra: [ci skip] fix canary changelog generation 2025-06-08 17:47:45 -05:00
b761a2c86d infra: Custom Update server instead of direct GitLab API calls
This reduces the amount of requests for an update from 3 if an update is needed, or 2 if not; to 1 if an update is needed, and none if an update is not. The difference comes from using this update server to check if an update is needed, and not GETing a snippet content for the release channels.
2025-06-08 17:37:34 -05:00
693837dca7 infra: [ci skip] make the canary release notes look nicer 2025-06-05 23:07:02 -05:00
70abff072b canary CI: checkout code before trying to get current revision 2025-06-05 20:56:17 -05:00
1e861b99a9 misc: Update LibHac
See merge request ryubing/libhac!3
2025-06-05 20:45:35 -05:00
13e404bde0 infra: [ci skip] Move tag creation to the end of the build process in CI 2025-06-05 01:57:21 -05:00
30 changed files with 994 additions and 429 deletions

View File

@ -24,54 +24,6 @@ env:
RELEASE: 1
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:
name: Release for ${{ matrix.platform.name }}
runs-on: ${{ matrix.platform.os }}
@ -91,16 +43,6 @@ jobs:
- name: Overwrite csc problem matcher
run: echo "::add-matcher::.github/csc.json"
- 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: Get version info
id: version_info
@ -202,34 +144,7 @@ jobs:
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"
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"
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: |
# 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 }}
shell: bash
macos_release:
name: Release MacOS universal
@ -290,28 +205,15 @@ jobs:
./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
uses: ncipollo/release-action@v1
with:
name: "Canary ${{ steps.version_info.outputs.build_version }}"
artifacts: "publish_ava/*.tar.gz"
tag: ${{ steps.version_info.outputs.build_version }}
body: ""
omitBodyDuringUpdate: true
allowUpdates: true
replacesArtifacts: true
owner: ${{ secrets.RC_OWNER }}
repo: ${{ secrets.RC_CANARY_NAME }}
token: ${{ secrets.ALT_RELEASE_TOKEN }}
create_gitlab_release:
name: Create GitLab Release
runs-on: ubuntu-24.04
needs:
- tag
- macos_release
- release
steps:
- uses: actions/checkout@v4
- name: Get version info
id: version_info
run: |
@ -330,10 +232,14 @@ jobs:
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](https://git.ryujinx.app/ryubing/ryujinx/-/compare/Canary-${{ steps.version_info.outputs.prev_build_version }}...Canary-${{ steps.version_info.outputs.build_version }})**"
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"
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"

View File

@ -40,7 +40,7 @@
<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.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="Gommon" Version="2.7.1.1" />
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />

View File

@ -1847,6 +1847,131 @@
"zh_TW": "路徑"
}
},
{
"ID": "GameListSortStatusNameAscending",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "Όνομα: A-Z",
"en_US": "Title: A-Z",
"es_ES": "Título: A-Z",
"fr_FR": "Titre : A-Z",
"he_IL": "",
"it_IT": "Titolo: A-Z",
"ja_JP": "タイトルA-Z",
"ko_KR": "제목: A-Z",
"no_NO": "Tittel: A-Z",
"pl_PL": "Tytuł: A-Z",
"pt_BR": "Título: A-Z",
"ru_RU": "Название: А-Z",
"sv_SE": "Titel: A-Z",
"th_TH": "ชื่อเรื่อง: A-Z",
"tr_TR": "Başlık: A-Z",
"uk_UA": "Назва: A-Z",
"zh_CN": "标题A-Z",
"zh_TW": "標題A-Z"
}
},
{
"ID": "GameListSortStatusNameDescending",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "Τίτλος: Z-A",
"en_US": "Title: Z-A",
"es_ES": "Título: Z-A",
"fr_FR": "Titre : Z-A",
"he_IL": "",
"it_IT": "Titolo: Z-A",
"ja_JP": "タイトルZ-A",
"ko_KR": "제목: Z-A",
"no_NO": "Tittel: Z-A",
"pl_PL": "Tytuł: Z-A",
"pt_BR": "Título: Z-A",
"ru_RU": "Название: Z-A",
"sv_SE": "Titel: Z-A",
"th_TH": "ชื่อเรื่อง: Z-A",
"tr_TR": "Başlık: Z-A",
"uk_UA": "Назва: Z-A",
"zh_CN": "标题Z-A",
"zh_TW": "標題Z-A"
}
},
{
"ID": "GameListSortStatusDisable",
"Translations": {
"ar_SA": "",
"de_DE": "Status: Deaktiviert",
"el_GR": "Κατάσταση: Απενεργοποιημένο",
"en_US": "Status: Disabled",
"es_ES": "Estado: Desactivado",
"fr_FR": "Statut : Désactivé",
"he_IL": "",
"it_IT": "Stato: Disabilitato",
"ja_JP": "ステータス:無効",
"ko_KR": "상태: 비활성화됨",
"no_NO": "Status: Deaktivert",
"pl_PL": "Status: Wyłączony",
"pt_BR": "Status: Desativado",
"ru_RU": "Статус: Отключено",
"sv_SE": "Status: Inaktiverad",
"th_TH": "",
"tr_TR": "Durum: Devre Dışı",
"uk_UA": "Статус: Вимкнено",
"zh_CN": "状态:禁用",
"zh_TW": "狀態:停用"
}
},
{
"ID": "GameListSortStatusAscending",
"Translations": {
"ar_SA": "الحالة: تصاعدي",
"de_DE": "Status: Aufsteigend",
"el_GR": "Κατάσταση: Αναγόμενη",
"en_US": "Status: Ascending",
"es_ES": "",
"fr_FR": "Statut : Croissant",
"he_IL": "סטטוס: עולה",
"it_IT": "Stato: Crescente",
"ja_JP": "ステータス:昇順",
"ko_KR": "상태: 오름차순",
"no_NO": "Status: Stigende",
"pl_PL": "Stan: Rosnący",
"pt_BR": "Status: Crescente",
"ru_RU": "Статус: По возрастанию",
"sv_SE": "Status: Stigande",
"th_TH": "สถานะ: เพิ่มขึ้น",
"tr_TR": "Durum: Artan",
"uk_UA": "Статус: Зростання",
"zh_CN": "状态:升序",
"zh_TW": "狀態:遞增"
}
},
{
"ID": "GameListSortStatusDescending",
"Translations": {
"ar_SA": "الحالة: تنازلي",
"de_DE": "Status: Absteigend",
"el_GR": "Κατάσταση: Καθοδική",
"en_US": "Status: Descending",
"es_ES": "",
"fr_FR": "Statut : Décroissant",
"he_IL": "סטטוס: יורד",
"it_IT": "Stato: Decrescente",
"ja_JP": "ステータス:降順",
"ko_KR": "상태: 내림차순",
"no_NO": "Status: Synkende",
"pl_PL": "Stan: Malejący",
"pt_BR": "Status: Decrescente",
"ru_RU": "Статус: По Убыванию",
"sv_SE": "Status: Fallande",
"th_TH": "สถานะ: ลดลง",
"tr_TR": "Durum: Azalan",
"uk_UA": "Статус: Зменшення",
"zh_CN": "状态:降序",
"zh_TW": "狀態:遞減"
}
},
{
"ID": "GameListHeaderCompatibilityStatus",
"Translations": {
@ -5450,26 +5575,26 @@
{
"ID": "SettingsTabGraphicsAPI",
"Translations": {
"ar_SA": "API الرسومات ",
"de_DE": "Grafik-API",
"el_GR": "API Γραφικά",
"en_US": "Graphics API",
"es_ES": "API de gráficos",
"fr_FR": "API Graphique",
"he_IL": "ממשק גראפי",
"it_IT": "API grafica",
"ja_JP": "グラフィックスAPI",
"ko_KR": "그래픽 API",
"no_NO": "Grafikk API",
"pl_PL": "Graficzne API",
"pt_BR": "API gráfica",
"ru_RU": "Графические API",
"sv_SE": "Grafik-API",
"th_TH": "API กราฟฟิก",
"tr_TR": "Grafikler API",
"uk_UA": "Графічний API",
"zh_CN": "图形 API",
"zh_TW": "圖形 API"
"ar_SA": "API الرسومات و تحسين",
"de_DE": "Grafik-API & Optimierung",
"el_GR": "API Γραφικά & Βελτιστοποίηση",
"en_US": "Graphics API & Optimization",
"es_ES": "API de gráficos & Optimización",
"fr_FR": "API Graphique & Optimisation",
"he_IL": "ממשק גראפי & אופטימיזציה",
"it_IT": "API grafica & Ottimizzazione",
"ja_JP": "グラフィックスAPI&最適化",
"ko_KR": "그래픽 API & 최적화",
"no_NO": "Grafikk-API & Optimalisering",
"pl_PL": "Graficzne API & Optymalizacja",
"pt_BR": "API gráfica & Otimização",
"ru_RU": "Графический API & Оптимизация",
"sv_SE": "Grafik-API & Optimering",
"th_TH": "API กราฟฟิก & การเพิ่มประสิทธิภาพ",
"tr_TR": "Grafikler API & Optimizasyon",
"uk_UA": "Графічний API & Оптимізація",
"zh_CN": "图形 API & 优化",
"zh_TW": "圖形 API & 優化"
}
},
{
@ -6547,6 +6672,31 @@
"zh_TW": "輸入"
}
},
{
"ID": "SettingsTabInputUseGlobalInput",
"Translations": {
"ar_SA": "إدخال عالمي",
"de_DE": "Globale Eingabe",
"el_GR": "Παγκόσμια εισαγωγή",
"en_US": "Global Input",
"es_ES": "Entrada Global",
"fr_FR": "Saisie Globale",
"he_IL": "קלט גלובלי",
"it_IT": "Input Globale",
"ja_JP": "グローバル入力",
"ko_KR": "글로벌 입력",
"no_NO": "Global Inndata",
"pl_PL": "Globalny Wpis",
"pt_BR": "Entrada Global",
"ru_RU": "Глобальный Ввод",
"sv_SE": "Global Input",
"th_TH": "การป้อนข้อมูลแบบโกลบอล",
"tr_TR": "Küresel Girdi",
"uk_UA": "Глобальний Ввід",
"zh_CN": "全局输入",
"zh_TW": "全域輸入"
}
},
{
"ID": "SettingsTabInputEnableDockedMode",
"Translations": {
@ -16422,6 +16572,31 @@
"zh_TW": "瀏覽自訂 GUI 佈景主題"
}
},
{
"ID": "UseGlobalInputTooltip",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "If this option is enabled in custom settings, the global input configuration will be used.\n\nIn the global settings: you can enable or disable it as needed; this setting will be inherited by any new custom configurations created.",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "Если эта опция включена в пользовательских настройках, будет использована глобальная конфигурация ввода.\n\nВ глобальных настройках: переключите эту опцию по своему усмотрению, это будет унаследовано для вновь созданых пользовательских конфигураций",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "DockModeToggleTooltip",
"Translations": {
@ -24372,6 +24547,106 @@
"zh_TW": "開啟相容性列表"
}
},
{
"ID": "CompatibilityListGamesAndApplications",
"Translations": {
"ar_SA": "",
"de_DE": "Spiele & Anwendungen",
"el_GR": "Παιχνίδια και Εφαρμογές",
"en_US": "Games & Applications",
"es_ES": "Juegos y Aplicaciones",
"fr_FR": "Jeux et Applications",
"he_IL": "משחקים ואפליקציות",
"it_IT": "Giochi e Applicazioni",
"ja_JP": "ゲームとアプリケーション",
"ko_KR": "게임 및 애플리케이션",
"no_NO": "Spill og Applikasjoner",
"pl_PL": "Gry i Aplikacje",
"pt_BR": "Jogos e Aplicativos",
"ru_RU": "Игры и Приложения",
"sv_SE": "Spel och Applikationer",
"th_TH": "",
"tr_TR": "Oyunlar ve Uygulamalar",
"uk_UA": "Ігри та Додатки",
"zh_CN": "游戏和应用程序",
"zh_TW": "遊戲與應用程式"
}
},
{
"ID": "CompatibilityListStatus",
"Translations": {
"ar_SA": "الحالة",
"de_DE": "",
"el_GR": "Κατάσταση",
"en_US": "Status",
"es_ES": "Estado",
"fr_FR": "Statut",
"he_IL": "מצב",
"it_IT": "Stato",
"ja_JP": "状況",
"ko_KR": "상태",
"no_NO": "",
"pl_PL": "Stan",
"pt_BR": "Estado",
"ru_RU": "Статус",
"sv_SE": "",
"th_TH": "สถานะ",
"tr_TR": "Durum",
"uk_UA": "Статус",
"zh_CN": "状态",
"zh_TW": "狀態"
}
},
{
"ID": "CompatibilityListDescription",
"Translations": {
"ar_SA": "",
"de_DE": "Probleme und Merkmale",
"el_GR": "Προβλήματα και Χαρακτηριστικά",
"en_US": "Issues & Features",
"es_ES": "Problemas y Características",
"fr_FR": "Problèmes et Caractéristiques",
"he_IL": "",
"it_IT": "Problemi e Caratteristiche",
"ja_JP": "問題点と特徴",
"ko_KR": "문제점 및 특징",
"no_NO": "Problemer og Egenskaper",
"pl_PL": "Problemy i Cechy",
"pt_BR": "Problemas e Características",
"ru_RU": "Проблемы и Особенности",
"sv_SE": "Problem och Egenskaper",
"th_TH": "",
"tr_TR": "Sorunlar ve Özellikler",
"uk_UA": "Проблеми та Особливості",
"zh_CN": "问题和特性",
"zh_TW": "問題與特性"
}
},
{
"ID": "CompatibilityListInfo",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "Πληροφορίες",
"en_US": "Info",
"es_ES": "Información",
"fr_FR": "",
"he_IL": "מידע",
"it_IT": "",
"ja_JP": "情報",
"ko_KR": "정보",
"no_NO": "",
"pl_PL": "Informacja",
"pt_BR": "",
"ru_RU": "Инфо",
"sv_SE": "",
"th_TH": "",
"tr_TR": "Bilgi",
"uk_UA": "Інфо",
"zh_CN": "信息",
"zh_TW": "資訊"
}
},
{
"ID": "CompatibilityListOnlyShowOwnedGames",
"Translations": {

View File

@ -1125,6 +1125,7 @@
0100034012606000,"Family Mysteries: Poisonous Promises",audio;crash,menus,2021-11-26 12:35:06
010017C012726000,"Fantasy Friends",,playable,2022-10-17 19:42:39
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
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

1 title_id game_name labels status last_updated
1125 0100034012606000 Family Mysteries: Poisonous Promises audio;crash menus 2021-11-26 12:35:06
1126 010017C012726000 Fantasy Friends playable 2022-10-17 19:42:39
1127 0100767008502000 FANTASY HERO ~unsigned legacy~ playable 2022-07-26 12:28:52
1128 0100755017EE0000 FANTASY LIFE i: The Girl Who Steals Time gpu;crash;vulkan-backend-bug ingame 2025-06-08 20:41:00
1129 0100944003820000 Fantasy Strike online playable 2021-02-27 01:59:18
1130 01000E2012F6E000 Fantasy Tavern Sextet -Vol.1 New World Days- gpu;crash;Needs Update ingame 2022-12-05 16:48:00
1131 01005C10136CA000 Fantasy Tavern Sextet -Vol.2 Adventurer's Days- gpu;slow;crash ingame 2021-11-06 02:57:29

View File

@ -4,20 +4,7 @@
<packageSources>
<clear />
<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" />
<add key="RyubingPkgs" value="https://git.ryujinx.app/api/v4/projects/1/packages/nuget/index.json" />
<!-- Only needed when using pre-release versions of Ryujinx.LibHac. -->
<!--<add key="LibHacAlpha" value="https://git.ryujinx.app/api/v4/projects/17/packages/nuget/index.json" />-->
</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>
<!-- key value for <packageSource> should match key values from <packageSources> element -->
<packageSource key="nuget.org">
<package pattern="*" />
</packageSource>
<packageSource key="LibHacAlpha">
<package pattern="Ryujinx.LibHac" />
</packageSource>
</packageSourceMapping>
</configuration>

View File

@ -133,7 +133,6 @@ namespace Ryujinx.Common
"0100c1f0051b6000", // Donkey Kong Country: Tropical Freeze
"0100ed000d390000", // Dr. Kawashima's Brain Training
"010067b017588000", // Endless Ocean Luminous
"0100d2f00d5c0000", // Nintendo Switch Sports
"01006b5012b32000", // Part Time UFO
"0100704000B3A000", // Snipperclips
"01006a800016e000", // Super Smash Bros. Ultimate
@ -169,6 +168,8 @@ namespace Ryujinx.Common
"010056e00853a000", // A Hat in Time
"0100fd1014726000", // Baldurs Gate: Dark Alliance
"01008c2019598000", // Bluey: The Video Game
"010096f00ff22000", // Borderlands 2: Game of the Year Edition
"010007400ff24000", // Borderlands: The Pre-Sequel Ultimate Edition
"0100c6800b934000", // Brawlhalla
"0100dbf01000a000", // Burnout Paradise Remastered
"0100744001588000", // Cars 3: Driven to Win

View File

@ -372,6 +372,12 @@
<Setter Property="BorderThickness"
Value="2"/>
</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">
<Setter Property="Background"
Value="{DynamicResource AppListBackgroundColor}" />

View File

@ -1,20 +0,0 @@
using System.Text.Json.Serialization;
namespace Ryujinx.Ava.Common.Models.GitLab
{
public class GitLabReleaseAssetJsonResponse
{
[JsonPropertyName("links")]
public GitLabReleaseAssetLinkJsonResponse[] Links { get; set; }
public class GitLabReleaseAssetLinkJsonResponse
{
[JsonPropertyName("id")]
public long Id { get; set; }
[JsonPropertyName("name")]
public string AssetName { get; set; }
[JsonPropertyName("url")]
public string Url { get; set; }
}
}
}

View File

@ -1,19 +0,0 @@
using System.Text.Json.Serialization;
namespace Ryujinx.Ava.Common.Models.GitLab
{
public class GitLabReleasesJsonResponse
{
[JsonPropertyName("name")]
public string Name { get; set; }
[JsonPropertyName("tag_name")]
public string TagName { get; set; }
[JsonPropertyName("assets")]
public GitLabReleaseAssetJsonResponse Assets { get; set; }
}
[JsonSerializable(typeof(GitLabReleasesJsonResponse), GenerationMode = JsonSourceGenerationMode.Metadata)]
public partial class GitLabReleasesJsonSerializerContext : JsonSerializerContext;
}

View File

@ -35,6 +35,7 @@ namespace Ryujinx.Ava
public static string Version { get; private set; }
public static string ConfigurationPath { 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 UseHardwareAcceleration { 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))
{
@ -168,15 +170,10 @@ namespace Ryujinx.Ava
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)
{
ConfigurationPath = gameDir;
UseExtraConfig = true;
}
return gameDir;
@ -184,8 +181,6 @@ namespace Ryujinx.Ava
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 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;
// Check if graphics backend was overridden

View File

@ -461,7 +461,15 @@ namespace Ryujinx.Ava.Systems
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);
_viewModel.IsGameRunning = true;

View File

@ -556,7 +556,7 @@ namespace Ryujinx.Ava.Systems.AppLibrary
data.Favorite = appMetadata.Favorite;
data.TimePlayed = appMetadata.TimePlayed;
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();

View File

@ -15,7 +15,7 @@ namespace Ryujinx.Ava.Systems.Configuration
/// <summary>
/// The current version of the file format
/// </summary>
public const int CurrentVersion = 69;
public const int CurrentVersion = 70;
/// <summary>
/// Version of the configuration file format
@ -152,6 +152,11 @@ namespace Ryujinx.Ava.Systems.Configuration
/// </summary>
public bool MatchSystemTime { get; set; }
/// <summary>
/// Enable or disable use global input config (Independent from controllers binding)
/// </summary>
public bool UseInputGlobalConfig { get; set; }
/// <summary>
/// Enables or disables Docked Mode
/// </summary>

View File

@ -90,6 +90,7 @@ namespace Ryujinx.Ava.Systems.Configuration
System.TimeZone.Value = cff.SystemTimeZone;
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.UseInputGlobalConfig.Value = cff.UseInputGlobalConfig;
System.EnableDockedMode.Value = cff.DockedMode;
System.EnablePtc.Value = cff.EnablePtc;
System.EnableLowPowerPtc.Value = cff.EnableLowPowerPtc;
@ -146,7 +147,7 @@ namespace Ryujinx.Ava.Systems.Configuration
Hid.EnableMouse.Value = cff.EnableMouse;
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.InputConfig.Value = cff.InputConfig ?? [];
Hid.InputConfig.Value = cff.InputConfig ?? [] ;
Hid.RainbowSpeed.Value = cff.RainbowSpeed;
Multiplayer.LanInterfaceId.Value = cff.MultiplayerLanInterfaceId;

View File

@ -326,6 +326,12 @@ namespace Ryujinx.Ava.Systems.Configuration
/// </summary>
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>
/// Enables or disables Docked Mode
/// </summary>
@ -417,6 +423,8 @@ namespace Ryujinx.Ava.Systems.Configuration
SystemTimeOffset.LogChangesToValue(nameof(SystemTimeOffset));
MatchSystemTime = new ReactiveObject<bool>();
MatchSystemTime.LogChangesToValue(nameof(MatchSystemTime));
UseInputGlobalConfig = new ReactiveObject<bool>();
UseInputGlobalConfig.LogChangesToValue(nameof(UseInputGlobalConfig));
EnableDockedMode = new ReactiveObject<bool>();
EnableDockedMode.LogChangesToValue(nameof(EnableDockedMode));
EnablePtc = new ReactiveObject<bool>();
@ -761,6 +769,8 @@ namespace Ryujinx.Ava.Systems.Configuration
/// </summary>
public static ConfigurationState Instance { get; private set; }
public static ConfigurationState InstanceExtra{ get; private set; }
/// <summary>
/// The UI section
/// </summary>

View File

@ -15,12 +15,13 @@ namespace Ryujinx.Ava.Systems.Configuration
{
public static void Initialize()
{
if (Instance != null)
if (Instance != null || InstanceExtra!= null)
{
throw new InvalidOperationException("Configuration is already initialized");
}
Instance = new ConfigurationState();
InstanceExtra= new ConfigurationState();
}
public ConfigurationFileFormat ToFileFormat()
@ -54,6 +55,7 @@ namespace Ryujinx.Ava.Systems.Configuration
SystemTimeZone = System.TimeZone,
SystemTimeOffset = System.SystemTimeOffset,
MatchSystemTime = System.MatchSystemTime,
UseInputGlobalConfig = System.UseInputGlobalConfig,
DockedMode = System.EnableDockedMode,
EnableDiscordIntegration = EnableDiscordIntegration,
UpdateCheckerType = UpdateCheckerType,
@ -178,6 +180,7 @@ namespace Ryujinx.Ava.Systems.Configuration
System.Region.Value = Region.USA;
System.TimeZone.Value = "UTC";
System.SystemTimeOffset.Value = 0;
System.UseInputGlobalConfig.Value = false;
System.EnableDockedMode.Value = true;
EnableDiscordIntegration.Value = true;
UpdateCheckerType.Value = UpdaterType.PromptAtStartup;

View File

@ -1,15 +1,13 @@
using Gommon;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Common.Models.GitLab;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Common;
using Ryujinx.Common.Helper;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using System;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using System.Runtime.InteropServices;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
@ -17,7 +15,31 @@ namespace Ryujinx.Ava.Systems
{
internal static partial class Updater
{
private static GitLabReleaseChannels.ChannelType _currentGitLabReleaseChannel;
private static string CreateUpdateQueryUrl()
{
#pragma warning disable CS8524
var os = RunningPlatform.CurrentOS switch
#pragma warning restore CS8524
{
OperatingSystemType.MacOS => "mac",
OperatingSystemType.Linux => "linux",
OperatingSystemType.Windows => "win"
};
var arch = RunningPlatform.Architecture switch
{
Architecture.Arm64 => "arm",
Architecture.X64 => "amd64",
_ => null
};
if (arch is null)
return null;
var rc = ReleaseInformation.IsCanaryBuild ? "canary" : "stable";
return $"https://update.ryujinx.app/latest/query?os={os}&arch={arch}&rc={rc}";
}
private static async Task<Optional<(Version Current, Version Incoming)>> CheckGitLabVersionAsync(bool showVersionUpToDate = false)
{
@ -35,38 +57,41 @@ namespace Ryujinx.Ava.Systems
return default;
}
Logger.Info?.Print(LogClass.Application, "Checking for updates from https://git.ryujinx.app.");
if (CreateUpdateQueryUrl() is not {} updateUrl)
{
Logger.Error?.Print(LogClass.Application, "Could not determine URL for updates.");
_running = false;
return default;
}
Logger.Info?.Print(LogClass.Application, $"Checking for updates from {updateUrl}.");
// Get latest version number from GitLab API
using HttpClient jsonClient = ConstructHttpClient();
// GitLab instance is located in Ukraine. Connection times will vary across the world.
jsonClient.Timeout = TimeSpan.FromSeconds(10);
jsonClient.Timeout = TimeSpan.FromSeconds(10);
if (_currentGitLabReleaseChannel == null)
try
{
GitLabReleaseChannels releaseChannels = await GitLabReleaseChannels.GetAsync(jsonClient);
UpdaterResponse response =
await jsonClient.GetFromJsonAsync(updateUrl, UpdaterResponseJsonContext.Default.UpdaterResponse);
_currentGitLabReleaseChannel = ReleaseInformation.IsCanaryBuild
? releaseChannels.Canary
: releaseChannels.Stable;
Logger.Info?.Print(LogClass.Application, $"Loaded GitLab release channel for '{(ReleaseInformation.IsCanaryBuild ? "canary" : "stable")}'");
_changelogUrlFormat = _currentGitLabReleaseChannel.UrlFormat;
_buildVer = response.Tag;
_buildUrl = response.DownloadUrl;
_changelogUrlFormat = response.ReleaseUrlFormat;
}
catch (Exception e)
{
throw new AggregateException(
$"An error occurred when parsing JSON response from API ({e.GetType().AsFullNamePrettyString()}): {e.Message}",
e);
}
string fetchedJson = await jsonClient.GetStringAsync(_currentGitLabReleaseChannel.GetLatestReleaseApiUrl());
GitLabReleasesJsonResponse fetched = JsonHelper.Deserialize(fetchedJson, _glSerializerContext.GitLabReleasesJsonResponse);
_buildVer = fetched.TagName;
_buildUrl = fetched.Assets.Links
.FirstOrDefault(link =>
link.AssetName.StartsWith("ryujinx") && link.AssetName.EndsWith(_platformExt)
)?.Url;
// If build URL not found, assume no new update is available.
if (_buildUrl is null)
if (_buildUrl is null or "")
{
if (showVersionUpToDate)
{
@ -104,35 +129,17 @@ namespace Ryujinx.Ava.Systems
return (currentVersion, newVersion);
}
[JsonSerializable(typeof(UpdaterResponse))]
partial class UpdaterResponseJsonContext : JsonSerializerContext;
[JsonSerializable(typeof(GitLabReleaseChannels))]
partial class GitLabReleaseChannelPairContext : JsonSerializerContext;
public class GitLabReleaseChannels
public class UpdaterResponse
{
public static async Task<GitLabReleaseChannels> GetAsync(HttpClient httpClient)
=> await httpClient.GetFromJsonAsync(
"https://git.ryujinx.app/ryubing/ryujinx/-/snippets/1/raw/main/meta.json",
GitLabReleaseChannelPairContext.Default.GitLabReleaseChannels);
[JsonPropertyName("tag")] public string Tag { get; set; }
[JsonPropertyName("download_url")] public string DownloadUrl { get; set; }
[JsonPropertyName("web_url")] public string ReleaseUrl { get; set; }
[JsonPropertyName("stable")] public ChannelType Stable { get; set; }
[JsonPropertyName("canary")] public ChannelType Canary { get; set; }
public class ChannelType
{
[JsonPropertyName("id")] public long Id { get; set; }
[JsonPropertyName("group")] public string Group { get; set; }
[JsonPropertyName("project")] public string Project { get; set; }
public string UrlFormat => $"https://git.ryujinx.app/{ToString()}/-/releases/{{0}}";
public override string ToString() => $"{Group}/{Project}";
public string GetLatestReleaseApiUrl() =>
$"https://git.ryujinx.app/api/v4/projects/{Id}/releases/permalink/latest";
}
[JsonIgnore] public string ReleaseUrlFormat => ReleaseUrl.Replace(Tag, "{0}");
}
}
}

View File

@ -6,7 +6,6 @@ using ICSharpCode.SharpZipLib.Tar;
using ICSharpCode.SharpZipLib.Zip;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Common.Models.Github;
using Ryujinx.Ava.Common.Models.GitLab;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.Utilities;
using Ryujinx.Common;
@ -33,19 +32,14 @@ namespace Ryujinx.Ava.Systems
internal static partial class Updater
{
private static readonly GithubReleasesJsonSerializerContext _ghSerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
private static readonly GitLabReleasesJsonSerializerContext _glSerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
private static readonly string _platformExt = BuildPlatformExtension();
private static readonly string _homeDir = AppDomain.CurrentDomain.BaseDirectory;
private static readonly string _updateDir = Path.Combine(Path.GetTempPath(), "Ryujinx", "update");
private static readonly string _updatePublishDir = Path.Combine(_updateDir, "publish");
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 bool _updateSuccessful;
@ -168,7 +162,7 @@ namespace Ryujinx.Ava.Systems
HttpClient result = new();
// 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;
}

View File

@ -4,6 +4,7 @@ using Ryujinx.Ava.Systems.AppLibrary;
using System;
using System.Collections.Generic;
using System.Linq;
using Ryujinx.Ava.Common.Locale;
namespace Ryujinx.Ava.UI.ViewModels
{
@ -11,15 +12,37 @@ namespace Ryujinx.Ava.UI.ViewModels
{
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 string[] _ownedGameTitleIds = [];
private Func<CompatibilityEntry, object> _sortKeySelector = x => x.GameName; // Default sort by GameName
public IEnumerable<CompatibilityEntry> CurrentEntries => OnlyShowOwnedGames
? _currentEntries.Where(x =>
x.TitleId.Check(tid => _ownedGameTitleIds.ContainsIgnoreCase(tid)))
: _currentEntries;
public CompatibilityViewModel() { }
public CompatibilityViewModel() {}
private void AppCountUpdated(object _, ApplicationCountUpdatedEventArgs __)
=> _ownedGameTitleIds = _appLibrary.Applications.Keys.Select(x => x.ToString("X16")).ToArray();
@ -27,19 +50,29 @@ namespace Ryujinx.Ava.UI.ViewModels
public CompatibilityViewModel(ApplicationLibrary appLibrary)
{
_appLibrary = appLibrary;
AppCountUpdated(null, null);
CountByStatus();
_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()
{
GC.SuppressFinalize(this);
_appLibrary.ApplicationCountUpdated -= AppCountUpdated;
}
private bool _onlyShowOwnedGames = true;
private 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)
{
if (string.IsNullOrEmpty(searchTerm))
{
SetEntries(CompatibilityDatabase.Entries);
SortApply();
return;
}
SetEntries(CompatibilityDatabase.Entries.Where(x =>
x.GameName.ContainsIgnoreCase(searchTerm)
|| x.TitleId.Check(tid => tid.ContainsIgnoreCase(searchTerm))));
SortApply();
}
private void SetEntries(IEnumerable<CompatibilityEntry> entries)
@ -72,5 +125,43 @@ namespace Ryujinx.Ava.UI.ViewModels
_currentEntries = entries.ToList();
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,
};
}
}
}
}

View File

@ -50,6 +50,9 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
private string _controllerImage;
private int _device;
private object _configViewModel;
private bool _isChangeTrackingActive;
private string _chosenProfile;
[ObservableProperty] private bool _isModified;
[ObservableProperty] private string _profileName;
[ObservableProperty] private bool _notificationIsVisible; // Automatically call the NotificationView 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> DeviceList { get; set; }
public bool UseGlobalConfig;
// XAML Flags
public bool ShowSettings => _device > 0;
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 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 string _profileChoose;
public string ProfileChoose
public string ChosenProfile
{
get => _profileChoose;
get => _chosenProfile;
set
{
// 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
_profileChoose = value;
_chosenProfile = value;
LoadProfile();
OnPropertyChanged();
}
@ -290,7 +280,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
public InputConfig Config { get; set; }
public InputViewModel(UserControl owner) : this()
public InputViewModel(UserControl owner, bool useGlobal = false) : this()
{
if (Program.PreviewerDetached)
{
@ -303,6 +293,8 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
_mainWindow.ViewModel.AppHost?.NpadManager.BlockInputUpdates();
UseGlobalConfig = useGlobal;
_isLoaded = false;
LoadDevices();
@ -335,9 +327,18 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
PlayerIndexes.Add(new(PlayerIndex.Handheld, LocaleManager.Instance[LocaleKeys.ControllerSettingsHandheld]));
}
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)
{
@ -902,7 +903,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
LoadProfiles();
ProfileChoose = ProfileName; // Show new profile
ChosenProfile = ProfileName; // Show new profile
}
else
{
@ -936,7 +937,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
LoadProfiles();
ProfileChoose = ProfilesList[0].ToString(); // Show default profile
ChosenProfile = ProfilesList[0].ToString(); // Show default profile
}
}
@ -966,7 +967,14 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
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));
@ -1007,18 +1015,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.
// 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);
}
public void NotifyChange(string property)
{
OnPropertyChanged(property);
if (UseGlobalConfig && Program.UseExtraConfig)
{
// In User Settings when "Use Global Input" is enabled, it saves global input to global setting
ConfigurationState.InstanceExtra.Hid.InputConfig.Value = newConfig;
ConfigurationState.InstanceExtra.ToFileFormat().SaveConfig(Program.GlobalConfigurationPath);
}
else
{
ConfigurationState.Instance.Hid.InputConfig.Value = newConfig;
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
}
}
public void NotifyChanges()

View File

@ -1575,28 +1575,31 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool InitializeUserConfig(ApplicationData application)
{
// Code where conditions will be met before loading the user configuration (Global Config)
string BackendThreadingInit = Program.BackendThreadingArg;
BackendThreadingInit ??= ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString();
string backendThreadingInit = Program.BackendThreadingArg ?? ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString();
// If a configuration is found in the "/games/xxxxxxxxxxxxxx" folder, the program will load the user setting.
string idGame = application.IdBaseString;
if (ConfigurationFileFormat.TryLoad(Program.GetDirGameUserConfig(idGame), out ConfigurationFileFormat configurationFileFormat))
{
// Loads the user configuration, having previously changed the global configuration to the user configuration
ConfigurationState.Instance.Load(configurationFileFormat, Program.GetDirGameUserConfig(idGame, true, true), idGame);
// Loads the user configuration, having previously changed the global configuration to the user configuration
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
if (ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString() != BackendThreadingInit)
if (ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString() != backendThreadingInit)
{
List<string> Arguments = new()
{
"--bt", ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString() // BackendThreading
};
Rebooter.RebootAppWithGame(application.Path, Arguments);
Rebooter.RebootAppWithGame(application.Path,
[
"--bt",
ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString()
]);
return true;
}
@ -1991,7 +1994,7 @@ namespace Ryujinx.Ava.UI.ViewModels
// just checking for file presence
viewModel.SelectedApplication.HasIndependentConfiguration = File.Exists(
Program.GetDirGameUserConfig(viewModel.SelectedApplication.IdString, false, false));
Program.GetDirGameUserConfig(viewModel.SelectedApplication.IdString));
viewModel.RefreshView();
});

View File

@ -53,6 +53,7 @@ namespace Ryujinx.Ava.UI.ViewModels
[ObservableProperty] private bool _isVulkanAvailable = true;
[ObservableProperty] private bool _gameListNeedsRefresh;
private readonly List<string> _gpuIds = [];
public bool _useInputGlobalConfig;
private int _graphicsBackendIndex;
private int _scalingFilter;
private int _scalingFilterLevel;
@ -64,6 +65,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public event Action CloseWindow;
public event Action SaveSettingsEvent;
public event Action<bool> LocalGlobalInputSwitchEvent;
private int _networkInterfaceIndex;
private int _multiplayerModeIndex;
private string _ldnPassphrase;
@ -84,6 +86,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool IsGameTitleNotNull => !string.IsNullOrEmpty(GameTitle);
public double PanelOpacity => IsGameTitleNotNull ? 0.5 : 1;
public int ResolutionScale
{
get => _resolutionScale;
@ -141,13 +144,26 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool EnableDockedMode { get; set; }
public bool EnableKeyboard { get; set; }
public bool EnableMouse { get; set; }
public bool DisableInputWhenOutOfFocus { get; set; }
public bool DisableInputWhenOutOfFocus { get; set; }
public int FocusLostActionType { 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
{
get => _vSyncMode;
get => _vSyncMode;
set
{
if (value is VSyncMode.Custom or VSyncMode.Switch or VSyncMode.Unbounded)
@ -371,7 +387,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool IsInvalidLdnPassphraseVisible { get; set; }
public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this(false)
public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this()
{
_virtualFileSystem = virtualFileSystem;
_contentManager = contentManager;
@ -392,7 +408,7 @@ namespace Ryujinx.Ava.UI.ViewModels
string gameName,
string gameId,
byte[] gameIconData,
bool enableToLoadCustomConfig) : this(enableToLoadCustomConfig)
bool customConfig) : this()
{
_virtualFileSystem = virtualFileSystem;
_contentManager = contentManager;
@ -408,9 +424,18 @@ namespace Ryujinx.Ava.UI.ViewModels
_gameTitle = gameName;
_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))
{
ConfigurationState.Instance.Load(configurationFileFormat, gameDir, gameId);
@ -426,7 +451,7 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
public SettingsViewModel(bool noLoadGlobalConfig = false)
public SettingsViewModel()
{
GameDirectories = [];
AutoloadDirectories = [];
@ -550,9 +575,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
EnableDiscordIntegration = config.EnableDiscordIntegration;
@ -578,6 +603,7 @@ namespace Ryujinx.Ava.UI.ViewModels
};
// Input
UseGlobalInputConfig = config.System.UseInputGlobalConfig;
EnableDockedMode = config.System.EnableDockedMode;
EnableKeyboard = config.Hid.EnableKeyboard;
EnableMouse = config.Hid.EnableMouse;
@ -660,9 +686,9 @@ namespace Ryujinx.Ava.UI.ViewModels
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
config.EnableDiscordIntegration.Value = EnableDiscordIntegration;
@ -684,6 +710,7 @@ namespace Ryujinx.Ava.UI.ViewModels
};
// Input
config.System.UseInputGlobalConfig.Value = UseGlobalInputConfig;
config.System.EnableDockedMode.Value = EnableDockedMode;
config.Hid.EnableKeyboard.Value = EnableKeyboard;
config.Hid.EnableMouse.Value = EnableMouse;
@ -796,11 +823,14 @@ namespace Ryujinx.Ava.UI.ViewModels
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))
{
Program.ReloadConfig();
}
*/
Program.ReloadConfig();
}
public void ApplyButton()
@ -810,7 +840,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public void DeleteConfigGame()
{
string gameDir = Program.GetDirGameUserConfig(GameId, false, false);
string gameDir = Program.GetDirGameUserConfig(GameId);
if (File.Exists(gameDir))
{

View File

@ -100,7 +100,7 @@
Name="ProfileBox"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
SelectedItem="{Binding ProfileChoose, Mode=TwoWay}"
SelectedItem="{Binding ChosenProfile, Mode=TwoWay}"
SelectionChanged="ComboBox_SelectionChanged"
ItemsSource="{Binding ProfilesList}"
Text="{Binding ProfileName, Mode=TwoWay}" />
@ -203,7 +203,6 @@
</StackPanel>
<ContentControl IsVisible="{Binding NotificationIsVisible}">
<ContentControl.Content>
<StackPanel>
<TextBlock
Margin="5,20,0,0"

View File

@ -1,6 +1,7 @@
using Avalonia.Controls;
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Systems.Configuration;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models;
@ -14,7 +15,7 @@ namespace Ryujinx.Ava.UI.Views.Input
public InputView()
{
ViewModel = new InputViewModel(this);
ViewModel = new InputViewModel(this, ConfigurationState.Instance.System.UseInputGlobalConfig);
InitializeComponent();
}
@ -24,6 +25,13 @@ namespace Ryujinx.Ava.UI.Views.Input
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)
{
if (PlayerIndexBox != null)

View File

@ -56,7 +56,27 @@
SelectedIndex="{Binding PreferredGpuIndex}"
ItemsSource="{Binding AvailableGpus}"/>
</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" />
<TextBlock Classes="h1" Text="{ext:Locale SettingsTabGraphicsFeatures}" />
<StackPanel Margin="10,0,0,0" Orientation="Vertical" Spacing="10">
@ -255,32 +275,7 @@
</ComboBox>
</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" />
<TextBlock Classes="h1" Text="{ext:Locale SettingsTabGraphicsDeveloperOptions}" />
<StackPanel

View File

@ -1,4 +1,4 @@
<UserControl
<UserControl
x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsInputView"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
@ -22,9 +22,16 @@
<Panel
Margin="10">
<Grid RowDefinitions="Auto,*,Auto">
<views:InputView
Grid.Row="0"
Name="InputView" />
<StackPanel>
<!--
Opacity="{Binding PanelOpacityInput}">
IsEnabled="{Binding !EnableConfigGlobal}">
-->
<views:InputView
Grid.Row="0"
Name="InputView" />
</StackPanel>
<StackPanel
Orientation="Vertical"
Grid.Row="2">
@ -34,6 +41,13 @@
<StackPanel
Orientation="Horizontal"
Spacing="10">
<CheckBox
ToolTip.Tip="{ext:Locale UseGlobalInputTooltip}"
MinWidth="0"
IsChecked="{Binding UseGlobalInputConfig}">
<TextBlock
Text="{ext:Locale SettingsTabInputUseGlobalInput}" />
</CheckBox>
<CheckBox
ToolTip.Tip="{ext:Locale DockModeToggleTooltip}"
MinWidth="0"

View File

@ -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:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:helpers="using:Ryujinx.Ava.UI.Helpers"
@ -17,65 +17,277 @@
<window:StyleableAppWindow.DataContext>
<viewModels:CompatibilityViewModel />
</window:StyleableAppWindow.DataContext>
<Grid RowDefinitions="Auto,*">
<Grid Grid.Row="0" ColumnDefinitions="Auto,*,Auto,Auto" Name="FlushControls">
<controls:RyujinxLogo
<Grid RowDefinitions="Auto,Auto,*">
<!-- UI FlushControls -->
<Grid Grid.Row="0" ColumnDefinitions="Auto,*,Auto,Auto,Auto" Name="FlushControls">
<controls:RyujinxLogo
Grid.Column="0"
Margin="15, 0, 7, 0"
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" />
<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}" />
<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.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" />
<CheckBox Grid.Column="1" Margin="7, 0, 0, 0" IsChecked="{Binding OnlyShowOwnedGames}" />
<TextBlock Grid.Column="2" Padding="0, 0, 1, 0" Margin="-10, 0, 18, 0" Text="{ext:Locale CompatibilityListOnlyShowOwnedGames}" />
<!-- Description Field Above ScrollViewer -->
<Grid Grid.Row="1" ColumnDefinitions="*,Auto" Margin="10, 5, 10, 5">
<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>
<ScrollViewer Grid.Row="1">
<ListBox Margin="12, 0, 13, 0"
Background="Transparent"
ItemsSource="{Binding CurrentEntries}">
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type systems:CompatibilityEntry}">
<Grid MinWidth="800"
Margin="10"
ColumnDefinitions="Auto,Auto,Auto,*"
Background="Transparent"
ToolTip.Tip="{Binding LocalizedLastUpdated}">
<TextBlock Grid.Column="0"
Text="{Binding GameName}"
Width="525"
VerticalAlignment="Center"
HorizontalAlignment="Center"
TextWrapping="Wrap" />
<TextBlock Grid.Column="1"
Width="135"
Padding="7, 0, 0, 0"
FontFamily="{StaticResource JetBrainsMono}"
Text="{Binding FormattedTitleId}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
TextWrapping="Wrap" />
<TextBlock Grid.Column="2"
Padding="7, 0"
Text="{Binding LocalizedStatus}"
Width="90"
Background="Transparent"
ToolTip.Tip="{Binding LocalizedStatusDescription}"
Foreground="{Binding Status, Converter={x:Static helpers:PlayabilityStatusConverter.Shared}}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
TextWrapping="NoWrap" />
<TextBlock Grid.Column="3"
Text="{Binding FormattedIssueLabels}"
VerticalAlignment="Center"
HorizontalAlignment="Left"
TextWrapping="WrapWithOverflow" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<!-- List of compatible games -->
<ScrollViewer Grid.Row="2">
<ListBox Margin="12, 0, 13, 0"
Background="Transparent"
ItemsSource="{Binding CurrentEntries}">
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type systems:CompatibilityEntry}">
<Grid MinWidth="800"
Margin="10"
ColumnDefinitions="Auto,Auto,Auto,*"
Background="Transparent"
ToolTip.Tip="{Binding LocalizedLastUpdated}">
<TextBlock Grid.Column="0"
Text="{Binding GameName}"
Width="525"
VerticalAlignment="Center"
HorizontalAlignment="Center"
TextWrapping="Wrap" />
<TextBlock Grid.Column="1"
Width="135"
Padding="7, 0, 0, 0"
FontFamily="{StaticResource JetBrainsMono}"
Text="{Binding FormattedTitleId}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
TextWrapping="Wrap" />
<TextBlock Grid.Column="2"
Padding="7, 0"
Text="{Binding LocalizedStatus}"
Width="100"
Background="Transparent"
ToolTip.Tip="{Binding LocalizedStatusDescription}"
Foreground="{Binding Status, Converter={x:Static helpers:PlayabilityStatusConverter.Shared}}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
TextWrapping="NoWrap" />
<TextBlock Grid.Column="3"
Text="{Binding FormattedIssueLabels}"
VerticalAlignment="Center"
HorizontalAlignment="Left"
TextWrapping="WrapWithOverflow" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</ScrollViewer>
<Grid></Grid>
</Grid>

View File

@ -1,5 +1,6 @@
using Avalonia.Controls;
using Ryujinx.Ava.Common.Locale;
using Avalonia.Interactivity;
using Ryujinx.Ava.Systems.Configuration;
using Ryujinx.Ava.UI.ViewModels;
using System.Threading.Tasks;
@ -42,5 +43,28 @@ namespace Ryujinx.Ava.UI.Windows
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));
}
}
}
}

View File

@ -120,30 +120,34 @@
IconSource="Code" />
</ui:NavigationView.MenuItems>
</ui:NavigationView>
<ReversibleStackPanel
Grid.Row="2"
Margin="10"
Spacing="10"
Orientation="Horizontal"
HorizontalAlignment="Right"
ReverseOrder="{x:Static helper:RunningPlatform.IsMacOS}">
<Button
Content="{ext:Locale SettingsButtonSave}"
Command="{Binding SaveUserConfig}" />
<Button
HotKey="Escape"
Content="{ext:Locale SettingsButtonClose}"
Command="{Binding CancelButton}" />
<Button
IsVisible="{Binding IsGameRunning}"
Content="{ext:Locale SettingsButtonApply}"
Command="{Binding ApplyButton}" />
<Button
IsVisible="{Binding !IsGameRunning}"
Content="{ext:Locale UserProfilesDelete}"
Command="{Binding DeleteConfigGame}"
Classes="red"/>
</ReversibleStackPanel>
<Grid Grid.Row="3"
ColumnDefinitions="Auto,*,Auto">
<StackPanel Grid.Column="0" Orientation="Horizontal" Margin="10" Spacing="10">
<Button
IsVisible="{Binding !IsGameRunning}"
Content="{ext:Locale UserProfilesDelete}"
Command="{Binding DeleteConfigGame}"
Classes="red"/>
</StackPanel>
<ReversibleStackPanel
Grid.Column="2"
Margin="10"
Spacing="10"
Orientation="Horizontal"
HorizontalAlignment="Right"
ReverseOrder="{x:Static helper:RunningPlatform.IsMacOS}">
<Button
Classes="accent"
Content="{ext:Locale SettingsButtonSave}"
Command="{Binding SaveUserConfig}" />
<Button
HotKey="Escape"
Content="{ext:Locale SettingsButtonClose}"
Command="{Binding CancelButton}" />
<Button
Content="{ext:Locale SettingsButtonApply}"
Command="{Binding ApplyButton}" />
</ReversibleStackPanel>
</Grid>
</Grid>
</window:StyleableAppWindow>

View File

@ -28,6 +28,8 @@ namespace Ryujinx.Ava.UI.Windows
ViewModel.CloseWindow += Close;
ViewModel.SaveSettingsEvent += SaveSettings;
ViewModel.LocalGlobalInputSwitchEvent += ToggleLocalGlobalInput;
InitializeComponent();
Load();
}
@ -37,6 +39,11 @@ namespace Ryujinx.Ava.UI.Windows
InputPage.InputView?.SaveCurrentProfile();
}
public void ToggleLocalGlobalInput(bool enableConfigGlobal)
{
InputPage.InputView?.ToggleLocalGlobalInput(enableConfigGlobal);
}
private void Load()
{
Pages.Children.Clear();
@ -90,6 +97,7 @@ namespace Ryujinx.Ava.UI.Windows
protected override void OnClosing(WindowClosingEventArgs e)
{
Program.UseExtraConfig = false;
InputPage.Dispose(); // You need to unload the gamepad settings, otherwise the controls will be blocked
base.OnClosing(e);
}