Compare commits

...

11 Commits

Author SHA1 Message Date
Evan Husted
da3f4e1d3a misc: Created generic type RyujinxControl to allow for more unified control view model definitions 2025-03-02 21:24:39 -06:00
Evan Husted
69d79322bb misc: chore: remove old title ID constructor for RendererHost 2025-03-02 21:23:36 -06:00
Evan Husted
c3af1dbf1a Stick Visualizer (#579)
![](https://i.imgur.com/iSaXRMr.png)

---------

Co-authored-by: MutantAura <domw0401@gmail.com>
2025-03-02 20:43:31 -06:00
Piplup
10ac381525 compat: Updates (#742)
These are branches i have on my private repo that i been meaning to push
Bluey The Videogame - compatibility/Bluey
Grand Theft Auto: III – The Definitive Edition -
compatibility/gta-definitiveedition
Grand Theft Auto: Vice City – The Definitive Edition -
compatibility/gta-definitiveedition
Grand Theft Auto: San Andreas – The Definitive Edition -
compatibility/gta-definitiveedition
SpongeBob SquarePants: The Cosmic Shake - compatibility/TheCosmicShake

p.s i didn't mess up one of the commit names i swear
2025-03-02 18:39:32 -06:00
Danik2343
e104ee6be3 Update: Russian Language (Some missing strings) (#732) 2025-02-27 16:53:18 -06:00
Nicola
e65d1ec6c9 JoyCon to Joy-Con (#729)
Joy-Con is the official name
2025-02-26 20:00:35 -06:00
Evan Husted
534f92506b misc: chore: Add warning logs for invalid ips patch attempts 2025-02-26 02:31:18 -06:00
Daniel Nylander
10d20c1ae3 Update Swedish locale (#720) 2025-02-26 02:02:07 -06:00
Evan Husted
e294a79975 UI: dev: [ci skip] Add Avalonia DevTools support to all Windows defined by Ryujinx, accessible via Ctrl + F12 when running in Debug. 2025-02-25 23:12:57 -06:00
Evan Husted
ec06a86899 UI: Increase default size for setting windows to include autoload setting & the bottom of the input settings 2025-02-25 22:51:26 -06:00
Vladimir Sokolov
2e4de17472 Custom configuration for each game (#632)
![image](https://github.com/user-attachments/assets/5dd139b4-2004-4c13-85d1-fc3378382adf)

![image](https://github.com/user-attachments/assets/9bcb8524-a403-428f-9f98-e8c03c75f079)

Now you can make a separate configuration (independent file) for each
game. All emulator settings are available except for some UI functionality ones. 
The configuration file can be changed and deleted from a separate menu. The
user configuration menu is available through the context menu on a given application.

---------

Co-authored-by: Evan Husted <greem@greemdev.net>
2025-02-25 21:11:20 -06:00
76 changed files with 1897 additions and 653 deletions

View File

@@ -97,7 +97,7 @@ If you are planning to contribute or just want to learn more about this project
- **Input**
We currently have support for keyboard, mouse, touch input, JoyCon input support, and nearly all controllers.
We currently have support for keyboard, mouse, touch input, Joy-Con input support, and nearly all controllers.
Motion controls are natively supported in most cases; for dual-JoyCon motion support, DS4Windows or BetterJoy are currently required.
In all scenarios, you can set up everything inside the input configuration menu.

View File

@@ -631,6 +631,7 @@
010030D012FF6000,"Bus Driver Simulator",,playable,2022-10-17 13:55:27
0100A9101418C000,"BUSTAFELLOWS",nvdec,playable,2020-10-17 20:04:41
0100177005C8A000,"BUTCHER",,playable,2021-01-11 18:50:17
01008c2019598000,"Bluey: The Videogame",,playable,2025-02-11 04:38:00
01000B900D8B0000,"Cadence of Hyrule: Crypt of the NecroDancer Featuring The Legend of Zelda",slow;nvdec,playable,2024-04-01 22:43:40
010065700EE06000,"Cadence of Hyrule: Crypt of the NecroDancer Featuring The Legend of Zelda Demo",demo;gpu;nvdec,ingame,2021-02-14 21:48:15
01005C00117A8000,"Café Enchanté",,playable,2020-11-13 14:54:25
@@ -1382,6 +1383,9 @@
0100763015C2E000,"Gunvolt Chronicles: Luminous Avenger iX 2",crash;Needs Update,nothing,2022-04-29 15:34:34
01002C8018554000,"Gurimugurimoa OnceMore Demo",,playable,2022-07-29 22:07:31
0100AC601DCA8000,"GYLT",crash,ingame,2024-03-18 20:16:51
0100c3c012718000,"Grand Theft Auto: III The Definitive Edition",gpu;UE4,ingame,2022-10-31 20:13:52
0100182014022000,"Grand Theft Auto: Vice City The Definitive Edition",gpu;UE4,ingame,2022-10-31 20:13:52
010065a014024000,"Grand Theft Auto: San Andreas The Definitive Edition",gpu;UE4,ingame,2022-10-31 20:13:52
0100822012D76000,"HAAK",gpu,ingame,2023-02-19 14:31:05
01007E100EFA8000,"Habroxia",,playable,2020-06-16 23:04:42
0100535012974000,"Hades",vulkan,playable,2022-10-05 10:45:21
@@ -2729,7 +2733,7 @@
0100C2500FC20000,"Splatoon™ 3",ldn-works;opengl-backend-bug;LAN;amd-vendor-bug,playable,2024-08-04 23:49:11
0100BA0018500000,"Splatoon™ 3: Splatfest World Premiere",gpu;online-broken;demo,ingame,2022-09-19 03:17:12
010062800D39C000,"SpongeBob SquarePants: Battle for Bikini Bottom - Rehydrated",online-broken;UE4;ldn-broken;vulkan-backend-bug,playable,2023-08-01 19:29:34
01009FB0172F4000,"SpongeBob SquarePants: The Cosmic Shake",gpu;UE4,ingame,2023-08-01 19:29:53
01009FB0172F4000,"SpongeBob SquarePants: The Cosmic Shake",gpu;UE4,ingame,2024-03-04 16:35:00
010097C01336A000,"Spooky Chase",,playable,2022-11-04 12:17:44
0100C6100D75E000,"Spooky Ghosts Dot Com",,playable,2021-06-15 15:16:11
0100DE9005170000,"Sports Party",nvdec,playable,2021-03-05 13:40:42
1 title_id game_name labels status last_updated
631 010030D012FF6000 Bus Driver Simulator playable 2022-10-17 13:55:27
632 0100A9101418C000 BUSTAFELLOWS nvdec playable 2020-10-17 20:04:41
633 0100177005C8A000 BUTCHER playable 2021-01-11 18:50:17
634 01008c2019598000 Bluey: The Videogame playable 2025-02-11 04:38:00
635 01000B900D8B0000 Cadence of Hyrule: Crypt of the NecroDancer Featuring The Legend of Zelda slow;nvdec playable 2024-04-01 22:43:40
636 010065700EE06000 Cadence of Hyrule: Crypt of the NecroDancer Featuring The Legend of Zelda Demo demo;gpu;nvdec ingame 2021-02-14 21:48:15
637 01005C00117A8000 Café Enchanté playable 2020-11-13 14:54:25
1383 0100763015C2E000 Gunvolt Chronicles: Luminous Avenger iX 2 crash;Needs Update nothing 2022-04-29 15:34:34
1384 01002C8018554000 Gurimugurimoa OnceMore Demo playable 2022-07-29 22:07:31
1385 0100AC601DCA8000 GYLT crash ingame 2024-03-18 20:16:51
1386 0100c3c012718000 Grand Theft Auto: III – The Definitive Edition gpu;UE4 ingame 2022-10-31 20:13:52
1387 0100182014022000 Grand Theft Auto: Vice City – The Definitive Edition gpu;UE4 ingame 2022-10-31 20:13:52
1388 010065a014024000 Grand Theft Auto: San Andreas – The Definitive Edition gpu;UE4 ingame 2022-10-31 20:13:52
1389 0100822012D76000 HAAK gpu ingame 2023-02-19 14:31:05
1390 01007E100EFA8000 Habroxia playable 2020-06-16 23:04:42
1391 0100535012974000 Hades vulkan playable 2022-10-05 10:45:21
2733 0100C2500FC20000 Splatoon™ 3 ldn-works;opengl-backend-bug;LAN;amd-vendor-bug playable 2024-08-04 23:49:11
2734 0100BA0018500000 Splatoon™ 3: Splatfest World Premiere gpu;online-broken;demo ingame 2022-09-19 03:17:12
2735 010062800D39C000 SpongeBob SquarePants: Battle for Bikini Bottom - Rehydrated online-broken;UE4;ldn-broken;vulkan-backend-bug playable 2023-08-01 19:29:34
2736 01009FB0172F4000 SpongeBob SquarePants: The Cosmic Shake gpu;UE4 ingame 2023-08-01 19:29:53 2024-03-04 16:35:00
2737 010097C01336A000 Spooky Chase playable 2022-11-04 12:17:44
2738 0100C6100D75E000 Spooky Ghosts Dot Com playable 2021-06-15 15:16:11
2739 0100DE9005170000 Sports Party nvdec playable 2021-03-05 13:40:42

View File

@@ -71,16 +71,24 @@ namespace Ryujinx.HLE.Loaders.Mods
int patchOffset = (int)offset;
int patchSize = patch.Length;
if (patchOffset < protectedOffset || patchOffset > memory.Length)
if (patchOffset < protectedOffset)
{
continue; // Add warning?
Logger.Warning?.Print(LogClass.ModLoader, $"Attempted to patch protected memory ({patchOffset:x} is within protected boundary of {protectedOffset:x}).");
continue;
}
if (patchOffset > memory.Length)
{
Logger.Warning?.Print(LogClass.ModLoader, $"Attempted to patch out of bounds memory (offset {patchOffset} ({patchOffset:x}) exceeds memory buffer length {memory.Length}).");
continue;
}
patchOffset -= protectedOffset;
if (patchOffset + patchSize > memory.Length)
{
patchSize = memory.Length - patchOffset; // Add warning?
Logger.Warning?.Print(LogClass.ModLoader, $"Patch offset ({patchOffset:x}) + size ({patchSize}) is greater than the size of the memory buffer ({memory.Length}). Attempting to fix this...");
patchSize = memory.Length - patchOffset;
}
Logger.Info?.Print(LogClass.ModLoader, $"Patching address offset {patchOffset:x} <= {BitConverter.ToString(patch).Replace('-', ' ')} len={patchSize}");

View File

@@ -1113,6 +1113,13 @@ namespace Ryujinx.Ava
});
(RendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.MakeCurrent(true);
// Reload settings when the game is turned off
// (resets custom settings if there were any)
Program.ReloadConfig();
// Reload application list (changes the status of the user setting if it was added or removed during the game)
Dispatcher.UIThread.Post(() => RyujinxApp.MainWindow.LoadApplications());
}
public void InitStatus()

View File

@@ -1,7 +1,8 @@
<Styles
<Styles
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
xmlns:windowing="clr-namespace:FluentAvalonia.UI.Windowing;assembly=FluentAvalonia">
<Design.PreviewWith>
<Border Height="2000"
@@ -30,7 +31,8 @@
<Button
Name="btnRem"
HorizontalAlignment="Right"
Content="Add" />
Content="Add"
Classes="red"/>
<TextBox
Width="100"
VerticalAlignment="Center"
@@ -41,7 +43,13 @@
</StackPanel>
</Grid>
<ui:NumberBox Value="1" />
<MenuItem
Header="123 0000"
ToolTip.Tip="What this"/>
<TextBlock
Classes="globalConfigMarker"/>
</StackPanel>
</Border>
</Design.PreviewWith>
<Style Selector="DropDownButton">
@@ -331,6 +339,14 @@
<Setter Property="Margin"
Value="0,5,0,0" />
</Style>
<Style Selector="TextBlock.globalConfigMarker" >
<Setter Property="Foreground" Value="SeaGreen"/>
<Setter Property="Margin" Value="5,0,0,0"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Text" Value="{ext:Locale GameSpecificConfigurationGlobal}"/>
</Style>
<Style Selector="StackPanel.globalConfigMarker">
</Style>
<Style Selector="ContextMenu">
<Setter Property="BorderBrush"
Value="{DynamicResource MenuFlyoutPresenterBorderBrush}" />
@@ -373,6 +389,19 @@
<Setter Property="Background"
Value="{DynamicResource AppListHoverBackgroundColor}" />
</Style>
<Style Selector="Button.red /template/ ContentPresenter">
<Setter Property="CornerRadius" Value="4"/>
<Setter Property="Background" Value="red"/>
<Setter Property="Foreground" Value="White"/>
</Style>
<Style Selector="Button.red:pointerover /template/ ContentPresenter">
<Setter Property="CornerRadius" Value="4"/>
<Setter Property="Background" Value="{DynamicResource WarningBackgroundColor}" />
<Setter Property="Foreground" Value="White"/>
</Style>
<Styles.Resources>
<SolidColorBrush x:Key="ThemeAccentColorBrush"
Color="{DynamicResource SystemAccentColor}" />

View File

@@ -1,4 +1,4 @@
<ResourceDictionary xmlns="https://github.com/avaloniaui"
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Default">
@@ -12,11 +12,13 @@
<Color x:Key="MenuFlyoutPresenterBorderColor">#C1C1C1</Color>
<Color x:Key="AppListBackgroundColor">#b3ffffff</Color>
<Color x:Key="AppListHoverBackgroundColor">#80cccccc</Color>
<Color x:Key="WarningBackgroundColor">#FF6347</Color>
<Color x:Key="SecondaryTextColor">#A0000000</Color>
<Color x:Key="FavoriteApplicationIconColor">#fffcd12a</Color>
<Color x:Key="Switch">#FF2EEAC9</Color>
<Color x:Key="Unbounded">#FFFF4554</Color>
<Color x:Key="Custom">#6483F5</Color>
<Color x:Key="Warning">#800080</Color>
</ResourceDictionary>
<ResourceDictionary x:Key="Light">
<SolidColorBrush x:Key="DataGridSelectionBackgroundBrush"
@@ -29,11 +31,13 @@
<Color x:Key="MenuFlyoutPresenterBorderColor">#C1C1C1</Color>
<Color x:Key="AppListBackgroundColor">#b3ffffff</Color>
<Color x:Key="AppListHoverBackgroundColor">#80cccccc</Color>
<Color x:Key="WarningBackgroundColor">#FF6347</Color>
<Color x:Key="SecondaryTextColor">#A0000000</Color>
<Color x:Key="FavoriteApplicationIconColor">#fffcd12a</Color>
<Color x:Key="Switch">#13c3a4</Color>
<Color x:Key="Unbounded">#FFFF4554</Color>
<Color x:Key="Custom">#6483F5</Color>
<Color x:Key="Warning">#800080</Color>
</ResourceDictionary>
<ResourceDictionary x:Key="Dark">
<SolidColorBrush x:Key="DataGridSelectionBackgroundBrush"
@@ -46,11 +50,13 @@
<Color x:Key="MenuFlyoutPresenterBorderColor">#3D3D3D</Color>
<Color x:Key="AppListBackgroundColor">#0FFFFFFF</Color>
<Color x:Key="AppListHoverBackgroundColor">#1EFFFFFF</Color>
<Color x:Key="WarningBackgroundColor">#FF6347</Color>
<Color x:Key="SecondaryTextColor">#A0FFFFFF</Color>
<Color x:Key="FavoriteApplicationIconColor">#fffcd12a</Color>
<Color x:Key="Switch">#FF2EEAC9</Color>
<Color x:Key="Unbounded">#FFFF4554</Color>
<Color x:Key="Custom">#6483F5</Color>
<Color x:Key="Warning">#FFA500</Color>
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>

View File

@@ -464,7 +464,7 @@
"pl_PL": "",
"pt_BR": "Abrir Pasta de Capturas de Tela",
"ru_RU": "Открыть папку со скриншотами",
"sv_SE": "",
"sv_SE": "Öppna skärmbildsmappen",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Відкрити теку скріншотів",
@@ -1564,7 +1564,7 @@
"pl_PL": "",
"pt_BR": "Desenvolvido por {0}",
"ru_RU": "Разработана {0}",
"sv_SE": "",
"sv_SE": "Utvecklat av {0}",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Розроблено: {0}",
@@ -1864,7 +1864,7 @@
"pl_PL": "",
"pt_BR": "Compatibilidade:",
"ru_RU": "Совместимость:",
"sv_SE": "",
"sv_SE": "Kompatibilitet:",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Сумісність:",
@@ -1889,7 +1889,7 @@
"pl_PL": "",
"pt_BR": "ID do Título:",
"ru_RU": "ID приложения",
"sv_SE": "",
"sv_SE": "Titel-id:",
"th_TH": "",
"tr_TR": "",
"uk_UA": "ID гри:",
@@ -1914,7 +1914,7 @@
"pl_PL": "",
"pt_BR": "Jogos Hospedados: {0}",
"ru_RU": "Запущенно игр: {0}",
"sv_SE": "",
"sv_SE": "Värdskap för spel: {0}",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Розміщені ігри: {0}",
@@ -1939,7 +1939,7 @@
"pl_PL": "",
"pt_BR": "Jogadores Online: {0}",
"ru_RU": "Игроков онлайн: {0}",
"sv_SE": "",
"sv_SE": "Online-spelare: {0}",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Гравців онлайн: {0}",
@@ -2289,7 +2289,7 @@
"pl_PL": "",
"pt_BR": "Limpar Cache PPTC",
"ru_RU": "Очистить кэш PPTC",
"sv_SE": "",
"sv_SE": "Rensa PPTC-cache",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Очистити кеш PPTC",
@@ -2314,7 +2314,7 @@
"pl_PL": "",
"pt_BR": "Apaga os arquivos de cache PPTC do aplicativo",
"ru_RU": "Удаляет все файлы кэша PPTC для приложения",
"sv_SE": "",
"sv_SE": "Tar bort alla PPTC-cachefiler för applikationen",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Видаляє всі файли кешу PPTC для застосунку",
@@ -2747,6 +2747,56 @@
"zh_TW": "建立桌面捷徑,啟動選取的應用程式"
}
},
{
"ID": "GameListContextMenuCreateCustomConfiguration",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Create Custom Configuration",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "Создать пользовательскую конфигурацию",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "GameListContextMenuEditCustomConfiguration",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Edit Custom Configuration",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "Изменить пользовательскую конфигурацию",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "GameListContextMenuCreateShortcutToolTipMacOS",
"Translations": {
@@ -2772,6 +2822,56 @@
"zh_TW": "在 macOS 的應用程式資料夾中建立捷徑,啟動選取的應用程式"
}
},
{
"ID": "CreateCustomConfigurationToolTip",
"Translations": {
"ar_SA": "ينشئ تكوينًا مستقلًا للعبة الحالية",
"de_DE": "Erstellt eine unabhängige Konfiguration für das aktuelle Spiel",
"el_GR": "Δημιουργεί μια ανεξάρτητη διαμόρφωση για το τρέχον παιχνίδι",
"en_US": "Creates an independent configuration for the selected game",
"es_ES": "Crea una configuración independiente para el juego actual",
"fr_FR": "Crée une configuration indépendante pour le jeu en cours",
"he_IL": "יוצר תצורה עצמאית למשחק הנוכחי",
"it_IT": "Crea una configurazione indipendente per il gioco attuale",
"ja_JP": "現在のゲーム用の独立した設定を作成します",
"ko_KR": "현재 게임에 대한 독립적인 설정을 생성합니다",
"no_NO": "Oppretter en uavhengig konfigurasjon for det gjeldende spillet",
"pl_PL": "Tworzy niezależną konfigurację dla bieżącej gry",
"pt_BR": "Cria uma configuração independente para o jogo atual",
"ru_RU": "Создает независимую конфигурацию для текущей игры",
"sv_SE": "Skapar en oberoende konfiguration för det aktuella spelet",
"th_TH": "สร้างการกำหนดค่าที่เป็นอิสระสำหรับเกมปัจจุบัน",
"tr_TR": "Mevcut oyun için bağımsız bir yapılandırma oluşturur",
"uk_UA": "Створює незалежну конфігурацію для поточної гри",
"zh_CN": "为当前游戏创建独立的配置",
"zh_TW": "為當前遊戲創建獨立的配置"
}
},
{
"ID": "EditCustomConfigurationToolTip",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Edit your existing independent configuration for the selected game",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "Отредактировать существующую независимую конфигурацию для выбранной игры.",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "GameListContextMenuShowCompatEntry",
"Translations": {
@@ -2789,7 +2889,7 @@
"pl_PL": "",
"pt_BR": "Mostrar Dados de Compatibilidade",
"ru_RU": "Показать записи о совместимости",
"sv_SE": "",
"sv_SE": "Visa kompatibilitetspost",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Iнформація про сумісність",
@@ -2814,7 +2914,7 @@
"pl_PL": "",
"pt_BR": "Exibe o jogo selecionado na Lista de Compatibilidade, que normalmente pode ser acessada pelo menu Ajuda.",
"ru_RU": "Отобразить выбранную игру в списке совместимости, доступ к которому вы обычно можете получить через меню Справки.",
"sv_SE": "",
"sv_SE": "Visa valt spel i kompatibilitetslistan som du normalt sett kan komma åt via hjälpmenyn.",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Показати цю гру в Списку Сумісності. Список сумісності також можна зайти в меню Довідки.",
@@ -2839,7 +2939,7 @@
"pl_PL": "",
"pt_BR": "Mostrar Informações do Jogo",
"ru_RU": "Показать информацию об игре",
"sv_SE": "",
"sv_SE": "Visa spelinformation",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Інформація про гру",
@@ -2864,7 +2964,7 @@
"pl_PL": "",
"pt_BR": "Exibe estatísticas e detalhes sobre o jogo selecionado.",
"ru_RU": "Показывать статистику и подробную информацию о выбранной игре.",
"sv_SE": "",
"sv_SE": "Visa statistik och detaljer om det aktuella spelet.",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Показати статистику та деталі обраної гри.",
@@ -3297,6 +3397,31 @@
"zh_TW": "設定"
}
},
{
"ID": "SettingsWithInfo",
"Translations": {
"ar_SA": "{0} - إعدادات",
"de_DE": "Einstellungen - {0}",
"el_GR": "Ρυθμίσεις - {0}",
"en_US": "Settings - {0}",
"es_ES": "Configuración - {0}",
"fr_FR": "Paramètres - {0}",
"he_IL": "{0} - הגדרות",
"it_IT": "Impostazioni - {0}",
"ja_JP": "設定 - {0}",
"ko_KR": "설정 - {0}",
"no_NO": "Innstillinger - {0}",
"pl_PL": "Ustawienia - {0}",
"pt_BR": "Configurações - {0}",
"ru_RU": "Параметры - {0}",
"sv_SE": "Inställningar - {0}",
"th_TH": "ตั้งค่า - {0}",
"tr_TR": "Ayarlar - {0}",
"uk_UA": "Налаштування - {0}",
"zh_CN": "设置 - {0}",
"zh_TW": "設定 - {0}"
}
},
{
"ID": "SettingsTabGeneral",
"Translations": {
@@ -3389,7 +3514,7 @@
"pl_PL": "",
"pt_BR": "Verificar Atualizações:",
"ru_RU": "Проверка наличия обновлений",
"sv_SE": "",
"sv_SE": "Leta efter uppdateringar:",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Перевірка оновлень:",
@@ -3414,7 +3539,7 @@
"pl_PL": "",
"pt_BR": "Desligado",
"ru_RU": "Отключить",
"sv_SE": "",
"sv_SE": "Av",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Вимкнути",
@@ -3439,7 +3564,7 @@
"pl_PL": "",
"pt_BR": "Ao Abrir",
"ru_RU": "При запуске",
"sv_SE": "",
"sv_SE": "Fråga",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Запитувати щоразу",
@@ -3464,7 +3589,7 @@
"pl_PL": "",
"pt_BR": "2° Plano",
"ru_RU": "В фоне",
"sv_SE": "",
"sv_SE": "Bakgrund",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Оновлювати в фоні",
@@ -3489,7 +3614,7 @@
"pl_PL": "",
"pt_BR": "Ao Perder o Foco:",
"ru_RU": "При выходе эмулятора из фокуса",
"sv_SE": "",
"sv_SE": "När emulatorn tappar fokus:",
"th_TH": "",
"tr_TR": "",
"uk_UA": "При втраті фокуса емулятором:",
@@ -3514,7 +3639,7 @@
"pl_PL": "",
"pt_BR": "Não Fazer Nada",
"ru_RU": "Ничего не делать",
"sv_SE": "",
"sv_SE": "Gör ingenting",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Нічого не робити",
@@ -3539,7 +3664,7 @@
"pl_PL": "",
"pt_BR": "Bloquear Controles",
"ru_RU": "Блокировать управление",
"sv_SE": "",
"sv_SE": "Blockera inmatning",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Блокувати введення",
@@ -3564,7 +3689,7 @@
"pl_PL": "",
"pt_BR": "Ficar Mudo",
"ru_RU": "Отключить звук",
"sv_SE": "",
"sv_SE": "Stäng av ljudet",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Вимкнути звук",
@@ -3589,7 +3714,7 @@
"pl_PL": "",
"pt_BR": "Bloquear Controles & Ficar Mudo",
"ru_RU": "Блокировать управление и отключить звук",
"sv_SE": "",
"sv_SE": "Blockera inmatningar och stäng av ljudet",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Блокувати введення та Вимкнути звук",
@@ -3614,7 +3739,7 @@
"pl_PL": "",
"pt_BR": "Pausar a Emulação",
"ru_RU": "Поставить паузу",
"sv_SE": "",
"sv_SE": "Pausa emuleringen",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Поставити на паузу",
@@ -3689,7 +3814,7 @@
"pl_PL": "",
"pt_BR": "Desativar Controles Quando Estiver Fora de Foco",
"ru_RU": "Отключает управление при выходе из фокуса",
"sv_SE": "",
"sv_SE": "Inaktivera inmatning när fokus tappas",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
@@ -4714,7 +4839,7 @@
"pl_PL": "",
"pt_BR": "Sincronizar com o Sistema PC",
"ru_RU": "Соответствовать времени в системе",
"sv_SE": "",
"sv_SE": "Matcha systemtid",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
@@ -4888,7 +5013,7 @@
"no_NO": "Lyd Inn/Ut",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"ru_RU": "Выход/Вход звука",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
@@ -5139,7 +5264,7 @@
"pl_PL": "",
"pt_BR": "Ignorar Applet do Controlador",
"ru_RU": "Игнорировать апплет контроллера",
"sv_SE": "",
"sv_SE": "Ignorera kontroller-applet",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Ігнорувати Аплет Контролера",
@@ -5989,7 +6114,7 @@
"pl_PL": "",
"pt_BR": "Habilitar Logs da IU",
"ru_RU": "Включить журнал интерфейса",
"sv_SE": "",
"sv_SE": "Aktivera gränssnittsloggar",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Увімкнути журнали інтерфейсу",
@@ -6389,7 +6514,7 @@
"pl_PL": "",
"pt_BR": "Redefinir Configurações",
"ru_RU": "Сбросить настройки",
"sv_SE": "",
"sv_SE": "Nollställ inställningar",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Скинути налаштування",
@@ -6414,7 +6539,7 @@
"pl_PL": "",
"pt_BR": "Quero redefinir minhas configurações.",
"ru_RU": "Я хочу сбросить свои настройки.",
"sv_SE": "",
"sv_SE": "Jag vill nollställa mina inställningar.",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Я хочу скинути налаштування.",
@@ -6438,7 +6563,7 @@
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"ru_RU": "Ок",
"sv_SE": "Ok",
"th_TH": "ตกลง",
"tr_TR": "Tamam",
@@ -6888,7 +7013,7 @@
"no_NO": "",
"pl_PL": "Pro Kontroler",
"pt_BR": "",
"ru_RU": "",
"ru_RU": "Pro контроллер",
"sv_SE": "",
"th_TH": "โปรคอนโทรลเลอร์",
"tr_TR": "Profesyonel Kumanda",
@@ -8389,7 +8514,7 @@
"pl_PL": "",
"pt_BR": "Velocidade do Arco-íris",
"ru_RU": "Скорость переливания",
"sv_SE": "",
"sv_SE": "Regnbågshastighet",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
@@ -12647,6 +12772,31 @@
"zh_TW": "正在下載更新..."
}
},
{
"ID": "DialogRebooterMessage",
"Translations": {
"ar_SA": "من فضلك انتظر، المحاكي في طور إعادة التشغيل",
"de_DE": "Bitte warten Sie, der Emulator wird neu gestartet",
"el_GR": "Παρακαλώ περιμένετε, ο εξομοιωτής επανεκκινείται",
"en_US": "Please wait, the emulator is restarting",
"es_ES": "Por favor, espere, el emulador se está reiniciando",
"fr_FR": "Veuillez patienter, l'émulateur est en train de redémarrer",
"he_IL": "אנא המתן, המחקה מתארגן מחדש",
"it_IT": "Attendere prego, l'emulatore si sta riavviando",
"ja_JP": "お待ちください、エミュレーターが再起動しています",
"ko_KR": "잠시만 기다려 주세요, 에뮬레이터가 재시작 중입니다",
"no_NO": "Vennligst vent, emulatoren starter på nytt",
"pl_PL": "Proszę czekać, emulator jest w trakcie ponownego uruchamiania",
"pt_BR": "Por favor, aguarde, o emulador está reiniciando",
"ru_RU": "Пожалуйста, подождите, эмулятор перезапускается",
"sv_SE": "Vänligen vänta, emulatorn startar om",
"th_TH": "กรุณารอสักครู่, ตัวจำลองกำลังเริ่มใหม่",
"tr_TR": "Lütfen bekleyin, emülatör yeniden başlatılıyor",
"uk_UA": "Будь ласка, зачекайте, емулятор перезавантажується",
"zh_CN": "请稍等,模拟器正在重新启动",
"zh_TW": "請稍候,模擬器正在重新啟動"
}
},
{
"ID": "DialogUpdaterExtractionMessage",
"Translations": {
@@ -13664,7 +13814,7 @@
"pl_PL": "",
"pt_BR": "Você está prestes a limpar todos os dados PPTC de:\n\n{0}\n\nTem certeza de que deseja continuar?",
"ru_RU": "Вы собираетесь удалить все данные PPTC из:\n\n{0}\n\nВы уверены, что хотите продолжить?",
"sv_SE": "",
"sv_SE": "Du är på väg att ta bort allt PPTC-data från:\n\n{0}\n\nÄr du säker på att du vill fortsätta?",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Ви збираєтесь видалити всі дані PPTC з:\n\n{0}\n\nБажаєте продовжити цю операцію?",
@@ -16514,7 +16664,7 @@
"pl_PL": "",
"pt_BR": "A caixa de diálogo do Applet do controlador não aparecerá se o controle for desconectado enquanto um aplicativo estiver em execução.\n\nDeixe a opção DESLIGADO se não tiver certeza.",
"ru_RU": "Диалоговое окно апплета контроллера не будет отображаться, если геймпад отключен во время работы приложения.\n\nОставьте выключенным, если не уверены.",
"sv_SE": "",
"sv_SE": "Handkontroller-appleten kommer inte att visas om gamepaden är frånkopplad under tiden en applikation körs.\n\nLämna AV om du är osäker.",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Діалогове вікно Аплету Контролера не з'явиться, якщо геймпад було відключено під час роботи програми.\n\nЗалиште вимкненим якщо не впевнені.",
@@ -16989,7 +17139,7 @@
"pl_PL": "",
"pt_BR": "Imprime mensagens de log do Avalonia (UI) no console.",
"ru_RU": "Выводит сообщения журнала Avalonia (интерфейс) в консоли.",
"sv_SE": "",
"sv_SE": "Skriver ut loggmeddelanden från Avalonia (användargränssnittet) i konsollen.",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Виводити повідомлення журналу Avalonia (UI) в консоль",
@@ -17189,7 +17339,7 @@
"pl_PL": "",
"pt_BR": "Abre a pasta de capturas de tela do Ryujinx",
"ru_RU": "Открывает папку скриншотов Ryujinx",
"sv_SE": "",
"sv_SE": "Öppna Ryujinx skärmbildsmapp",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Відкрити теку куди зберігаються скріншоти Ryujinx",
@@ -17939,7 +18089,7 @@
"pl_PL": "",
"pt_BR": "Atualização Disponível!",
"ru_RU": "Доступно обновление!",
"sv_SE": "",
"sv_SE": "Uppdatering finns tillgänglig!",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Доступне оновлення!",
@@ -19522,6 +19672,31 @@
"zh_TW": "{0} 更新程式"
}
},
{
"ID": "RyujinxRebooter",
"Translations": {
"ar_SA": "إعادة تشغيل {0}",
"de_DE": "Neustart von {0}",
"el_GR": "Επανεκκίνηση {0}",
"en_US": "{0} Reboot",
"es_ES": "Reinicio de {0}",
"fr_FR": "Redémarrage de {0}",
"he_IL": "אתחול {0}",
"it_IT": "Riavvio di {0}",
"ja_JP": "{0} 再起動",
"ko_KR": "{0} 재부팅",
"no_NO": "Omstart av {0}",
"pl_PL": "Ponowne uruchomienie {0}",
"pt_BR": "Reinício de {0}",
"ru_RU": "{0} Перезагрузка",
"sv_SE": "Ominläsning av {0}",
"th_TH": "เริ่มต้นใหม่ {0}",
"tr_TR": "{0} Yeniden Başlatma",
"uk_UA": "Перезавантаження {0}",
"zh_CN": "{0} 重启",
"zh_TW": "{0} 重新啟動"
}
},
{
"ID": "SettingsTabHotkeys",
"Translations": {
@@ -19838,7 +20013,7 @@
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"ru_RU": "Амибо",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
@@ -23889,7 +24064,7 @@
"pl_PL": "",
"pt_BR": "Inicializa e roda sem travamentos ou bugs de GPU de qualquer tipo, e em uma velocidade rápida o suficiente para ser aproveitado em um PC comum.",
"ru_RU": "Запускается и работает без любого рода сбоев или графисечких ошибок и на скорости, достаточной для работы на обычном ПК.",
"sv_SE": "",
"sv_SE": "Startar upp och spelas utan några krascher eller GPU-fel av några slag och med en hastighet som är snabb nog för bra upplevelse på en genomsnittlig PC.",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Запускається та оптимально працює (без збоїв або графічних багів) на середньостатистичному комп'ютері.",
@@ -23914,7 +24089,7 @@
"pl_PL": "",
"pt_BR": "Inicializa e entra no jogo, mas sofre de um ou mais dos seguintes: travamentos, deadlocks, bugs de GPU, áudio ruim que distrai ou é simplesmente muito lento. O jogo ainda pode ser jogado até o fim, mas não da forma como foi criado para ser jogado.",
"ru_RU": "Запускается и работает, но возникает одна или несколько из следующих проблем: сбои, взаимоблокировки, ошибки GPU, отвлекающие звуки или просто слишком медленная работа. Возможно, игру всё же удастся пройти до конца, но не так, как она задумана.",
"sv_SE": "",
"sv_SE": "Startar och går in i spelet men lider av ett eller flera av följande: kraschar, deadlocks, GPU-buggar, distraherande dåligt ljud eller är helt enkelt för långsamt. Spelet kan fortfarande spelas hela vägen igenom, men inte så som spelet är avsett att spelas.",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Запускається, але в грі на вас чекатимуть одна або декілька наступних проблем: збої, зависання, графічні баги, спотворений звук або ж гра загалом працюватиме надто повільно. Можливо, її все ще можна пройти, але досвід буде не найкращим.",
@@ -23939,7 +24114,7 @@
"pl_PL": "",
"pt_BR": "Inicializa e passa da tela de título, mas não entra no jogo principal.",
"ru_RU": "Загружается титульный экран и можно перейти дальше, но сама игра не работает.",
"sv_SE": "",
"sv_SE": "Startar upp och går förbi titelskärmen men tar sig inte in i huvudspelet.",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Запускається та проходить початковий екран, але пограти не вийде.",
@@ -23964,7 +24139,7 @@
"pl_PL": "",
"pt_BR": "Inizializa, mas não passa da tela de título.",
"ru_RU": "Загружается, но не проходит дальше титульного экрана.",
"sv_SE": "",
"sv_SE": "Startar upp men tar sig inte förbi titelskärmen.",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Запускається, але не відображає навіть початкового екрану.",
@@ -23989,7 +24164,7 @@
"pl_PL": "",
"pt_BR": "Não inicializa ou não mostra sinais de atividade.",
"ru_RU": "Не запускается или не подаёт признаков жизни.",
"sv_SE": "",
"sv_SE": "Startar inte upp eller visar någon form av aktivitet.",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Взагалі не запускається.",
@@ -23997,6 +24172,56 @@
"zh_TW": ""
}
},
{
"ID": "GameSpecificConfigurationHeader",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Custom Config",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "GameSpecificConfigurationGlobal",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "(Global)",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "ExtractAocListHeader",
"Translations": {
@@ -24039,7 +24264,7 @@
"pl_PL": "",
"pt_BR": "Imagem da Presença do Discord",
"ru_RU": "Изображение для статуса активности",
"sv_SE": "",
"sv_SE": "Bild för Rich Presence",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Зображення картки активності Discord",
@@ -24064,7 +24289,7 @@
"pl_PL": "",
"pt_BR": "Presença Dinâmica do Discord",
"ru_RU": "Динамический статус активности",
"sv_SE": "",
"sv_SE": "Dynamisk Rich Presence",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Динамічна картка активності Discord",

View File

@@ -54,6 +54,7 @@ namespace Ryujinx.Ava.Common.Locale
SetDynamicValues(LocaleKeys.RyujinxInfo, RyujinxApp.FullAppName);
SetDynamicValues(LocaleKeys.RyujinxConfirm, RyujinxApp.FullAppName);
SetDynamicValues(LocaleKeys.RyujinxUpdater, RyujinxApp.FullAppName);
SetDynamicValues(LocaleKeys.RyujinxRebooter, RyujinxApp.FullAppName);
}
public string this[LocaleKeys key]

View File

@@ -32,8 +32,10 @@ namespace Ryujinx.Ava
public static double DesktopScaleFactor { get; set; } = 1.0;
public static string Version { get; private set; }
public static string ConfigurationPath { get; private set; }
public static string GlobalConfigurationPath { get; private set; }
public static bool PreviewerDetached { get; private set; }
public static bool UseHardwareAcceleration { get; private set; }
public static string BackendThreadingArg { get; private set; }
[LibraryImport("user32.dll", SetLastError = true)]
public static partial int MessageBoxA(nint hWnd, [MarshalAs(UnmanagedType.LPStr)] string text, [MarshalAs(UnmanagedType.LPStr)] string caption, uint type);
@@ -156,11 +158,38 @@ namespace Ryujinx.Ava
}
}
public static string GetDirGameUserConfig(string gameId, bool rememberGlobalDir = false, bool changeFolderForGame = false)
{
if (string.IsNullOrEmpty(gameId))
{
return "";
}
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;
}
return gameDir;
}
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);
// Now load the configuration as the other subsystems are now registered
if (File.Exists(localConfigurationPath))
{
@@ -217,6 +246,11 @@ namespace Ryujinx.Ava
_ => ConfigurationState.Instance.Graphics.BackendThreading
};
if (CommandLineState.OverrideBackendThreadingAfterReboot is not null)
{
BackendThreadingArg = CommandLineState.OverrideBackendThreadingAfterReboot;
}
// Check if docked mode was overriden.
if (CommandLineState.OverrideDockedMode.HasValue)
ConfigurationState.Instance.System.EnableDockedMode.Value = CommandLineState.OverrideDockedMode.Value;
@@ -232,6 +266,33 @@ namespace Ryujinx.Ava
_ => ConfigurationState.Instance.HideCursor,
};
// Check if memoryManagerMode was overridden.
if (CommandLineState.OverrideMemoryManagerMode is not null)
if (Enum.TryParse(CommandLineState.OverrideMemoryManagerMode, true, out MemoryManagerMode result))
{
ConfigurationState.Instance.System.MemoryManagerMode.Value = result;
}
// Check if PPTC was overridden.
if (CommandLineState.OverridePPTC is not null)
if (Enum.TryParse(CommandLineState.OverridePPTC, true, out bool result))
{
ConfigurationState.Instance.System.EnablePtc.Value = result;
}
// Check if region was overridden.
if (CommandLineState.OverrideSystemRegion is not null)
if (Enum.TryParse(CommandLineState.OverrideSystemRegion, true, out Ryujinx.HLE.HOS.SystemState.RegionCode result))
{
ConfigurationState.Instance.System.Region.Value = (Utilities.Configuration.System.Region)result;
}
//Check if language was overridden.
if (CommandLineState.OverrideSystemLanguage is not null)
if (Enum.TryParse(CommandLineState.OverrideSystemLanguage, true, out Ryujinx.HLE.HOS.SystemState.SystemLanguage result))
{
ConfigurationState.Instance.System.Language.Value = (Utilities.Configuration.System.Language)result;
}
// Check if hardware-acceleration was overridden.
if (CommandLineState.OverrideHardwareAcceleration != null)

76
src/Ryujinx/Rebooter.cs Normal file
View File

@@ -0,0 +1,76 @@
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.Utilities;
using SkiaSharp;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace Ryujinx.Ava
{
internal static class Rebooter
{
private static readonly string _updateDir = Path.Combine(Path.GetTempPath(), "Ryujinx", "update");
public static void RebootAppWithGame(string gamePath, List<string> args)
{
_ = Reboot(gamePath, args);
}
private static async Task Reboot(string gamePath, List<string> args)
{
bool shouldRestart = true;
TaskDialog taskDialog = new()
{
Header = LocaleManager.Instance[LocaleKeys.RyujinxRebooter],
SubHeader = LocaleManager.Instance[LocaleKeys.DialogRebooterMessage],
IconSource = new SymbolIconSource { Symbol = Symbol.Games },
XamlRoot = RyujinxApp.MainWindow,
};
if (shouldRestart)
{
List<string> arguments = CommandLineState.Arguments.ToList();
string executableDirectory = AppDomain.CurrentDomain.BaseDirectory;
var dialogTask = taskDialog.ShowAsync(true);
await Task.Delay(500);
// Find the process name.
string ryuName = Path.GetFileName(Environment.ProcessPath) ?? string.Empty;
// Fallback if the executable could not be found.
if (ryuName.Length == 0 || !Path.Exists(Path.Combine(executableDirectory, ryuName)))
{
ryuName = OperatingSystem.IsWindows() ? "Ryujinx.exe" : "Ryujinx";
}
ProcessStartInfo processStart = new(ryuName)
{
UseShellExecute = true,
WorkingDirectory = executableDirectory,
};
foreach (var arg in args)
{
processStart.ArgumentList.Add(arg);
}
processStart.ArgumentList.Add(gamePath);
Process.Start(processStart);
Environment.Exit(0);
}
}
}
}

View File

@@ -95,7 +95,7 @@ namespace Ryujinx.Ava.UI.Applet
_parent.SettingsWindow =
new SettingsWindow(_parent.VirtualFileSystem, _parent.ContentManager);
await _parent.SettingsWindow.ShowDialog(window);
await StyleableAppWindow.ShowAsync(_parent.SettingsWindow, window);
_parent.SettingsWindow = null;

View File

@@ -17,11 +17,7 @@
<viewModels:ProfileSelectorDialogViewModel />
</Design.DataContext>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" RowDefinitions="*,Auto">
<Border
CornerRadius="5"

View File

@@ -9,17 +9,14 @@ using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Services.Account.Acc;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Threading.Tasks;
using UserProfile = Ryujinx.Ava.UI.Models.UserProfile;
using UserProfileSft = Ryujinx.HLE.HOS.Services.Account.Acc.UserProfile;
namespace Ryujinx.Ava.UI.Applet
{
public partial class ProfileSelectorDialog : UserControl
public partial class ProfileSelectorDialog : RyujinxControl<ProfileSelectorDialogViewModel>
{
public ProfileSelectorDialogViewModel ViewModel { get; set; }
public ProfileSelectorDialog(ProfileSelectorDialogViewModel viewModel)
{
DataContext = ViewModel = viewModel;

View File

@@ -19,6 +19,18 @@
Header="{ext:Locale GameListContextMenuCreateShortcut}"
Icon="{ext:Icon fa-solid fa-bookmark}"
ToolTip.Tip="{OnPlatform Default={ext:Locale GameListContextMenuCreateShortcutToolTip}, macOS={ext:Locale GameListContextMenuCreateShortcutToolTipMacOS}}" />
<MenuItem
Click="EditGameConfiguration_Click"
IsVisible="{Binding SelectedApplication.HasIndependentConfiguration}"
Header="{ext:Locale GameListContextMenuEditCustomConfiguration}"
Icon="{ext:Icon fa-solid fa-gear}"
ToolTip.Tip="{ext:Locale EditCustomConfigurationToolTip}" />
<MenuItem
Click="EditGameConfiguration_Click"
IsVisible="{Binding !SelectedApplication.HasIndependentConfiguration}"
Header="{ext:Locale GameListContextMenuCreateCustomConfiguration}"
Icon="{ext:Icon fa-solid fa-gear}"
ToolTip.Tip="{ext:Locale CreateCustomConfigurationToolTip}" />
<MenuItem
IsVisible="{Binding HasCompatibilityEntry}"
Click="OpenApplicationCompatibility_Click"

View File

@@ -26,6 +26,7 @@ namespace Ryujinx.Ava.UI.Controls
{
public class ApplicationContextMenu : MenuFlyout
{
public ApplicationContextMenu()
{
InitializeComponent();
@@ -91,11 +92,14 @@ namespace Ryujinx.Ava.UI.Controls
public async void OpenCheatManager_Click(object sender, RoutedEventArgs args)
{
if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
await new CheatWindow(
await StyleableAppWindow.ShowAsync(
new CheatWindow(
viewModel.VirtualFileSystem,
viewModel.SelectedApplication.IdString,
viewModel.SelectedApplication.Name,
viewModel.SelectedApplication.Path).ShowDialog((Window)viewModel.TopLevel);
viewModel.SelectedApplication.Path
)
);
}
public void OpenModsDirectory_Click(object sender, RoutedEventArgs args)
@@ -387,6 +391,19 @@ namespace Ryujinx.Ava.UI.Controls
);
}
public async void EditGameConfiguration_Click(object sender, RoutedEventArgs args)
{
if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
{
await StyleableAppWindow.ShowAsync(new GameSpecificSettingsWindow(viewModel));
// just checking for file presence
viewModel.SelectedApplication.HasIndependentConfiguration = File.Exists(Program.GetDirGameUserConfig(viewModel.SelectedApplication.IdString,false,false));
viewModel.RefreshView();
}
}
public async void OpenApplicationCompatibility_Click(object sender, RoutedEventArgs args)
{
if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })

View File

@@ -14,7 +14,7 @@ using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.Controls
{
public partial class ApplicationDataView : UserControl
public partial class ApplicationDataView : RyujinxControl<ApplicationDataViewModel>
{
public static async Task Show(ApplicationData appData)
{
@@ -25,7 +25,7 @@ namespace Ryujinx.Ava.UI.Controls
SecondaryButtonText = string.Empty,
CloseButtonText = LocaleManager.Instance[LocaleKeys.SettingsButtonClose],
MinWidth = 256,
Content = new ApplicationDataView { DataContext = new ApplicationDataViewModel(appData) }
Content = new ApplicationDataView { ViewModel = new ApplicationDataViewModel(appData) }
};
Style closeButton = new(x => x.Name("CloseButton"));

View File

@@ -7,16 +7,14 @@
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
d:DesignHeight="450"
d:DesignWidth="800"
Focusable="True"
mc:Ignorable="d"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
x:DataType="viewModels:MainWindowViewModel">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid RowDefinitions="*">
<ListBox
Grid.Row="0"
Padding="8"
@@ -56,11 +54,7 @@
Classes.small="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridSmall}"
ClipToBounds="True"
CornerRadius="4">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid RowDefinitions="Auto,Auto">
<Image
Grid.Row="0"
HorizontalAlignment="Stretch"
@@ -73,12 +67,18 @@
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
IsVisible="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).ShowNames}">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding Name}"
TextAlignment="Center"
TextWrapping="Wrap" />
<TextBlock
IsVisible="{Binding HasIndependentConfiguration}"
Text="{ext:Locale GameSpecificConfigurationHeader}"
TextAlignment="Center"
TextWrapping="Wrap"
Foreground="{DynamicResource Warning}" />
</StackPanel>
</Panel>
</Grid>
</Border>
@@ -86,10 +86,28 @@
Margin="5,5,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
FontSize="16"
FontSize="18"
Foreground="{DynamicResource FavoriteApplicationIconColor}"
IsVisible="{Binding Favorite}"
Symbol="StarFilled" />
<Grid IsVisible="{Binding !$parent[UserControl].((viewModels:MainWindowViewModel)DataContext).ShowNames}">
<Border
Margin="15,35,5,15"
HorizontalAlignment="Left"
VerticalAlignment="Bottom"
Width="90"
Height="20"
CornerRadius="4"
IsVisible="{Binding HasIndependentConfiguration}"
Background="{DynamicResource ThemeContentBackgroundColor}">
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{ext:Locale GameSpecificConfigurationHeader}"
TextAlignment="Center"
TextWrapping="Wrap" />
</Border>
</Grid>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>

View File

@@ -2,12 +2,13 @@ using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.Utilities.AppLibrary;
using System;
namespace Ryujinx.Ava.UI.Controls
{
public partial class ApplicationGridView : UserControl
public partial class ApplicationGridView : RyujinxControl<MainWindowViewModel>
{
public static readonly RoutedEvent<ApplicationOpenedEventArgs> ApplicationOpenedEvent =
RoutedEvent.Register<ApplicationGridView, ApplicationOpenedEventArgs>(nameof(ApplicationOpened), RoutingStrategies.Bubble);

View File

@@ -6,16 +6,14 @@
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
d:DesignHeight="450"
d:DesignWidth="800"
Focusable="True"
mc:Ignorable="d"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
x:DataType="viewModels:MainWindowViewModel">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid RowDefinitions="*">
<ListBox
Name="GameListBox"
Grid.Row="0"
@@ -156,6 +154,13 @@
Text="{Binding Converter={x:Static helpers:MultiplayerInfoConverter.Instance}}"
TextAlignment="Start"
TextWrapping="Wrap"/>
<TextBlock
HorizontalAlignment="Stretch"
IsVisible="{Binding HasIndependentConfiguration}"
Text="{ext:Locale GameSpecificConfigurationHeader}"
TextAlignment="Start"
TextWrapping="Wrap"
Foreground="{DynamicResource Warning}" />
</StackPanel>
<StackPanel
Grid.Column="4"

View File

@@ -11,7 +11,7 @@ using System.Linq;
namespace Ryujinx.Ava.UI.Controls
{
public partial class ApplicationListView : UserControl
public partial class ApplicationListView : RyujinxControl<MainWindowViewModel>
{
public static readonly RoutedEvent<ApplicationOpenedEventArgs> ApplicationOpenedEvent =
RoutedEvent.Register<ApplicationListView, ApplicationOpenedEventArgs>(nameof(ApplicationOpened), RoutingStrategies.Bubble);
@@ -32,9 +32,6 @@ namespace Ryujinx.Ava.UI.Controls
private async void PlayabilityStatus_OnClick(object sender, RoutedEventArgs e)
{
if (DataContext is not MainWindowViewModel mwvm)
return;
if (sender is not Button { Content: TextBlock playabilityLabel })
return;
@@ -43,16 +40,13 @@ namespace Ryujinx.Ava.UI.Controls
private async void IdString_OnClick(object sender, RoutedEventArgs e)
{
if (DataContext is not MainWindowViewModel mwvm)
return;
if (sender is not Button { Content: TextBlock idText })
return;
if (!RyujinxApp.IsClipboardAvailable(out IClipboard clipboard))
return;
ApplicationData appData = mwvm.Applications.FirstOrDefault(it => it.IdString == idText.Text);
ApplicationData appData = ViewModel.Applications.FirstOrDefault(it => it.IdString == idText.Text);
if (appData is null)
return;

View File

@@ -10,7 +10,7 @@ using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.Controls
{
public partial class DlcSelectView : UserControl
public partial class DlcSelectView : RyujinxControl<DlcSelectViewModel>
{
public DlcSelectView()
{
@@ -28,7 +28,7 @@ namespace Ryujinx.Ava.UI.Controls
PrimaryButtonText = LocaleManager.Instance[LocaleKeys.Continue],
SecondaryButtonText = string.Empty,
CloseButtonText = string.Empty,
Content = new DlcSelectView { DataContext = viewModel }
Content = new DlcSelectView { ViewModel = viewModel }
};
Style closeButton = new(x => x.Name("CloseButton"));

View File

@@ -23,13 +23,12 @@ using UserProfile = Ryujinx.Ava.UI.Models.UserProfile;
namespace Ryujinx.Ava.UI.Controls
{
public partial class NavigationDialogHost : UserControl
public partial class NavigationDialogHost : RyujinxControl<UserProfileViewModel>
{
public AccountManager AccountManager { get; }
public ContentManager ContentManager { get; }
public VirtualFileSystem VirtualFileSystem { get; }
public HorizonClient HorizonClient { get; }
public UserProfileViewModel ViewModel { get; set; }
public NavigationDialogHost()
{

View File

@@ -0,0 +1,18 @@
using Avalonia.Controls;
using Gommon;
using Ryujinx.Ava.UI.ViewModels;
using System;
namespace Ryujinx.Ava.UI.Controls
{
public class RyujinxControl<TViewModel> : UserControl where TViewModel : BaseModel
{
public TViewModel ViewModel
{
get => (TViewModel)DataContext ?? throw new InvalidOperationException(
$"Underlying DataContext is not of type {typeof(TViewModel).AsPrettyString()}; " +
$"Actual type is {DataContext?.GetType().AsPrettyString()}");
set => DataContext = value;
}
}
}

View File

@@ -13,15 +13,7 @@
<Grid
Margin="20"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
VerticalAlignment="Stretch" ColumnDefinitions="Auto,*" RowDefinitions="Auto,Auto">
<Image
Grid.Row="1"
Height="70"

View File

@@ -1,6 +1,7 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Threading;
@@ -385,6 +386,10 @@ namespace Ryujinx.Ava.UI.Helpers
ShowInTaskbar = false,
};
#if DEBUG
_contentDialogOverlayWindow.AttachDevTools(new KeyGesture(Key.F12, KeyModifiers.Control));
#endif
parent.PositionChanged += OverlayOnPositionChanged;
void OverlayOnPositionChanged(object sender, PixelPointEventArgs e)

View File

@@ -0,0 +1,260 @@
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.UI.ViewModels.Input;
using Ryujinx.Input;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.Models.Input
{
public class StickVisualizer : BaseModel, IDisposable
{
public const int DrawStickPollRate = 50; // Milliseconds per poll.
public const int DrawStickCircumference = 5;
public const float DrawStickScaleFactor = DrawStickCanvasCenter;
public const int DrawStickCanvasSize = 100;
public const int DrawStickBorderSize = DrawStickCanvasSize + 5;
public const float DrawStickCanvasCenter = (DrawStickCanvasSize - DrawStickCircumference) / 2;
public const float MaxVectorLength = DrawStickCanvasSize / 2;
public CancellationTokenSource PollTokenSource;
public CancellationToken PollToken;
private static float _vectorLength;
private static float _vectorMultiplier;
private bool disposedValue;
private DeviceType _type;
public DeviceType Type
{
get => _type;
set
{
_type = value;
OnPropertyChanged();
}
}
private GamepadInputConfig _gamepadConfig;
public GamepadInputConfig GamepadConfig
{
get => _gamepadConfig;
set
{
_gamepadConfig = value;
OnPropertyChanged();
}
}
private KeyboardInputConfig _keyboardConfig;
public KeyboardInputConfig KeyboardConfig
{
get => _keyboardConfig;
set
{
_keyboardConfig = value;
OnPropertyChanged();
}
}
private (float, float) _uiStickLeft;
public (float, float) UiStickLeft
{
get => (_uiStickLeft.Item1 * DrawStickScaleFactor, _uiStickLeft.Item2 * DrawStickScaleFactor);
set
{
_uiStickLeft = value;
OnPropertyChanged();
OnPropertyChanged(nameof(UiStickRightX));
OnPropertyChanged(nameof(UiStickRightY));
OnPropertyChanged(nameof(UiDeadzoneRight));
}
}
private (float, float) _uiStickRight;
public (float, float) UiStickRight
{
get => (_uiStickRight.Item1 * DrawStickScaleFactor, _uiStickRight.Item2 * DrawStickScaleFactor);
set
{
_uiStickRight = value;
OnPropertyChanged();
OnPropertyChanged(nameof(UiStickLeftX));
OnPropertyChanged(nameof(UiStickLeftY));
OnPropertyChanged(nameof(UiDeadzoneLeft));
}
}
public float UiStickLeftX => ClampVector(UiStickLeft).Item1;
public float UiStickLeftY => ClampVector(UiStickLeft).Item2;
public float UiStickRightX => ClampVector(UiStickRight).Item1;
public float UiStickRightY => ClampVector(UiStickRight).Item2;
public int UiStickCircumference => DrawStickCircumference;
public int UiCanvasSize => DrawStickCanvasSize;
public int UiStickBorderSize => DrawStickBorderSize;
public float? UiDeadzoneLeft => _gamepadConfig?.DeadzoneLeft * DrawStickCanvasSize - DrawStickCircumference;
public float? UiDeadzoneRight => _gamepadConfig?.DeadzoneRight * DrawStickCanvasSize - DrawStickCircumference;
private InputViewModel Parent;
public StickVisualizer(InputViewModel parent)
{
Parent = parent;
PollTokenSource = new CancellationTokenSource();
PollToken = PollTokenSource.Token;
Task.Run(Initialize, PollToken);
}
public void UpdateConfig(object config)
{
if (config is ControllerInputViewModel padConfig)
{
GamepadConfig = padConfig.Config;
Type = DeviceType.Controller;
return;
}
else if (config is KeyboardInputViewModel keyConfig)
{
KeyboardConfig = keyConfig.Config;
Type = DeviceType.Keyboard;
return;
}
Type = DeviceType.None;
}
public async Task Initialize()
{
(float, float) leftBuffer;
(float, float) rightBuffer;
while (!PollToken.IsCancellationRequested)
{
leftBuffer = (0f, 0f);
rightBuffer = (0f, 0f);
switch (Type)
{
case DeviceType.Keyboard:
IKeyboard keyboard = (IKeyboard)Parent.AvaloniaKeyboardDriver.GetGamepad("0");
if (keyboard != null)
{
KeyboardStateSnapshot snapshot = keyboard.GetKeyboardStateSnapshot();
if (snapshot.IsPressed((Key)KeyboardConfig.LeftStickRight))
{
leftBuffer.Item1 += 1;
}
if (snapshot.IsPressed((Key)KeyboardConfig.LeftStickLeft))
{
leftBuffer.Item1 -= 1;
}
if (snapshot.IsPressed((Key)KeyboardConfig.LeftStickUp))
{
leftBuffer.Item2 += 1;
}
if (snapshot.IsPressed((Key)KeyboardConfig.LeftStickDown))
{
leftBuffer.Item2 -= 1;
}
if (snapshot.IsPressed((Key)KeyboardConfig.RightStickRight))
{
rightBuffer.Item1 += 1;
}
if (snapshot.IsPressed((Key)KeyboardConfig.RightStickLeft))
{
rightBuffer.Item1 -= 1;
}
if (snapshot.IsPressed((Key)KeyboardConfig.RightStickUp))
{
rightBuffer.Item2 += 1;
}
if (snapshot.IsPressed((Key)KeyboardConfig.RightStickDown))
{
rightBuffer.Item2 -= 1;
}
UiStickLeft = leftBuffer;
UiStickRight = rightBuffer;
}
break;
case DeviceType.Controller:
IGamepad controller = Parent.SelectedGamepad;
if (controller != null)
{
leftBuffer = controller.GetStick((StickInputId)GamepadConfig.LeftJoystick);
rightBuffer = controller.GetStick((StickInputId)GamepadConfig.RightJoystick);
}
break;
case DeviceType.None:
break;
default:
throw new ArgumentException($"Unable to poll device type \"{Type}\"");
}
UiStickLeft = leftBuffer;
UiStickRight = rightBuffer;
await Task.Delay(DrawStickPollRate, PollToken);
}
PollTokenSource.Dispose();
}
public static (float, float) ClampVector((float, float) vect)
{
_vectorMultiplier = 1;
_vectorLength = MathF.Sqrt((vect.Item1 * vect.Item1) + (vect.Item2 * vect.Item2));
if (_vectorLength > MaxVectorLength)
{
_vectorMultiplier = MaxVectorLength / _vectorLength;
}
vect.Item1 = vect.Item1 * _vectorMultiplier + DrawStickCanvasCenter;
vect.Item2 = vect.Item2 * _vectorMultiplier + DrawStickCanvasCenter;
return vect;
}
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
PollTokenSource.Cancel();
}
KeyboardConfig = null;
GamepadConfig = null;
Parent = null;
disposedValue = true;
}
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
}

View File

@@ -2,9 +2,7 @@
using Avalonia.Controls;
using Avalonia.Media;
using Ryujinx.Ava.Utilities.Configuration;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using System;
namespace Ryujinx.Ava.UI.Renderer
@@ -39,32 +37,6 @@ namespace Ryujinx.Ava.UI.Renderer
_ => throw new NotImplementedException()
};
public RendererHost(string titleId)
{
Focusable = true;
FlowDirection = FlowDirection.LeftToRight;
EmbeddedWindow =
#pragma warning disable CS8524
ConfigurationState.Instance.Graphics.GraphicsBackend.Value switch
#pragma warning restore CS8524
{
GraphicsBackend.OpenGl => new EmbeddedWindowOpenGL(),
GraphicsBackend.Vulkan => new EmbeddedWindowVulkan(),
};
string backendText = EmbeddedWindow switch
{
EmbeddedWindowVulkan => "Vulkan",
EmbeddedWindowOpenGL => "OpenGL",
_ => throw new NotImplementedException()
};
Logger.Info?.PrintMsg(LogClass.Gpu, $"Backend ({ConfigurationState.Instance.Graphics.GraphicsBackend.Value}): {backendText}");
Initialize();
}
private void Initialize()
{

View File

@@ -1,5 +1,9 @@
using Avalonia.Svg.Skia;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Input;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models.Input;
using Ryujinx.Ava.UI.Views.Input;
using Ryujinx.Common.Utilities;
@@ -10,7 +14,29 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
{
public partial class ControllerInputViewModel : BaseModel
{
[ObservableProperty] private GamepadInputConfig _config;
private GamepadInputConfig _config;
public GamepadInputConfig Config
{
get => _config;
set
{
_config = value;
OnPropertyChanged();
}
}
private StickVisualizer _visualizer;
public StickVisualizer Visualizer
{
get => _visualizer;
set
{
_visualizer = value;
OnPropertyChanged();
}
}
private bool _isLeft;
public bool IsLeft
@@ -42,9 +68,10 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
public InputViewModel ParentModel { get; }
public ControllerInputViewModel(InputViewModel model, GamepadInputConfig config)
public ControllerInputViewModel(InputViewModel model, GamepadInputConfig config, StickVisualizer visualizer)
{
ParentModel = model;
Visualizer = visualizer;
model.NotifyChangesEvent += OnParentModelChanged;
OnParentModelChanged();
config.PropertyChanged += (_, args) =>

View File

@@ -49,7 +49,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
private int _controller;
private string _controllerImage;
private int _device;
[ObservableProperty] private object _configViewModel;
private object _configViewModel;
[ObservableProperty] private string _profileName;
private bool _isLoaded;
@@ -74,6 +74,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
OnPropertiesChanged(nameof(HasLed), nameof(CanClearLed));
}
}
public StickVisualizer VisualStick { get; private set; }
public ObservableCollection<PlayerModel> PlayerIndexes { get; set; }
public ObservableCollection<(DeviceType Type, string Id, string Name)> Devices { get; set; }
@@ -94,6 +95,19 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
public bool IsModified { get; set; }
public event Action NotifyChangesEvent;
public object ConfigViewModel
{
get => _configViewModel;
set
{
_configViewModel = value;
VisualStick.UpdateConfig(value);
OnPropertyChanged();
}
}
public PlayerIndex PlayerIdChoose
{
get => _playerIdChoose;
@@ -269,6 +283,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
Devices = [];
ProfilesList = [];
DeviceList = [];
VisualStick = new StickVisualizer(this);
ControllerImage = ProControllerResource;
@@ -289,12 +304,12 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
if (Config is StandardKeyboardInputConfig keyboardInputConfig)
{
ConfigViewModel = new KeyboardInputViewModel(this, new KeyboardInputConfig(keyboardInputConfig));
ConfigViewModel = new KeyboardInputViewModel(this, new KeyboardInputConfig(keyboardInputConfig), VisualStick);
}
if (Config is StandardControllerInputConfig controllerInputConfig)
{
ConfigViewModel = new ControllerInputViewModel(this, new GamepadInputConfig(controllerInputConfig));
ConfigViewModel = new ControllerInputViewModel(this, new GamepadInputConfig(controllerInputConfig), VisualStick);
}
}
@@ -893,6 +908,8 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
_mainWindow.ViewModel.AppHost?.NpadManager.UnblockInputUpdates();
VisualStick.Dispose();
SelectedGamepad?.Dispose();
AvaloniaKeyboardDriver.Dispose();

View File

@@ -6,7 +6,29 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
{
public partial class KeyboardInputViewModel : BaseModel
{
[ObservableProperty] private KeyboardInputConfig _config;
private KeyboardInputConfig _config;
public KeyboardInputConfig Config
{
get => _config;
set
{
_config = value;
OnPropertyChanged();
}
}
private StickVisualizer _visualizer;
public StickVisualizer Visualizer
{
get => _visualizer;
set
{
_visualizer = value;
OnPropertyChanged();
}
}
private bool _isLeft;
public bool IsLeft
@@ -38,9 +60,10 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
public readonly InputViewModel ParentModel;
public KeyboardInputViewModel(InputViewModel model, KeyboardInputConfig config)
public KeyboardInputViewModel(InputViewModel model, KeyboardInputConfig config, StickVisualizer visualizer)
{
ParentModel = model;
Visualizer = visualizer;
model.NotifyChangesEvent += OnParentModelChanged;
OnParentModelChanged();
Config = config;

View File

@@ -356,6 +356,11 @@ namespace Ryujinx.Ava.UI.ViewModels
_ => null,
};
}
set
{
ListSelectedApplication = value;
GridSelectedApplication = value;
}
}
public bool HasCompatibilityEntry => SelectedApplication.HasPlayabilityInfo;
@@ -1551,8 +1556,50 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
public bool InitializeUserConfig(ApplicationData application)
{
// Code where conditions will be met before loading the user configuration (Global Config)
BackendThreading backendThreadingValue = ConfigurationState.Instance.Graphics.BackendThreading.Value;
string BackendThreadingInit = Program.BackendThreadingArg;
if (BackendThreadingInit is null)
{
BackendThreadingInit = 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);
}
// Code where conditions will be executed after loading user configuration
if (ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString() != BackendThreadingInit)
{
List<string> Arguments = new List<string>
{
"--bt", ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString() // BackendThreading
};
Rebooter.RebootAppWithGame(application.Path, Arguments);
return true;
}
return false;
}
public async Task LoadApplication(ApplicationData application, bool startFullscreen = false, BlitStruct<ApplicationControlProperty>? customNacpData = null)
{
if (InitializeUserConfig(application))
{
return;
}
if (AppHost != null)
{
await ContentDialogHelper.CreateInfoDialog(
@@ -1613,6 +1660,7 @@ namespace Ryujinx.Ava.UI.ViewModels
Thread gameThread = new(InitializeGame) { Name = "GUI.WindowThread" };
gameThread.Start();
}
public void SwitchToRenderer(bool startFullscreen) =>
@@ -1699,7 +1747,7 @@ namespace Ryujinx.Ava.UI.ViewModels
string titleId = AppHost.Device.Processes.ActiveApplication.ProgramIdText.ToUpper();
AmiiboWindow window = new(ShowAll, LastScannedAmiiboId, titleId);
await window.ShowDialog(Window);
await StyleableAppWindow.ShowAsync(window);
if (window.IsScanned)
{

View File

@@ -1,5 +1,6 @@
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Media.Imaging;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
@@ -27,6 +28,7 @@ using Ryujinx.HLE.HOS.Services.Time.TimeZone;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Net.NetworkInformation;
using System.Threading.Tasks;
@@ -68,6 +70,19 @@ namespace Ryujinx.Ava.UI.ViewModels
public SettingsHacksViewModel DirtyHacks { get; }
private readonly bool _isGameRunning;
private Bitmap _gameIcon;
private string _gameTitle;
private string _gamePath;
private string _gameId;
public bool IsGameRunning => _isGameRunning;
public Bitmap GameIcon => _gameIcon;
public string GamePath => _gamePath;
public string GameTitle => _gameTitle;
public string GameId => _gameId;
public bool IsGameTitleNotNull => !string.IsNullOrEmpty(GameTitle);
public double PanelOpacity => IsGameTitleNotNull ? 0.5 : 1;
public int ResolutionScale
{
get => _resolutionScale;
@@ -335,7 +350,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool IsInvalidLdnPassphraseVisible { get; set; }
public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this()
public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this(false)
{
_virtualFileSystem = virtualFileSystem;
_contentManager = contentManager;
@@ -348,7 +363,51 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
public SettingsViewModel()
public SettingsViewModel(
VirtualFileSystem virtualFileSystem,
ContentManager contentManager,
bool gameRunning,
string gamePath,
string gameName,
string gameId,
byte[] gameIconData,
bool enableToLoadCustomConfig) : this(enableToLoadCustomConfig)
{
_virtualFileSystem = virtualFileSystem;
_contentManager = contentManager;
if (gameIconData != null && gameIconData.Length > 0)
{
using (var ms = new MemoryStream(gameIconData))
{
_gameIcon = new Bitmap(ms);
}
}
_isGameRunning = gameRunning;
_gamePath = gamePath;
_gameTitle = gameName;
_gameId = gameId;
if (enableToLoadCustomConfig) // During the game. If there is no user config, then load the global config window
{
string gameDir = Program.GetDirGameUserConfig(gameId, false, true);
if (ConfigurationFileFormat.TryLoad(gameDir, out ConfigurationFileFormat configurationFileFormat))
{
ConfigurationState.Instance.Load(configurationFileFormat, gameDir, gameId);
}
LoadCurrentConfiguration(); // Needed to load custom configuration
}
if (Program.PreviewerDetached)
{
Task.Run(LoadTimeZones);
}
}
public SettingsViewModel(bool noLoadGlobalConfig = false)
{
GameDirectories = [];
AutoloadDirectories = [];
@@ -363,6 +422,8 @@ namespace Ryujinx.Ava.UI.ViewModels
if (Program.PreviewerDetached)
{
Task.Run(LoadAvailableGpus);
// if (!noLoadGlobalConfig)// Default is false, but loading custom config avoids double call
LoadCurrentConfiguration();
DirtyHacks = new SettingsHacksViewModel(this);
@@ -711,15 +772,39 @@ namespace Ryujinx.Ava.UI.ViewModels
}
private static void RevertIfNotSaved()
{
// maybe this is an unnecessary check(all options need to be tested)
if (string.IsNullOrEmpty(Program.GlobalConfigurationPath))
{
Program.ReloadConfig();
}
}
public void ApplyButton()
{
SaveSettings();
}
public void DeleteConfigGame()
{
string gameDir = Program.GetDirGameUserConfig(GameId,false,false);
if (File.Exists(gameDir))
{
File.Delete(gameDir);
}
RevertIfNotSaved();
CloseWindow?.Invoke();
}
public void SaveUserConfig()
{
SaveSettings();
RevertIfNotSaved(); // Revert global configuration after saving user configuration
CloseWindow?.Invoke();
}
public void OkButton()
{
SaveSettings();

View File

@@ -21,7 +21,7 @@ using Image = SkiaSharp.SKImage;
namespace Ryujinx.Ava.UI.ViewModels
{
internal partial class UserFirmwareAvatarSelectorViewModel : BaseModel
public partial class UserFirmwareAvatarSelectorViewModel : BaseModel
{
private static readonly Dictionary<string, byte[]> _avatarStore = new();

View File

@@ -2,7 +2,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
namespace Ryujinx.Ava.UI.ViewModels
{
internal partial class UserProfileImageSelectorViewModel : BaseModel
public partial class UserProfileImageSelectorViewModel : BaseModel
{
[ObservableProperty] private bool _firmwareFound;
}

View File

@@ -34,12 +34,7 @@
<!-- Button / JoyStick Settings -->
<Grid
Name="SettingButtons"
MinHeight="450">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
MinHeight="450" ColumnDefinitions="Auto,*,Auto">
<!-- Left Controls -->
<StackPanel
Orientation="Vertical"
@@ -54,15 +49,7 @@
CornerRadius="5">
<Grid
Margin="10"
HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
HorizontalAlignment="Stretch" ColumnDefinitions="*,*" RowDefinitions="*,*">
<StackPanel
Grid.Column="0"
Grid.Row="0"
@@ -316,17 +303,99 @@
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<!-- Controller Picture -->
<Image
Margin="0,10"
MaxHeight="300"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Source="{Binding Image}" />
<Border
BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="1"
CornerRadius="5"
Margin="0,0, 0, 5"
MinHeight="90">
<StackPanel Orientation="Vertical">
<Image
Margin="5,10"
MaxHeight="300"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Source="{Binding Image}" />
<StackPanel
Margin="10"
Orientation="Horizontal"
Spacing="20"
HorizontalAlignment="Center">
<Border
BorderBrush="Transparent"
Height="{Binding Visualizer.UiStickBorderSize}"
Width="{Binding Visualizer.UiStickBorderSize}"
IsVisible="{Binding IsLeft}">
<Canvas
Background="{DynamicResource ThemeBackgroundColor}"
Height="{Binding Visualizer.UiCanvasSize}"
Width="{Binding Visualizer.UiCanvasSize}">
<Grid
Height="{Binding Visualizer.UiCanvasSize}"
Width="{Binding Visualizer.UiCanvasSize}"
Background="{DynamicResource ThemeBackgroundColor}">
<Ellipse
HorizontalAlignment="Center"
Stroke="{DynamicResource ThemeControlBorderColor}"
StrokeThickness="1"
Width="{Binding Visualizer.UiCanvasSize}"
Height="{Binding Visualizer.UiCanvasSize}" />
<Ellipse
HorizontalAlignment="Center"
Fill="Black"
Opacity="100"
Height="{Binding Visualizer.UiDeadzoneLeft}"
Width="{Binding Visualizer.UiDeadzoneLeft}" />
</Grid>
<Ellipse
Fill="{DynamicResource Warning}"
Width="{Binding Visualizer.UiStickCircumference}"
Height="{Binding Visualizer.UiStickCircumference}"
Canvas.Bottom="{Binding Visualizer.UiStickLeftY}"
Canvas.Left="{Binding Visualizer.UiStickLeftX}" />
</Canvas>
</Border>
<Border
BorderBrush="Transparent"
Height="{Binding Visualizer.UiStickBorderSize}"
Width="{Binding Visualizer.UiStickBorderSize}"
IsVisible="{Binding IsRight}">
<Canvas
Background="{DynamicResource ThemeBackgroundColor}"
Height="{Binding Visualizer.UiCanvasSize}"
Width="{Binding Visualizer.UiCanvasSize}">
<Grid
Height="{Binding Visualizer.UiCanvasSize}"
Width="{Binding Visualizer.UiCanvasSize}"
Background="{DynamicResource ThemeBackgroundColor}">
<Ellipse
HorizontalAlignment="Center"
Stroke="{DynamicResource ThemeControlBorderColor}"
StrokeThickness="1"
Width="{Binding Visualizer.UiCanvasSize}"
Height="{Binding Visualizer.UiCanvasSize}" />
<Ellipse
HorizontalAlignment="Center"
Fill="Black"
Opacity="100"
Height="{Binding Visualizer.UiDeadzoneRight}"
Width="{Binding Visualizer.UiDeadzoneRight}" />
</Grid>
<Ellipse
Fill="{DynamicResource Warning}"
Width="{Binding Visualizer.UiStickCircumference}"
Height="{Binding Visualizer.UiStickCircumference}"
Canvas.Bottom="{Binding Visualizer.UiStickRightY}"
Canvas.Left="{Binding Visualizer.UiStickRightX}" />
</Canvas>
</Border>
</StackPanel>
</StackPanel>
</Border>
<Border
BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="1"
CornerRadius="5">
<StackPanel
Margin="8"
Orientation="Vertical">
@@ -428,7 +497,7 @@
</Border>
<!-- Motion, Rumble, LED -->
<StackPanel
Margin="0,10,0,0"
Margin="0,5,0,0"
Spacing="5"
Orientation="Vertical"
VerticalAlignment="Bottom">
@@ -438,11 +507,7 @@
CornerRadius="5"
VerticalAlignment="Bottom"
HorizontalAlignment="Stretch">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid ColumnDefinitions="*,Auto">
<CheckBox
Margin="10"
MinWidth="0"
@@ -464,11 +529,7 @@
CornerRadius="5"
HorizontalAlignment="Stretch"
Margin="0,-1,0,0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid ColumnDefinitions="*,Auto">
<CheckBox
Margin="10"
MinWidth="0"
@@ -490,11 +551,7 @@
CornerRadius="5"
HorizontalAlignment="Stretch"
Margin="0,-1,0,0">
<Grid IsVisible="{Binding ParentModel.HasLed}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid IsVisible="{Binding ParentModel.HasLed}" ColumnDefinitions="*,Auto">
<CheckBox
Margin="10, 10, 5, 10"
MinWidth="0"
@@ -526,15 +583,7 @@
CornerRadius="5">
<Grid
Margin="10"
HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
HorizontalAlignment="Stretch" ColumnDefinitions="*,*" RowDefinitions="*,*">
<StackPanel
Grid.Column="1"
Grid.Row="0"

View File

@@ -4,6 +4,7 @@ using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.LogicalTree;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels.Input;
using Ryujinx.Common.Configuration.Hid.Controller;
@@ -14,7 +15,7 @@ using StickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId;
namespace Ryujinx.Ava.UI.Views.Input
{
public partial class ControllerInputView : UserControl
public partial class ControllerInputView : RyujinxControl<ControllerInputViewModel>
{
private ButtonKeyAssigner _currentAssigner;
@@ -217,20 +218,12 @@ namespace Ryujinx.Ava.UI.Views.Input
PointerPressed -= MouseClick;
}
private IButtonAssigner CreateButtonAssigner(bool forStick)
{
IButtonAssigner assigner;
ControllerInputViewModel controllerInputViewModel = DataContext as ControllerInputViewModel;
assigner = new GamepadButtonAssigner(
controllerInputViewModel.ParentModel.SelectedGamepad,
(controllerInputViewModel.ParentModel.Config as StandardControllerInputConfig).TriggerThreshold,
private IButtonAssigner CreateButtonAssigner(bool forStick) =>
new GamepadButtonAssigner(
ViewModel.ParentModel.SelectedGamepad,
(ViewModel.ParentModel.Config as StandardControllerInputConfig).TriggerThreshold,
forStick);
return assigner;
}
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(e);

View File

@@ -35,22 +35,13 @@
Margin="0 0 0 5"
Orientation="Vertical"
Spacing="5">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="10" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid ColumnDefinitions="*,10,*">
<!-- Player Selection -->
<Grid
Grid.Column="0"
Margin="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
VerticalAlignment="Center" ColumnDefinitions="Auto,*">
<TextBlock
Margin="5,0,10,0"
Width="90"
@@ -77,14 +68,7 @@
Grid.Column="2"
Margin="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
VerticalAlignment="Center" ColumnDefinitions="Auto,*,Auto,Auto,Auto">
<TextBlock
Margin="5,0,10,0"
Width="90"
@@ -139,22 +123,12 @@
</Grid>
</Grid>
<Separator />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="10" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid ColumnDefinitions="*,10,*">
<!-- Input Device -->
<Grid
Grid.Column="0"
Margin="2"
HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
HorizontalAlignment="Stretch" ColumnDefinitions="Auto,*,Auto">
<TextBlock
Grid.Column="0"
Margin="5,0,10,0"
@@ -186,11 +160,7 @@
Grid.Column="2"
Margin="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
VerticalAlignment="Center" ColumnDefinitions="Auto,*">
<TextBlock
Margin="5,0,10,0"
Width="90"

View File

@@ -1,19 +1,19 @@
using Avalonia.Controls;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.ViewModels.Input;
namespace Ryujinx.Ava.UI.Views.Input
{
public partial class InputView : UserControl
public partial class InputView : RyujinxControl<InputViewModel>
{
private bool _dialogOpen;
private InputViewModel ViewModel { get; set; }
public InputView()
{
DataContext = ViewModel = new InputViewModel(this);
ViewModel = new InputViewModel(this);
InitializeComponent();
}

View File

@@ -32,12 +32,7 @@
<!-- Button / JoyStick Settings -->
<Grid
Name="SettingButtons"
MinHeight="450">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
MinHeight="450" ColumnDefinitions="Auto,*,Auto">
<!-- Left Controls -->
<StackPanel
Orientation="Vertical"
@@ -52,15 +47,7 @@
CornerRadius="5">
<Grid
Margin="10"
HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
HorizontalAlignment="Stretch" ColumnDefinitions="*,*" RowDefinitions="*,*">
<StackPanel
Grid.Column="0"
Grid.Row="0"
@@ -309,12 +296,79 @@
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<!-- Controller Picture -->
<Image
<Border
BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="1"
CornerRadius="5"
Margin="0,10"
MaxHeight="300"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Source="{Binding Image}" />
MinHeight="90">
<StackPanel
Margin="10"
Orientation="Horizontal"
Spacing="20"
HorizontalAlignment="Center">
<Border
BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="1"
CornerRadius="5"
Height="{Binding Visualizer.UiStickBorderSize}"
Width="{Binding Visualizer.UiStickBorderSize}"
IsVisible="{Binding IsLeft}">
<Canvas
Background="{DynamicResource ThemeBackgroundColor}"
Height="{Binding Visualizer.UiCanvasSize}"
Width="{Binding Visualizer.UiCanvasSize}">
<Grid
Height="{Binding Visualizer.UiCanvasSize}"
Width="{Binding Visualizer.UiCanvasSize}"
Background="{DynamicResource ThemeBackgroundColor}">
<Ellipse
HorizontalAlignment="Center"
Stroke="Black"
StrokeThickness="1"
Width="{Binding Visualizer.UiCanvasSize}"
Height="{Binding Visualizer.UiCanvasSize}"/>
</Grid>
<Ellipse
Fill="Red"
Width="{Binding Visualizer.UiStickCircumference}"
Height="{Binding Visualizer.UiStickCircumference}"
Canvas.Bottom="{Binding Visualizer.UiStickLeftY}"
Canvas.Left="{Binding Visualizer.UiStickLeftX}" />
</Canvas>
</Border>
<Border
BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="1"
CornerRadius="5"
Height="{Binding Visualizer.UiStickBorderSize}"
Width="{Binding Visualizer.UiStickBorderSize}"
IsVisible="{Binding IsRight}">
<Canvas
Background="{DynamicResource ThemeBackgroundColor}"
Height="{Binding Visualizer.UiCanvasSize}"
Width="{Binding Visualizer.UiCanvasSize}">
<Grid
Height="{Binding Visualizer.UiCanvasSize}"
Width="{Binding Visualizer.UiCanvasSize}"
Background="{DynamicResource ThemeBackgroundColor}">
<Ellipse
HorizontalAlignment="Center"
Stroke="Black"
StrokeThickness="1"
Width="{Binding Visualizer.UiCanvasSize}"
Height="{Binding Visualizer.UiCanvasSize}"/>
</Grid>
<Ellipse
Fill="Red"
Width="{Binding Visualizer.UiStickCircumference}"
Height="{Binding Visualizer.UiStickCircumference}"
Canvas.Bottom="{Binding Visualizer.UiStickRightY}"
Canvas.Left="{Binding Visualizer.UiStickRightX}" />
</Canvas>
</Border>
</StackPanel>
</Border>
<Border
BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="1"
@@ -413,15 +467,7 @@
CornerRadius="5">
<Grid
Margin="10"
HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
HorizontalAlignment="Stretch" ColumnDefinitions="*,*" RowDefinitions="*,*">
<StackPanel
Grid.Column="1"
Grid.Row="0"

View File

@@ -4,6 +4,7 @@ using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.LogicalTree;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels.Input;
using Ryujinx.Input;
@@ -13,7 +14,7 @@ using Key = Ryujinx.Common.Configuration.Hid.Key;
namespace Ryujinx.Ava.UI.Views.Input
{
public partial class KeyboardInputView : UserControl
public partial class KeyboardInputView : RyujinxControl<KeyboardInputViewModel>
{
private ButtonKeyAssigner _currentAssigner;
@@ -60,106 +61,103 @@ namespace Ryujinx.Ava.UI.Views.Input
PointerPressed += MouseClick;
if (DataContext is not KeyboardInputViewModel viewModel)
return;
IKeyboard keyboard =
(IKeyboard)viewModel.ParentModel.AvaloniaKeyboardDriver.GetGamepad("0"); // Open Avalonia keyboard for cancel operations.
(IKeyboard)ViewModel.ParentModel.AvaloniaKeyboardDriver.GetGamepad("0"); // Open Avalonia keyboard for cancel operations.
IButtonAssigner assigner =
new KeyboardKeyAssigner((IKeyboard)viewModel.ParentModel.SelectedGamepad);
new KeyboardKeyAssigner((IKeyboard)ViewModel.ParentModel.SelectedGamepad);
_currentAssigner.ButtonAssigned += (_, e) =>
_currentAssigner.ButtonAssigned += (_, be) =>
{
if (e.ButtonValue.HasValue)
if (be.ButtonValue.HasValue)
{
Button buttonValue = e.ButtonValue.Value;
viewModel.ParentModel.IsModified = true;
Button buttonValue = be.ButtonValue.Value;
ViewModel.ParentModel.IsModified = true;
switch (button.Name)
{
case "ButtonZl":
viewModel.Config.ButtonZl = buttonValue.AsHidType<Key>();
ViewModel.Config.ButtonZl = buttonValue.AsHidType<Key>();
break;
case "ButtonL":
viewModel.Config.ButtonL = buttonValue.AsHidType<Key>();
ViewModel.Config.ButtonL = buttonValue.AsHidType<Key>();
break;
case "ButtonMinus":
viewModel.Config.ButtonMinus = buttonValue.AsHidType<Key>();
ViewModel.Config.ButtonMinus = buttonValue.AsHidType<Key>();
break;
case "LeftStickButton":
viewModel.Config.LeftStickButton = buttonValue.AsHidType<Key>();
ViewModel.Config.LeftStickButton = buttonValue.AsHidType<Key>();
break;
case "LeftStickUp":
viewModel.Config.LeftStickUp = buttonValue.AsHidType<Key>();
ViewModel.Config.LeftStickUp = buttonValue.AsHidType<Key>();
break;
case "LeftStickDown":
viewModel.Config.LeftStickDown = buttonValue.AsHidType<Key>();
ViewModel.Config.LeftStickDown = buttonValue.AsHidType<Key>();
break;
case "LeftStickRight":
viewModel.Config.LeftStickRight = buttonValue.AsHidType<Key>();
ViewModel.Config.LeftStickRight = buttonValue.AsHidType<Key>();
break;
case "LeftStickLeft":
viewModel.Config.LeftStickLeft = buttonValue.AsHidType<Key>();
ViewModel.Config.LeftStickLeft = buttonValue.AsHidType<Key>();
break;
case "DpadUp":
viewModel.Config.DpadUp = buttonValue.AsHidType<Key>();
ViewModel.Config.DpadUp = buttonValue.AsHidType<Key>();
break;
case "DpadDown":
viewModel.Config.DpadDown = buttonValue.AsHidType<Key>();
ViewModel.Config.DpadDown = buttonValue.AsHidType<Key>();
break;
case "DpadLeft":
viewModel.Config.DpadLeft = buttonValue.AsHidType<Key>();
ViewModel.Config.DpadLeft = buttonValue.AsHidType<Key>();
break;
case "DpadRight":
viewModel.Config.DpadRight = buttonValue.AsHidType<Key>();
ViewModel.Config.DpadRight = buttonValue.AsHidType<Key>();
break;
case "LeftButtonSr":
viewModel.Config.LeftButtonSr = buttonValue.AsHidType<Key>();
ViewModel.Config.LeftButtonSr = buttonValue.AsHidType<Key>();
break;
case "LeftButtonSl":
viewModel.Config.LeftButtonSl = buttonValue.AsHidType<Key>();
ViewModel.Config.LeftButtonSl = buttonValue.AsHidType<Key>();
break;
case "RightButtonSr":
viewModel.Config.RightButtonSr = buttonValue.AsHidType<Key>();
ViewModel.Config.RightButtonSr = buttonValue.AsHidType<Key>();
break;
case "RightButtonSl":
viewModel.Config.RightButtonSl = buttonValue.AsHidType<Key>();
ViewModel.Config.RightButtonSl = buttonValue.AsHidType<Key>();
break;
case "ButtonZr":
viewModel.Config.ButtonZr = buttonValue.AsHidType<Key>();
ViewModel.Config.ButtonZr = buttonValue.AsHidType<Key>();
break;
case "ButtonR":
viewModel.Config.ButtonR = buttonValue.AsHidType<Key>();
ViewModel.Config.ButtonR = buttonValue.AsHidType<Key>();
break;
case "ButtonPlus":
viewModel.Config.ButtonPlus = buttonValue.AsHidType<Key>();
ViewModel.Config.ButtonPlus = buttonValue.AsHidType<Key>();
break;
case "ButtonA":
viewModel.Config.ButtonA = buttonValue.AsHidType<Key>();
ViewModel.Config.ButtonA = buttonValue.AsHidType<Key>();
break;
case "ButtonB":
viewModel.Config.ButtonB = buttonValue.AsHidType<Key>();
ViewModel.Config.ButtonB = buttonValue.AsHidType<Key>();
break;
case "ButtonX":
viewModel.Config.ButtonX = buttonValue.AsHidType<Key>();
ViewModel.Config.ButtonX = buttonValue.AsHidType<Key>();
break;
case "ButtonY":
viewModel.Config.ButtonY = buttonValue.AsHidType<Key>();
ViewModel.Config.ButtonY = buttonValue.AsHidType<Key>();
break;
case "RightStickButton":
viewModel.Config.RightStickButton = buttonValue.AsHidType<Key>();
ViewModel.Config.RightStickButton = buttonValue.AsHidType<Key>();
break;
case "RightStickUp":
viewModel.Config.RightStickUp = buttonValue.AsHidType<Key>();
ViewModel.Config.RightStickUp = buttonValue.AsHidType<Key>();
break;
case "RightStickDown":
viewModel.Config.RightStickDown = buttonValue.AsHidType<Key>();
ViewModel.Config.RightStickDown = buttonValue.AsHidType<Key>();
break;
case "RightStickRight":
viewModel.Config.RightStickRight = buttonValue.AsHidType<Key>();
ViewModel.Config.RightStickRight = buttonValue.AsHidType<Key>();
break;
case "RightStickLeft":
viewModel.Config.RightStickLeft = buttonValue.AsHidType<Key>();
ViewModel.Config.RightStickLeft = buttonValue.AsHidType<Key>();
break;
}
}

View File

@@ -2,19 +2,18 @@
using Avalonia.Controls;
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Models.Input;
using Ryujinx.Ava.UI.ViewModels.Input;
using System.Threading.Tasks;
namespace Ryujinx.UI.Views.Input
{
public partial class LedInputView : UserControl
public partial class LedInputView : RyujinxControl<LedInputViewModel>
{
private readonly LedInputViewModel _viewModel;
public LedInputView(ControllerInputViewModel viewModel)
{
DataContext = _viewModel = new LedInputViewModel
ViewModel = new LedInputViewModel
{
ParentModel = viewModel.ParentModel,
TurnOffLed = viewModel.Config.TurnOffLed,
@@ -29,20 +28,18 @@ namespace Ryujinx.UI.Views.Input
private void ColorPickerButton_OnColorChanged(ColorPickerButton sender, ColorButtonColorChangedEventArgs args)
{
if (!args.NewColor.HasValue) return;
if (DataContext is not LedInputViewModel lvm) return;
if (!lvm.EnableLedChanging) return;
if (lvm.TurnOffLed) return;
if (!ViewModel.EnableLedChanging) return;
if (ViewModel.TurnOffLed) return;
lvm.ParentModel.SelectedGamepad.SetLed(args.NewColor.Value.ToUInt32());
ViewModel.ParentModel.SelectedGamepad.SetLed(args.NewColor.Value.ToUInt32());
}
private void ColorPickerButton_OnAttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e)
{
if (DataContext is not LedInputViewModel lvm) return;
if (!lvm.EnableLedChanging) return;
if (lvm.TurnOffLed) return;
if (!ViewModel.EnableLedChanging) return;
if (ViewModel.TurnOffLed) return;
lvm.ParentModel.SelectedGamepad.SetLed(lvm.LedColor.ToUInt32());
ViewModel.ParentModel.SelectedGamepad.SetLed(ViewModel.LedColor.ToUInt32());
}
public static async Task Show(ControllerInputViewModel viewModel)
@@ -57,13 +54,13 @@ namespace Ryujinx.UI.Views.Input
CloseButtonText = LocaleManager.Instance[LocaleKeys.ControllerSettingsClose],
Content = content,
};
contentDialog.PrimaryButtonClick += (sender, args) =>
contentDialog.PrimaryButtonClick += (_, _) =>
{
GamepadInputConfig config = viewModel.Config;
config.EnableLedChanging = content._viewModel.EnableLedChanging;
config.LedColor = content._viewModel.LedColor;
config.UseRainbowLed = content._viewModel.UseRainbowLed;
config.TurnOffLed = content._viewModel.TurnOffLed;
config.EnableLedChanging = content.ViewModel.EnableLedChanging;
config.LedColor = content.ViewModel.LedColor;
config.UseRainbowLed = content.ViewModel.UseRainbowLed;
config.TurnOffLed = content.ViewModel.TurnOffLed;
};
await contentDialog.ShowAsync();

View File

@@ -11,11 +11,7 @@
x:Class="Ryujinx.Ava.UI.Views.Input.MotionInputView"
x:DataType="viewModels:MotionInputViewModel"
Focusable="True">
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Grid Margin="10" RowDefinitions="Auto,*">
<StackPanel Orientation="Vertical">
<StackPanel
Orientation="Horizontal"
@@ -80,11 +76,7 @@
BorderThickness="1"
CornerRadius="5"
HorizontalAlignment="Stretch">
<Grid VerticalAlignment="Top">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid VerticalAlignment="Top" RowDefinitions="Auto,*">
<StackPanel
Grid.Row="1"
HorizontalAlignment="Center"
@@ -118,15 +110,7 @@
Text="{Binding DsuServerPort, Mode=TwoWay}" />
</StackPanel>
<StackPanel Orientation="Vertical">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid RowDefinitions="*,*" ColumnDefinitions="*,*">
<TextBlock
Margin="0,10,0,0"
VerticalAlignment="Center"

View File

@@ -1,16 +1,15 @@
using Avalonia.Controls;
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Models.Input;
using Ryujinx.Ava.UI.ViewModels.Input;
using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.Views.Input
{
public partial class MotionInputView : UserControl
public partial class MotionInputView : RyujinxControl<MotionInputViewModel>
{
private readonly MotionInputViewModel _viewModel;
public MotionInputView()
{
InitializeComponent();
@@ -20,7 +19,7 @@ namespace Ryujinx.Ava.UI.Views.Input
{
GamepadInputConfig config = viewModel.Config;
_viewModel = new MotionInputViewModel
ViewModel = new MotionInputViewModel
{
Slot = config.Slot,
AltSlot = config.AltSlot,
@@ -33,7 +32,6 @@ namespace Ryujinx.Ava.UI.Views.Input
};
InitializeComponent();
DataContext = _viewModel;
}
public static async Task Show(ControllerInputViewModel viewModel)
@@ -48,17 +46,17 @@ namespace Ryujinx.Ava.UI.Views.Input
CloseButtonText = LocaleManager.Instance[LocaleKeys.ControllerSettingsClose],
Content = content,
};
contentDialog.PrimaryButtonClick += (sender, args) =>
contentDialog.PrimaryButtonClick += (_, _) =>
{
GamepadInputConfig config = viewModel.Config;
config.Slot = content._viewModel.Slot;
config.Sensitivity = content._viewModel.Sensitivity;
config.GyroDeadzone = content._viewModel.GyroDeadzone;
config.AltSlot = content._viewModel.AltSlot;
config.DsuServerHost = content._viewModel.DsuServerHost;
config.DsuServerPort = content._viewModel.DsuServerPort;
config.EnableCemuHookMotion = content._viewModel.EnableCemuHookMotion;
config.MirrorInput = content._viewModel.MirrorInput;
config.Slot = content.ViewModel.Slot;
config.Sensitivity = content.ViewModel.Sensitivity;
config.GyroDeadzone = content.ViewModel.GyroDeadzone;
config.AltSlot = content.ViewModel.AltSlot;
config.DsuServerHost = content.ViewModel.DsuServerHost;
config.DsuServerPort = content.ViewModel.DsuServerPort;
config.EnableCemuHookMotion = content.ViewModel.EnableCemuHookMotion;
config.MirrorInput = content.ViewModel.MirrorInput;
};
await contentDialog.ShowAsync();

View File

@@ -10,11 +10,7 @@
x:Class="Ryujinx.Ava.UI.Views.Input.RumbleInputView"
x:DataType="viewModels:RumbleInputViewModel"
Focusable="True">
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Grid Margin="10" RowDefinitions="Auto,*">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBlock

View File

@@ -1,16 +1,15 @@
using Avalonia.Controls;
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Models.Input;
using Ryujinx.Ava.UI.ViewModels.Input;
using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.Views.Input
{
public partial class RumbleInputView : UserControl
public partial class RumbleInputView : RyujinxControl<RumbleInputViewModel>
{
private readonly RumbleInputViewModel _viewModel;
public RumbleInputView()
{
InitializeComponent();
@@ -20,15 +19,13 @@ namespace Ryujinx.Ava.UI.Views.Input
{
GamepadInputConfig config = viewModel.Config;
_viewModel = new RumbleInputViewModel
ViewModel = new RumbleInputViewModel
{
StrongRumble = config.StrongRumble,
WeakRumble = config.WeakRumble,
};
InitializeComponent();
DataContext = _viewModel;
}
public static async Task Show(ControllerInputViewModel viewModel)
@@ -44,11 +41,11 @@ namespace Ryujinx.Ava.UI.Views.Input
Content = content,
};
contentDialog.PrimaryButtonClick += (sender, args) =>
contentDialog.PrimaryButtonClick += (_, _) =>
{
GamepadInputConfig config = viewModel.Config;
config.StrongRumble = content._viewModel.StrongRumble;
config.WeakRumble = content._viewModel.WeakRumble;
config.StrongRumble = content.ViewModel.StrongRumble;
config.WeakRumble = content.ViewModel.WeakRumble;
};
await contentDialog.ShowAsync();

View File

@@ -6,6 +6,7 @@ using Gommon;
using LibHac.Common;
using LibHac.Ns;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.UI.Windows;
@@ -19,15 +20,15 @@ using Ryujinx.Common.Utilities;
using Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.Views.Main
{
public partial class MainMenuBarView : UserControl
public partial class MainMenuBarView : RyujinxControl<MainWindowViewModel>
{
public MainWindow Window { get; private set; }
public MainWindowViewModel ViewModel { get; private set; }
public MainMenuBarView()
{
@@ -131,7 +132,24 @@ namespace Ryujinx.Ava.UI.Views.Main
Rainbow.Enable();
await Window.SettingsWindow.ShowDialog(Window);
if (ViewModel.SelectedApplication is null) // Checks if game data exists
{
await StyleableAppWindow.ShowAsync(Window.SettingsWindow);
}
else
{
bool customConfigExists = File.Exists(Program.GetDirGameUserConfig(ViewModel.SelectedApplication.IdString));
if (!ViewModel.IsGameRunning || !customConfigExists)
{
await Window.SettingsWindow.ShowDialog(Window); // The game is not running, or if the user configuration does not exist
}
else
{
// If there is a custom configuration in the folder
await StyleableAppWindow.ShowAsync(new GameSpecificSettingsWindow(ViewModel, customConfigExists));
}
}
Rainbow.Disable();
Rainbow.Reset();
@@ -158,11 +176,13 @@ namespace Ryujinx.Ava.UI.Views.Main
string name = ViewModel.AppHost.Device.Processes.ActiveApplication.ApplicationControlProperties.Title[(int)ViewModel.AppHost.Device.System.State.DesiredTitleLanguage].NameString.ToString();
await new CheatWindow(
await StyleableAppWindow.ShowAsync(
new CheatWindow(
Window.VirtualFileSystem,
ViewModel.AppHost.Device.Processes.ActiveApplication.ProgramIdText,
name,
ViewModel.SelectedApplication.Path).ShowDialog(Window);
ViewModel.SelectedApplication.Path)
);
ViewModel.AppHost.Device.EnableCheats();
}

View File

@@ -4,6 +4,8 @@ using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Threading;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.Ava.Utilities.Configuration;
using Ryujinx.Common;
@@ -13,7 +15,7 @@ using System;
namespace Ryujinx.Ava.UI.Views.Main
{
public partial class MainStatusBarView : UserControl
public partial class MainStatusBarView : RyujinxControl<MainWindowViewModel>
{
public MainWindow Window;
@@ -29,7 +31,7 @@ namespace Ryujinx.Ava.UI.Views.Main
if (VisualRoot is MainWindow window)
{
Window = window;
DataContext = window.ViewModel;
ViewModel = window.ViewModel;
LocaleManager.Instance.LocaleChanged += () => Dispatcher.UIThread.Post(() =>
{
if (Window.ViewModel.EnableNonGameRunningControls)

View File

@@ -3,16 +3,15 @@ using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Ryujinx.Ava.Common;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.UI.Windows;
using System;
namespace Ryujinx.Ava.UI.Views.Main
{
public partial class MainViewControls : UserControl
public partial class MainViewControls : RyujinxControl<MainWindowViewModel>
{
public MainWindowViewModel ViewModel;
public MainViewControls()
{
InitializeComponent();
@@ -24,7 +23,7 @@ namespace Ryujinx.Ava.UI.Views.Main
if (VisualRoot is MainWindow window)
{
DataContext = ViewModel = window.ViewModel;
ViewModel = window.ViewModel;
}
}

View File

@@ -21,12 +21,7 @@
<Border Classes="settings">
<Panel
Margin="10">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid RowDefinitions="Auto,*,Auto">
<views:InputView
Grid.Row="0"
Name="InputView" />

View File

@@ -156,6 +156,8 @@
ValueMemberBinding="{Binding Mode=OneWay, Converter={x:Static helpers:TimeZoneConverter.Instance}}" />
</StackPanel>
<StackPanel
IsEnabled="{Binding !IsGameTitleNotNull}"
Opacity="{Binding PanelOpacity}"
Margin="0,0,0,10"
Orientation="Horizontal">
<TextBlock
@@ -169,8 +171,11 @@
SelectedDate="{Binding CurrentDate}"
ToolTip.Tip="{ext:Locale TimeTooltip}"
Width="350" />
<TextBlock Classes="globalConfigMarker" IsVisible="{Binding IsGameTitleNotNull}"/>
</StackPanel>
<StackPanel
IsEnabled="{Binding !IsGameTitleNotNull}"
Opacity="{Binding PanelOpacity}"
Margin="250,0,0,10"
Orientation="Horizontal">
<TimePicker
@@ -180,8 +185,12 @@
SelectedTime="{Binding CurrentTime}"
Width="350"
ToolTip.Tip="{ext:Locale TimeTooltip}" />
<TextBlock Classes="globalConfigMarker" IsVisible="{Binding IsGameTitleNotNull}"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<StackPanel
IsEnabled="{Binding !IsGameTitleNotNull}"
Opacity="{Binding PanelOpacity}"
Orientation="Horizontal">
<TextBlock
VerticalAlignment="Center"
Text="{ext:Locale SettingsTabSystemSystemTimeMatch}"
@@ -191,6 +200,7 @@
VerticalAlignment="Center"
IsChecked="{Binding MatchSystemTime}"
ToolTip.Tip="{ext:Locale MatchTimeTooltip}"/>
<TextBlock Classes="globalConfigMarker" IsVisible="{Binding IsGameTitleNotNull}"/>
</StackPanel>
<Separator />
<StackPanel Margin="0,10,0,10"

View File

@@ -27,20 +27,42 @@
<TextBlock Classes="h1" Text="{ext:Locale SettingsTabGeneralGeneral}" />
<StackPanel Margin="10,0,0,0" Orientation="Vertical">
<CheckBox IsChecked="{Binding EnableDiscordIntegration}">
<StackPanel Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
ToolTip.Tip="{ext:Locale ToggleDiscordTooltip}"
Text="{ext:Locale SettingsTabGeneralEnableDiscordRichPresence}" />
</StackPanel>
</CheckBox>
<CheckBox IsChecked="{Binding ShowConfirmExit}">
<CheckBox
IsEnabled="{Binding !IsGameTitleNotNull}"
Opacity="{Binding PanelOpacity}"
IsChecked="{Binding ShowConfirmExit}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{ext:Locale SettingsTabGeneralShowConfirmExitDialog}" />
<TextBlock Classes="globalConfigMarker" IsVisible="{Binding IsGameTitleNotNull}" />
</StackPanel>
</CheckBox>
<CheckBox IsChecked="{Binding RememberWindowState}">
<CheckBox
IsEnabled="{Binding !IsGameTitleNotNull}"
Opacity="{Binding PanelOpacity}"
IsChecked="{Binding RememberWindowState}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{ext:Locale SettingsTabGeneralRememberWindowState}" />
<TextBlock Classes="globalConfigMarker" IsVisible="{Binding IsGameTitleNotNull}" />
</StackPanel>
</CheckBox>
<CheckBox IsChecked="{Binding ShowTitleBar}" IsVisible="{x:Static helper:RunningPlatform.IsWindows}">
<CheckBox
IsEnabled="{Binding !IsGameTitleNotNull}"
Opacity="{Binding PanelOpacity}"
IsChecked="{Binding ShowTitleBar}" IsVisible="{x:Static helper:RunningPlatform.IsWindows}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{ext:Locale SettingsTabGeneralShowTitleBar}" />
<TextBlock Classes="globalConfigMarker" IsVisible="{Binding IsGameTitleNotNull}" />
</StackPanel>
</CheckBox>
<StackPanel Margin="0, 15, 0, 0" Orientation="Horizontal">
<StackPanel
Margin="0, 15, 0, 0"
Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
Text="{ext:Locale SettingsTabGeneralFocusLossType}"
Width="150" />
@@ -64,7 +86,11 @@
</ComboBoxItem>
</ComboBox>
</StackPanel>
<StackPanel Margin="0, 15, 0, 0" Orientation="Horizontal">
<StackPanel
IsEnabled="{Binding !IsGameTitleNotNull}"
Opacity="{Binding PanelOpacity}"
Margin="0, 15, 0, 0"
Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
Text="{ext:Locale SettingsTabGeneralCheckUpdatesOnLaunch}"
Width="150" />
@@ -81,8 +107,11 @@
<TextBlock Text="{ext:Locale SettingsTabGeneralCheckUpdatesOnLaunchBackground}" />
</ComboBoxItem>
</ComboBox>
<TextBlock Classes="globalConfigMarker" IsVisible="{Binding IsGameTitleNotNull}"/>
</StackPanel>
<StackPanel Margin="0, 15, 0, 0" Orientation="Horizontal">
<StackPanel
Margin="0, 15, 0, 0"
Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
Text="{ext:Locale SettingsTabGeneralHideCursor}"
Width="150" />
@@ -100,7 +129,11 @@
</ComboBoxItem>
</ComboBox>
</StackPanel>
<StackPanel Margin="0, 15, 0, 10" Orientation="Horizontal">
<StackPanel
IsEnabled="{Binding !IsGameTitleNotNull}"
Opacity="{Binding PanelOpacity}"
Margin="0, 15, 0, 10"
Orientation="Horizontal">
<TextBlock
VerticalAlignment="Center"
Text="{ext:Locale SettingsTabGeneralTheme}"
@@ -118,11 +151,17 @@
<TextBlock Text="{ext:Locale SettingsTabGeneralThemeDark}" />
</ComboBoxItem>
</ComboBox>
<TextBlock Classes="globalConfigMarker" IsVisible="{Binding IsGameTitleNotNull}"/>
</StackPanel>
</StackPanel>
<Separator Height="1" />
<StackPanel Orientation="Horizontal">
<TextBlock Classes="h1" Text="{ext:Locale SettingsTabGeneralGameDirectories}" />
<TextBlock Classes="globalConfigMarker" IsVisible="{Binding IsGameTitleNotNull}"/>
</StackPanel>
<StackPanel
IsEnabled="{Binding !IsGameTitleNotNull}"
Opacity="{Binding PanelOpacity}"
Margin="10,0,0,0"
HorizontalAlignment="Stretch"
Orientation="Vertical"
@@ -138,12 +177,7 @@
</Style>
</ListBox.Styles>
</ListBox>
<Grid HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid HorizontalAlignment="Stretch" ColumnDefinitions="*,Auto,Auto">
<TextBox
Name="GameDirPathBox"
Margin="0"
@@ -172,10 +206,15 @@
</StackPanel>
<Separator Height="1" />
<StackPanel Orientation="Vertical" Spacing="5">
<StackPanel Orientation="Horizontal">
<TextBlock Classes="h1" Text="{ext:Locale SettingsTabGeneralAutoloadDirectories}" />
<TextBlock Classes="globalConfigMarker" IsVisible="{Binding IsGameTitleNotNull}"/>
</StackPanel>
<TextBlock Foreground="{DynamicResource SecondaryTextColor}" Text="{ext:Locale SettingsTabGeneralAutoloadNote}" />
</StackPanel>
<StackPanel
IsEnabled="{Binding !IsGameTitleNotNull}"
Opacity="{Binding PanelOpacity}"
Margin="10,0,0,0"
HorizontalAlignment="Stretch"
Orientation="Vertical"
@@ -191,12 +230,7 @@
</Style>
</ListBox.Styles>
</ListBox>
<Grid HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid HorizontalAlignment="Stretch" ColumnDefinitions="*,Auto,Auto">
<TextBox
Name="AutoloadDirPathBox"
Margin="0"

View File

@@ -14,15 +14,7 @@
mc:Ignorable="d"
Focusable="True"
x:DataType="models:TempProfile">
<Grid Margin="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Margin="0" ColumnDefinitions="Auto,*" RowDefinitions="*,Auto">
<StackPanel
Grid.Row="0"
Grid.Column="0"

View File

@@ -13,13 +13,12 @@ using UserProfile = Ryujinx.Ava.UI.Models.UserProfile;
namespace Ryujinx.Ava.UI.Views.User
{
public partial class UserEditorView : UserControl
public partial class UserEditorView : RyujinxControl<TempProfile>
{
private NavigationDialogHost _parent;
private UserProfile _profile;
private bool _isNewUser;
public TempProfile TempProfile { get; set; }
public static uint MaxProfileNameLength => 0x20;
public bool IsDeletable => _profile.UserId != AccountManager.DefaultUserId;
@@ -42,7 +41,7 @@ namespace Ryujinx.Ava.UI.Views.User
(NavigationDialogHost parent, UserProfile profile, bool isNewUser) = ((NavigationDialogHost parent, UserProfile profile, bool isNewUser))arg.Parameter;
_isNewUser = isNewUser;
_profile = profile;
TempProfile = new TempProfile(_profile);
ViewModel = new TempProfile(_profile);
_parent = parent;
break;
@@ -51,8 +50,6 @@ namespace Ryujinx.Ava.UI.Views.User
((ContentDialog)_parent.Parent).Title = $"{LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle]} - " +
$"{(_isNewUser ? LocaleManager.Instance[LocaleKeys.UserEditorTitleCreate] : LocaleManager.Instance[LocaleKeys.UserEditorTitle])}";
DataContext = TempProfile;
AddPictureButton.IsVisible = _isNewUser;
ChangePictureButton.IsVisible = !_isNewUser;
IdLabel.IsVisible = _profile != null;
@@ -72,7 +69,7 @@ namespace Ryujinx.Ava.UI.Views.User
{
if (_isNewUser)
{
if (TempProfile.Name != String.Empty || TempProfile.Image != null)
if (ViewModel.Name != string.Empty || ViewModel.Image != null)
{
if (await ContentDialogHelper.CreateChoiceDialog(
LocaleManager.Instance[LocaleKeys.DialogUserProfileUnsavedChangesTitle],
@@ -89,7 +86,7 @@ namespace Ryujinx.Ava.UI.Views.User
}
else
{
if (_profile.Name != TempProfile.Name || _profile.Image != TempProfile.Image)
if (_profile.Name != ViewModel.Name || _profile.Image != ViewModel.Image)
{
if (await ContentDialogHelper.CreateChoiceDialog(
LocaleManager.Instance[LocaleKeys.DialogUserProfileUnsavedChangesTitle],
@@ -115,31 +112,31 @@ namespace Ryujinx.Ava.UI.Views.User
{
DataValidationErrors.ClearErrors(NameBox);
if (string.IsNullOrWhiteSpace(TempProfile.Name))
if (string.IsNullOrWhiteSpace(ViewModel.Name))
{
DataValidationErrors.SetError(NameBox, new DataValidationException(LocaleManager.Instance[LocaleKeys.UserProfileEmptyNameError]));
return;
}
if (TempProfile.Image == null)
if (ViewModel.Image == null)
{
_parent.Navigate(typeof(UserProfileImageSelectorView), (_parent, TempProfile));
_parent.Navigate(typeof(UserProfileImageSelectorView), (_parent, ViewModel));
return;
}
if (_profile != null && !_isNewUser)
{
_profile.Name = TempProfile.Name;
_profile.Image = TempProfile.Image;
_profile.Name = ViewModel.Name;
_profile.Image = ViewModel.Image;
_profile.UpdateState();
_parent.AccountManager.SetUserName(_profile.UserId, _profile.Name);
_parent.AccountManager.SetUserImage(_profile.UserId, _profile.Image);
}
else if (_isNewUser)
{
_parent.AccountManager.AddUser(TempProfile.Name, TempProfile.Image, TempProfile.UserId);
_parent.AccountManager.AddUser(ViewModel.Name, ViewModel.Image, ViewModel.UserId);
}
else
{
@@ -151,7 +148,7 @@ namespace Ryujinx.Ava.UI.Views.User
public void SelectProfileImage()
{
_parent.Navigate(typeof(UserProfileImageSelectorView), (_parent, TempProfile));
_parent.Navigate(typeof(UserProfileImageSelectorView), (_parent, ViewModel));
}
private void ChangePictureButton_Click(object sender, RoutedEventArgs e)

View File

@@ -20,13 +20,7 @@
<Grid
Margin="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
VerticalAlignment="Stretch" RowDefinitions="Auto,*,Auto,Auto">
<ListBox
Grid.Row="1"
BorderThickness="0"

View File

@@ -1,4 +1,3 @@
using Avalonia.Controls;
using Avalonia.Interactivity;
using FluentAvalonia.UI.Controls;
using FluentAvalonia.UI.Navigation;
@@ -11,7 +10,7 @@ using System.IO;
namespace Ryujinx.Ava.UI.Views.User
{
public partial class UserFirmwareAvatarSelectorView : UserControl
public partial class UserFirmwareAvatarSelectorView : RyujinxControl<UserFirmwareAvatarSelectorViewModel>
{
private NavigationDialogHost _parent;
private TempProfile _profile;
@@ -20,8 +19,6 @@ namespace Ryujinx.Ava.UI.Views.User
{
ContentManager = contentManager;
DataContext = ViewModel;
InitializeComponent();
}
@@ -55,8 +52,6 @@ namespace Ryujinx.Ava.UI.Views.User
public ContentManager ContentManager { get; private set; }
internal UserFirmwareAvatarSelectorViewModel ViewModel { get; set; }
private void GoBack(object sender, RoutedEventArgs e)
{
_parent.GoBack();

View File

@@ -17,12 +17,7 @@
</Design.DataContext>
<Grid
HorizontalAlignment="Stretch"
VerticalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="70" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
VerticalAlignment="Center" RowDefinitions="Auto,70,Auto">
<TextBlock
Grid.Row="0"
TextWrapping="Wrap"

View File

@@ -15,14 +15,12 @@ using System.IO;
namespace Ryujinx.Ava.UI.Views.User
{
public partial class UserProfileImageSelectorView : UserControl
public partial class UserProfileImageSelectorView : RyujinxControl<UserProfileImageSelectorViewModel>
{
private ContentManager _contentManager;
private NavigationDialogHost _parent;
private TempProfile _profile;
internal UserProfileImageSelectorViewModel ViewModel { get; private set; }
public UserProfileImageSelectorView()
{
InitializeComponent();

View File

@@ -18,11 +18,7 @@
<viewModels:UserProfileViewModel />
</Design.DataContext>
<Grid HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
VerticalAlignment="Stretch" RowDefinitions="*,Auto">
<Border
CornerRadius="5"
BorderBrush="{DynamicResource AppListHoverBackgroundColor}"
@@ -41,11 +37,7 @@
VerticalAlignment="Stretch"
ClipToBounds="True"
CornerRadius="5">
<Grid Margin="0">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid Margin="0" ColumnDefinitions="*,Auto">
<TextBlock
HorizontalAlignment="Stretch"
Text="{Binding UserId}"

View File

@@ -4,10 +4,11 @@ using FluentAvalonia.UI.Controls;
using FluentAvalonia.UI.Navigation;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.ViewModels;
namespace Ryujinx.Ava.UI.Views.User
{
public partial class UserRecovererView : UserControl
public partial class UserRecovererView : RyujinxControl<UserProfileViewModel>
{
private NavigationDialogHost _parent;

View File

@@ -19,19 +19,10 @@
<Design.DataContext>
<viewModels:UserSaveManagerViewModel />
</Design.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid RowDefinitions="Auto,*,Auto">
<Grid
Grid.Row="0"
HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
HorizontalAlignment="Stretch" ColumnDefinitions="Auto,*">
<StackPanel
Spacing="10"
Orientation="Horizontal"
@@ -80,11 +71,7 @@
<Grid
Grid.Column="1"
HorizontalAlignment="Stretch"
Margin="10,0, 0, 0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
Margin="10,0, 0, 0" ColumnDefinitions="Auto,*">
<Label Content="{ext:Locale Search}" VerticalAlignment="Center" />
<TextBox
Margin="5,0,0,0"
@@ -118,11 +105,7 @@
</ListBox.Styles>
<ListBox.ItemTemplate>
<DataTemplate x:DataType="models:SaveModel">
<Grid HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid HorizontalAlignment="Stretch" ColumnDefinitions="*,Auto">
<StackPanel
Grid.Column="0"
Orientation="Horizontal"

View File

@@ -23,10 +23,8 @@ using UserId = LibHac.Fs.UserId;
namespace Ryujinx.Ava.UI.Views.User
{
public partial class UserSaveManagerView : UserControl
public partial class UserSaveManagerView : RyujinxControl<UserSaveManagerViewModel>
{
internal UserSaveManagerViewModel ViewModel { get; private set; }
private AccountManager _accountManager;
private HorizonClient _horizonClient;
private VirtualFileSystem _virtualFileSystem;
@@ -66,7 +64,7 @@ namespace Ryujinx.Ava.UI.Views.User
public void LoadSaves()
{
ViewModel.Saves.Clear();
Dispatcher.UIThread.Post(() => ViewModel.Saves.Clear());
ObservableCollection<SaveModel> saves = [];
SaveDataFilter saveDataFilter = SaveDataFilter.Make(
programId: default,

View File

@@ -18,11 +18,7 @@
<Design.DataContext>
<viewModels:UserProfileViewModel />
</Design.DataContext>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" RowDefinitions="*,Auto">
<Border
CornerRadius="5"
BorderBrush="{DynamicResource AppListHoverBackgroundColor}"

View File

@@ -11,12 +11,10 @@ using Button = Avalonia.Controls.Button;
namespace Ryujinx.Ava.UI.Views.User
{
public partial class UserSelectorViews : UserControl
public partial class UserSelectorViews : RyujinxControl<UserProfileViewModel>
{
private NavigationDialogHost _parent;
public UserProfileViewModel ViewModel { get; set; }
public UserSelectorViews()
{
InitializeComponent();

View File

@@ -0,0 +1,149 @@
<window:StyleableAppWindow
x:Class="Ryujinx.Ava.UI.Windows.GameSpecificSettingsWindow"
xmlns="https://github.com/avaloniaui"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:window="clr-namespace:Ryujinx.Ava.UI.Windows"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
xmlns:settings="clr-namespace:Ryujinx.Ava.UI.Views.Settings"
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
xmlns:helper="clr-namespace:Ryujinx.Common.Helper;assembly=Ryujinx.Common"
Width="1100"
Height="910"
MinWidth="800"
MinHeight="480"
WindowStartupLocation="CenterOwner"
x:DataType="viewModels:SettingsViewModel"
mc:Ignorable="d"
Focusable="True">
<Design.DataContext>
<viewModels:SettingsViewModel />
</Design.DataContext>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" MinWidth="600" RowDefinitions="Auto,*,Auto">
<ContentPresenter
x:Name="ContentPresenter"
Grid.Row="1"
IsVisible="False"
KeyboardNavigation.IsTabStop="False"/>
<Grid Name="Pages" IsVisible="False" Grid.Row="2">
<settings:SettingsUiView Name="UiPage" />
<settings:SettingsInputView Name="InputPage" />
<settings:SettingsSystemView Name="SystemPage" />
<settings:SettingsCPUView Name="CpuPage" />
<settings:SettingsGraphicsView Name="GraphicsPage" />
<settings:SettingsAudioView Name="AudioPage" />
<settings:SettingsNetworkView Name="NetworkPage" />
<settings:SettingsLoggingView Name="LoggingPage" />
<settings:SettingsHacksView Name="HacksPage" />
</Grid>
<ui:NavigationView
Grid.Row="1"
IsSettingsVisible="False"
Name="NavPanel"
IsBackEnabled="False"
PaneDisplayMode="Left"
Margin="2,10,10,0"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
OpenPaneLength="200"
IsPaneToggleButtonVisible="False">
<!-- For image -->
<ui:NavigationView.PaneHeader>
<Grid HorizontalAlignment="Center" VerticalAlignment="Center" RowDefinitions="Auto,Auto,Auto,5">
<TextBlock Text="{Binding GameId}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Margin="0,0,0,10"
TextAlignment="Center" Grid.Row="0" />
<Image Source="{Binding GameIcon}"
Width="160"
Height="160"
Grid.Row="1"
Margin="0,0,0,10"
HorizontalAlignment="Center" />
<TextBlock Text="{Binding GameTitle}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Margin="0,0,0,10"
TextAlignment="Center" Grid.Row="2" />
<Separator Height="1" Grid.Row="3" Margin="0,0,0,10" HorizontalAlignment="Stretch"/>
</Grid>
</ui:NavigationView.PaneHeader>
<ui:NavigationView.MenuItems>
<ui:NavigationViewItem
IsSelected="True"
Content="{ext:Locale SettingsTabGeneral}"
Tag="UiPage"
IconSource="New" />
<ui:NavigationViewItem
Content="{ext:Locale SettingsTabInput}"
Tag="InputPage"
IconSource="Games" />
<ui:NavigationViewItem
Content="{ext:Locale SettingsTabSystem}"
Tag="SystemPage"
IconSource="Settings" />
<ui:NavigationViewItem
Content="{ext:Locale SettingsTabCpu}"
Tag="CpuPage">
<ui:NavigationViewItem.IconSource>
<ui:FontIconSource
FontFamily="avares://Ryujinx/Assets/Fonts#Segoe Fluent Icons"
Glyph="{helpers:GlyphValueConverter Chip}" />
</ui:NavigationViewItem.IconSource>
</ui:NavigationViewItem>
<ui:NavigationViewItem
Content="{ext:Locale SettingsTabGraphics}"
Tag="GraphicsPage"
IconSource="Image" />
<ui:NavigationViewItem
Content="{ext:Locale SettingsTabAudio}"
IconSource="Audio"
Tag="AudioPage" />
<ui:NavigationViewItem
Content="{ext:Locale SettingsTabNetwork}"
Tag="NetworkPage"
IconSource="Globe" />
<ui:NavigationViewItem
Content="{ext:Locale SettingsTabLogging}"
Tag="LoggingPage"
IconSource="Document" />
<ui:NavigationViewItem
IsVisible="{Binding ShowDirtyHacks}"
Content="Dirty Hacks"
Tag="HacksPage"
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>
</window:StyleableAppWindow>

View File

@@ -0,0 +1,115 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Shapes;
using Avalonia.Input;
using Avalonia.Media.Imaging;
using FluentAvalonia.Core;
using FluentAvalonia.UI.Controls;
using Projektanker.Icons.Avalonia;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.UI.ViewModels.Input;
using Ryujinx.Ava.Utilities;
using Ryujinx.Ava.Utilities.Configuration;
using Ryujinx.Common.Configuration;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS.SystemState;
using Ryujinx.Input;
using System;
using System.IO;
using System.Linq;
using Key = Avalonia.Input.Key;
namespace Ryujinx.Ava.UI.Windows
{
public partial class GameSpecificSettingsWindow : StyleableAppWindow
{
internal readonly SettingsViewModel ViewModel;
public GameSpecificSettingsWindow(MainWindowViewModel viewModel, bool findUserConfigDir = true)
{
Title = string.Format(LocaleManager.Instance[LocaleKeys.SettingsWithInfo], viewModel.SelectedApplication.Name, viewModel.SelectedApplication.IdString);
DataContext = ViewModel = new SettingsViewModel(
viewModel.VirtualFileSystem,
viewModel.ContentManager,
viewModel.IsGameRunning,
viewModel.SelectedApplication.Path,
viewModel.SelectedApplication.Name,
viewModel.SelectedApplication.IdString,
viewModel.SelectedApplication.Icon,
findUserConfigDir);
ViewModel.CloseWindow += Close;
ViewModel.SaveSettingsEvent += SaveSettings;
InitializeComponent();
Load();
}
public void SaveSettings()
{
InputPage.InputView?.SaveCurrentProfile();
}
private void Load()
{
Pages.Children.Clear();
NavPanel.SelectionChanged += NavPanelOnSelectionChanged;
NavPanel.SelectedItem = NavPanel.MenuItems.ElementAt(0);
}
private void NavPanelOnSelectionChanged(object sender, NavigationViewSelectionChangedEventArgs e)
{
if (e.SelectedItem is NavigationViewItem navItem && navItem.Tag is not null)
{
switch (navItem.Tag.ToString())
{
case nameof(UiPage):
UiPage.ViewModel = ViewModel;
NavPanel.Content = UiPage;
break;
case nameof(InputPage):
NavPanel.Content = InputPage;
break;
case nameof(SystemPage):
SystemPage.ViewModel = ViewModel;
NavPanel.Content = SystemPage;
break;
case nameof(CpuPage):
NavPanel.Content = CpuPage;
break;
case nameof(GraphicsPage):
NavPanel.Content = GraphicsPage;
break;
case nameof(AudioPage):
NavPanel.Content = AudioPage;
break;
case nameof(NetworkPage):
NetworkPage.ViewModel = ViewModel;
NavPanel.Content = NetworkPage;
break;
case nameof(LoggingPage):
NavPanel.Content = LoggingPage;
break;
case nameof(HacksPage):
HacksPage.DataContext = ViewModel;
NavPanel.Content = HacksPage;
break;
default:
throw new NotImplementedException();
}
}
}
protected override void OnClosing(WindowClosingEventArgs e)
{
InputPage.Dispose(); // You need to unload the gamepad settings, otherwise the controls will be blocked
base.OnClosing(e);
}
}
}

View File

@@ -385,6 +385,7 @@ namespace Ryujinx.Ava.UI.Windows
if (applicationData != null)
{
ViewModel.SelectedApplication = applicationData;
await ViewModel.LoadApplication(applicationData, _startFullscreen);
}
else
@@ -396,6 +397,7 @@ namespace Ryujinx.Ava.UI.Windows
else
{
applicationData = applications[0];
ViewModel.SelectedApplication = applicationData;
await ViewModel.LoadApplication(applicationData, _startFullscreen);
}
}

View File

@@ -70,11 +70,7 @@
<DataTemplate
DataType="models:ModModel">
<Panel Margin="10">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid ColumnDefinitions="*,Auto">
<TextBlock
HorizontalAlignment="Left"
VerticalAlignment="Center"

View File

@@ -12,7 +12,7 @@
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
xmlns:helper="clr-namespace:Ryujinx.Common.Helper;assembly=Ryujinx.Common"
Width="1100"
Height="768"
Height="927"
MinWidth="800"
MinHeight="480"
WindowStartupLocation="CenterOwner"

View File

@@ -1,6 +1,4 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.ViewModels;
@@ -8,7 +6,6 @@ using Ryujinx.HLE.FileSystem;
using Ryujinx.Input;
using System;
using System.Linq;
using Key = Avalonia.Input.Key;
namespace Ryujinx.Ava.UI.Windows
{
@@ -27,10 +24,6 @@ namespace Ryujinx.Ava.UI.Windows
InitializeComponent();
Load();
#if DEBUG
this.AttachDevTools(new KeyGesture(Key.F12, KeyModifiers.Alt));
#endif
}
public SettingsWindow()

View File

@@ -1,15 +1,26 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Media;
using Avalonia.Platform;
using FluentAvalonia.UI.Windowing;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.ViewModels;
using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.Windows
{
public abstract class StyleableAppWindow : AppWindow
{
public static async Task ShowAsync(StyleableAppWindow appWindow, Window owner = null)
{
#if DEBUG
appWindow.AttachDevTools(new KeyGesture(Key.F12, KeyModifiers.Control));
#endif
await appWindow.ShowDialog(owner ?? RyujinxApp.MainWindow);
}
protected StyleableAppWindow()
{
WindowStartupLocation = WindowStartupLocation.CenterOwner;
@@ -36,6 +47,14 @@ namespace Ryujinx.Ava.UI.Windows
public abstract class StyleableWindow : Window
{
public static async Task ShowAsync(StyleableWindow window, Window owner = null)
{
#if DEBUG
window.AttachDevTools(new KeyGesture(Key.F12, KeyModifiers.Control));
#endif
await window.ShowDialog(owner ?? RyujinxApp.MainWindow);
}
protected StyleableWindow()
{
WindowStartupLocation = WindowStartupLocation.CenterOwner;

View File

@@ -13,14 +13,7 @@
x:DataType="viewModels:XCITrimmerViewModel"
Focusable="True"
mc:Ignorable="d">
<Grid Margin="20 0 20 0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Margin="20 0 20 0" RowDefinitions="Auto,Auto,*,Auto,Auto">
<Panel
Margin="10 10 10 10"
Grid.Row="0">
@@ -30,12 +23,7 @@
Margin="0 0 10 10"
IsVisible="{Binding !Processing}"
Grid.Row="1">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid ColumnDefinitions="Auto,*,Auto">
<StackPanel
Grid.Column="0"
Orientation="Horizontal">
@@ -145,11 +133,7 @@
<DataTemplate
DataType="models:XCITrimmerFileModel">
<Panel Margin="10">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="65*" />
<ColumnDefinition Width="35*" />
</Grid.ColumnDefinitions>
<Grid ColumnDefinitions="65*,35*">
<TextBlock
Grid.Column="0"
Margin="10 0 10 0"
@@ -160,11 +144,7 @@
TextTrimming="CharacterEllipsis"
Text="{Binding Name}">
</TextBlock>
<Grid Grid.Column="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="45*" />
<ColumnDefinition Width="55*" />
</Grid.ColumnDefinitions>
<Grid Grid.Column="1" ColumnDefinitions="45*,55*">
<ProgressBar
Height="10"
Margin="10 0 10 0"
@@ -226,15 +206,7 @@
BorderThickness="1"
CornerRadius="5"
Padding="2.5">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid ColumnDefinitions="Auto,*" RowDefinitions="Auto,Auto">
<TextBlock
Grid.Column="0"
Grid.Row="0"
@@ -274,11 +246,7 @@
<Panel
Grid.Row="4"
HorizontalAlignment="Stretch">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid ColumnDefinitions="*,Auto">
<StackPanel
Grid.Column="0"
Orientation="Horizontal"

View File

@@ -23,6 +23,7 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
public class ApplicationData
{
public bool Favorite { get; set; }
public bool HasIndependentConfiguration { get; set; }
public byte[] Icon { get; set; }
public string Name { get; set; } = "Unknown";

View File

@@ -532,6 +532,7 @@ namespace Ryujinx.Ava.Utilities.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.FileExtension = Path.GetExtension(applicationPath).TrimStart('.').ToUpper();

View File

@@ -7,11 +7,16 @@ namespace Ryujinx.Ava.Utilities
public static class CommandLineState
{
public static string[] Arguments { get; private set; }
public static int CountArguments { get; private set; }
public static bool? OverrideDockedMode { get; private set; }
public static bool? OverrideHardwareAcceleration { get; private set; }
public static string OverrideGraphicsBackend { get; private set; }
public static string OverrideBackendThreading { get; private set; }
public static string OverrideBackendThreadingAfterReboot { get; private set; }
public static string OverridePPTC { get; private set; }
public static string OverrideMemoryManagerMode { get; private set; }
public static string OverrideSystemRegion { get; private set; }
public static string OverrideSystemLanguage { get; private set; }
public static string OverrideHideCursor { get; private set; }
public static string BaseDirPathArg { get; private set; }
public static FilePath FirmwareToInstallPathArg { get; set; }
@@ -21,6 +26,7 @@ namespace Ryujinx.Ava.Utilities
public static bool StartFullscreenArg { get; private set; }
public static bool HideAvailableUpdates { get; private set; }
public static void ParseArguments(string[] args)
{
List<string> arguments = [];
@@ -30,6 +36,11 @@ namespace Ryujinx.Ava.Utilities
{
string arg = args[i];
if (arg.Contains("-") || arg.Contains("--"))
{
CountArguments++;
}
switch (arg)
{
case "-r":
@@ -100,6 +111,57 @@ namespace Ryujinx.Ava.Utilities
OverrideBackendThreading = args[++i];
break;
case "--bt":
if (i + 1 >= args.Length)
{
Logger.Error?.Print(LogClass.Application, $"Invalid option '{arg}'");
continue;
}
OverrideBackendThreadingAfterReboot = args[++i];
break;
case "--pptc":
if (i + 1 >= args.Length)
{
Logger.Error?.Print(LogClass.Application, $"Invalid option '{arg}'");
continue;
}
OverridePPTC = args[++i];
break;
case "-m":
case "--memory-manager-mode":
if (i + 1 >= args.Length)
{
Logger.Error?.Print(LogClass.Application, $"Invalid option '{arg}'");
continue;
}
OverrideMemoryManagerMode = args[++i];
break;
case "--system-region":
if (i + 1 >= args.Length)
{
Logger.Error?.Print(LogClass.Application, $"Invalid option '{arg}'");
continue;
}
OverrideSystemRegion = args[++i];
break;
case "--system-language":
if (i + 1 >= args.Length)
{
Logger.Error?.Print(LogClass.Application, $"Invalid option '{arg}'");
continue;
}
OverrideSystemLanguage = args[++i];
break;
case "-i":
case "--application-id":
LaunchApplicationId = args[++i];

View File

@@ -18,9 +18,10 @@ namespace Ryujinx.Ava.Utilities.Configuration
{
public partial class ConfigurationState
{
public void Load(ConfigurationFileFormat cff, string configurationFilePath)
public void Load(ConfigurationFileFormat cff, string configurationFilePath, string titleId = "")
{
bool configurationFileUpdated = false;
bool shouldLoadFromFile = string.IsNullOrEmpty(titleId);
if (cff.Version is < 0 or > ConfigurationFileFormat.CurrentVersion)
{
@@ -43,14 +44,15 @@ namespace Ryujinx.Ava.Utilities.Configuration
configurationFileUpdated = true;
}
EnableDiscordIntegration.Value = cff.EnableDiscordIntegration;
CheckUpdatesOnStart.Value = cff.CheckUpdatesOnStart;
UpdateCheckerType.Value = cff.UpdateCheckerType;
CheckUpdatesOnStart.Value = shouldLoadFromFile ? cff.CheckUpdatesOnStart : CheckUpdatesOnStart.Value; // Get from global config only
UpdateCheckerType.Value = shouldLoadFromFile ? cff.UpdateCheckerType : UpdateCheckerType.Value; // Get from global config only
FocusLostActionType.Value = cff.FocusLostActionType;
ShowConfirmExit.Value = cff.ShowConfirmExit;
RememberWindowState.Value = cff.RememberWindowState;
ShowTitleBar.Value = cff.ShowTitleBar;
EnableHardwareAcceleration.Value = cff.EnableHardwareAcceleration;
ShowConfirmExit.Value = shouldLoadFromFile ? cff.ShowConfirmExit : ShowConfirmExit.Value; // Get from global config only
RememberWindowState.Value = shouldLoadFromFile ? cff.RememberWindowState : RememberWindowState.Value; // Get from global config only
ShowTitleBar.Value = shouldLoadFromFile ? cff.ShowTitleBar : ShowTitleBar.Value; // Get from global config only
EnableHardwareAcceleration.Value = shouldLoadFromFile ? cff.EnableHardwareAcceleration : EnableHardwareAcceleration.Value; // Get from global config only
HideCursor.Value = cff.HideCursor;
Logger.EnableFileLog.Value = cff.EnableFileLog;
@@ -87,8 +89,8 @@ namespace Ryujinx.Ava.Utilities.Configuration
System.Language.Value = cff.SystemLanguage;
System.Region.Value = cff.SystemRegion;
System.TimeZone.Value = cff.SystemTimeZone;
System.SystemTimeOffset.Value = cff.SystemTimeOffset;
System.MatchSystemTime.Value = cff.MatchSystemTime;
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.EnableDockedMode.Value = cff.DockedMode;
System.EnablePtc.Value = cff.EnablePtc;
System.EnableLowPowerPtc.Value = cff.EnableLowPowerPtc;
@@ -103,46 +105,47 @@ namespace Ryujinx.Ava.Utilities.Configuration
System.IgnoreControllerApplet.Value = cff.IgnoreApplet;
System.UseHypervisor.Value = cff.UseHypervisor;
UI.GuiColumns.FavColumn.Value = cff.GuiColumns.FavColumn;
UI.GuiColumns.IconColumn.Value = cff.GuiColumns.IconColumn;
UI.GuiColumns.AppColumn.Value = cff.GuiColumns.AppColumn;
UI.GuiColumns.DevColumn.Value = cff.GuiColumns.DevColumn;
UI.GuiColumns.VersionColumn.Value = cff.GuiColumns.VersionColumn;
UI.GuiColumns.TimePlayedColumn.Value = cff.GuiColumns.TimePlayedColumn;
UI.GuiColumns.LastPlayedColumn.Value = cff.GuiColumns.LastPlayedColumn;
UI.GuiColumns.FileExtColumn.Value = cff.GuiColumns.FileExtColumn;
UI.GuiColumns.FileSizeColumn.Value = cff.GuiColumns.FileSizeColumn;
UI.GuiColumns.PathColumn.Value = cff.GuiColumns.PathColumn;
UI.ColumnSort.SortColumnId.Value = cff.ColumnSort.SortColumnId;
UI.ColumnSort.SortAscending.Value = cff.ColumnSort.SortAscending;
UI.GameDirs.Value = cff.GameDirs;
UI.AutoloadDirs.Value = cff.AutoloadDirs ?? [];
UI.ShownFileTypes.NSP.Value = cff.ShownFileTypes.NSP;
UI.ShownFileTypes.PFS0.Value = cff.ShownFileTypes.PFS0;
UI.ShownFileTypes.XCI.Value = cff.ShownFileTypes.XCI;
UI.ShownFileTypes.NCA.Value = cff.ShownFileTypes.NCA;
UI.ShownFileTypes.NRO.Value = cff.ShownFileTypes.NRO;
UI.ShownFileTypes.NSO.Value = cff.ShownFileTypes.NSO;
UI.LanguageCode.Value = cff.LanguageCode;
UI.BaseStyle.Value = cff.BaseStyle;
UI.GameListViewMode.Value = cff.GameListViewMode;
UI.ShowNames.Value = cff.ShowNames;
UI.IsAscendingOrder.Value = cff.IsAscendingOrder;
UI.GridSize.Value = cff.GridSize;
UI.ApplicationSort.Value = cff.ApplicationSort;
UI.StartFullscreen.Value = cff.StartFullscreen;
UI.StartNoUI.Value = cff.StartNoUI;
UI.ShowConsole.Value = cff.ShowConsole;
UI.WindowStartup.WindowSizeWidth.Value = cff.WindowStartup.WindowSizeWidth;
UI.WindowStartup.WindowSizeHeight.Value = cff.WindowStartup.WindowSizeHeight;
UI.WindowStartup.WindowPositionX.Value = cff.WindowStartup.WindowPositionX;
UI.WindowStartup.WindowPositionY.Value = cff.WindowStartup.WindowPositionY;
UI.WindowStartup.WindowMaximized.Value = cff.WindowStartup.WindowMaximized;
UI.GuiColumns.FavColumn.Value = shouldLoadFromFile ? cff.GuiColumns.FavColumn : UI.GuiColumns.FavColumn.Value;
UI.GuiColumns.IconColumn.Value = shouldLoadFromFile ? cff.GuiColumns.IconColumn : UI.GuiColumns.IconColumn.Value;
UI.GuiColumns.AppColumn.Value = shouldLoadFromFile ? cff.GuiColumns.AppColumn : UI.GuiColumns.AppColumn.Value;
UI.GuiColumns.DevColumn.Value = shouldLoadFromFile ? cff.GuiColumns.DevColumn : UI.GuiColumns.DevColumn.Value;
UI.GuiColumns.VersionColumn.Value = shouldLoadFromFile ? cff.GuiColumns.VersionColumn : UI.GuiColumns.VersionColumn.Value;
UI.GuiColumns.TimePlayedColumn.Value = shouldLoadFromFile ? cff.GuiColumns.TimePlayedColumn : UI.GuiColumns.TimePlayedColumn.Value;
UI.GuiColumns.LastPlayedColumn.Value = shouldLoadFromFile ? cff.GuiColumns.LastPlayedColumn : UI.GuiColumns.LastPlayedColumn.Value;
UI.GuiColumns.FileExtColumn.Value = shouldLoadFromFile ? cff.GuiColumns.FileExtColumn : UI.GuiColumns.FileExtColumn.Value;
UI.GuiColumns.FileSizeColumn.Value = shouldLoadFromFile ? cff.GuiColumns.FileSizeColumn : UI.GuiColumns.FileSizeColumn.Value;
UI.GuiColumns.PathColumn.Value = shouldLoadFromFile ? cff.GuiColumns.PathColumn : UI.GuiColumns.PathColumn.Value;
UI.ColumnSort.SortColumnId.Value = shouldLoadFromFile ? cff.ColumnSort.SortColumnId : UI.ColumnSort.SortColumnId.Value;
UI.ColumnSort.SortAscending.Value = shouldLoadFromFile ? cff.ColumnSort.SortAscending : UI.ColumnSort.SortAscending.Value;
UI.GameDirs.Value = shouldLoadFromFile ? cff.GameDirs : UI.GameDirs.Value;
UI.AutoloadDirs.Value = shouldLoadFromFile ? (cff.AutoloadDirs ?? []) : UI.AutoloadDirs.Value;
UI.ShownFileTypes.NSP.Value = shouldLoadFromFile ? cff.ShownFileTypes.NSP : UI.ShownFileTypes.NSP.Value;
UI.ShownFileTypes.PFS0.Value = shouldLoadFromFile ? cff.ShownFileTypes.PFS0 : UI.ShownFileTypes.PFS0.Value;
UI.ShownFileTypes.XCI.Value = shouldLoadFromFile ? cff.ShownFileTypes.XCI : UI.ShownFileTypes.XCI.Value;
UI.ShownFileTypes.NCA.Value = shouldLoadFromFile ? cff.ShownFileTypes.NCA : UI.ShownFileTypes.NCA.Value;
UI.ShownFileTypes.NRO.Value = shouldLoadFromFile ? cff.ShownFileTypes.NRO : UI.ShownFileTypes.NRO.Value;
UI.ShownFileTypes.NSO.Value = shouldLoadFromFile ? cff.ShownFileTypes.NSO : UI.ShownFileTypes.NSO.Value;
UI.LanguageCode.Value = shouldLoadFromFile ? cff.LanguageCode : UI.LanguageCode.Value;
UI.BaseStyle.Value = shouldLoadFromFile ? cff.BaseStyle : UI.BaseStyle.Value;
UI.GameListViewMode.Value = shouldLoadFromFile ? cff.GameListViewMode : UI.GameListViewMode.Value;
UI.ShowNames.Value = shouldLoadFromFile ? cff.ShowNames : UI.ShowNames.Value;
UI.IsAscendingOrder.Value = shouldLoadFromFile ? cff.IsAscendingOrder : UI.IsAscendingOrder.Value;
UI.GridSize.Value = shouldLoadFromFile ? cff.GridSize : UI.GridSize.Value;
UI.ApplicationSort.Value = shouldLoadFromFile ? cff.ApplicationSort : UI.ApplicationSort.Value;
UI.StartFullscreen.Value = shouldLoadFromFile ? cff.StartFullscreen : UI.StartFullscreen.Value;
UI.StartNoUI.Value = shouldLoadFromFile ? cff.StartNoUI : UI.StartNoUI.Value;
UI.ShowConsole.Value = shouldLoadFromFile ? cff.ShowConsole : UI.ShowConsole.Value;
UI.WindowStartup.WindowSizeWidth.Value = shouldLoadFromFile ? cff.WindowStartup.WindowSizeWidth : UI.WindowStartup.WindowSizeWidth.Value;
UI.WindowStartup.WindowSizeHeight.Value = shouldLoadFromFile ? cff.WindowStartup.WindowSizeHeight : UI.WindowStartup.WindowSizeHeight.Value;
UI.WindowStartup.WindowPositionX.Value = shouldLoadFromFile ? cff.WindowStartup.WindowPositionX : UI.WindowStartup.WindowPositionX.Value;
UI.WindowStartup.WindowPositionY.Value = shouldLoadFromFile ? cff.WindowStartup.WindowPositionY : UI.WindowStartup.WindowPositionY.Value;
UI.WindowStartup.WindowMaximized.Value = shouldLoadFromFile ? cff.WindowStartup.WindowMaximized : UI.WindowStartup.WindowMaximized.Value;
Hid.EnableKeyboard.Value = cff.EnableKeyboard;
Hid.EnableMouse.Value = cff.EnableMouse;
Hid.DisableInputWhenOutOfFocus.Value = cff.DisableInputWhenOutOfFocus;
Hid.Hotkeys.Value = cff.Hotkeys;
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.RainbowSpeed.Value = cff.RainbowSpeed;

View File

@@ -12,7 +12,7 @@ namespace Ryujinx.Ava.Utilities
public static class ShortcutHelper
{
[SupportedOSPlatform("windows")]
private static void CreateShortcutWindows(string applicationFilePath, string applicationId, byte[] iconData, string iconPath, string cleanedAppName, string desktopPath)
private static void CreateShortcutWindows(string applicationFilePath, string applicationId, byte[] iconData, string iconPath, string cleanedAppName, string desktopPath, string args = "")
{
string basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, AppDomain.CurrentDomain.FriendlyName + ".exe");
iconPath += ".ico";
@@ -22,13 +22,13 @@ namespace Ryujinx.Ava.Utilities
image.Resize(new SKImageInfo(128, 128), SKFilterQuality.High);
SaveBitmapAsIcon(image, iconPath);
Shortcut shortcut = Shortcut.CreateShortcut(basePath, GetArgsString(applicationFilePath, applicationId), iconPath, 0);
Shortcut shortcut = Shortcut.CreateShortcut(basePath, GetArgsString(applicationFilePath, applicationId, args), iconPath, 0);
shortcut.StringData.NameString = cleanedAppName;
shortcut.WriteToFile(Path.Combine(desktopPath, cleanedAppName + ".lnk"));
}
[SupportedOSPlatform("linux")]
private static void CreateShortcutLinux(string applicationFilePath, string applicationId, byte[] iconData, string iconPath, string desktopPath, string cleanedAppName)
private static void CreateShortcutLinux(string applicationFilePath, string applicationId, byte[] iconData, string iconPath, string desktopPath, string cleanedAppName, string args = "")
{
string basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx.sh");
string desktopFile = EmbeddedResources.ReadAllText("Ryujinx/Assets/ShortcutFiles/shortcut-template.desktop");
@@ -40,11 +40,11 @@ namespace Ryujinx.Ava.Utilities
data.SaveTo(file);
using StreamWriter outputFile = new(Path.Combine(desktopPath, cleanedAppName + ".desktop"));
outputFile.Write(desktopFile, cleanedAppName, iconPath, $"{basePath} {GetArgsString(applicationFilePath, applicationId)}");
outputFile.Write(desktopFile, cleanedAppName, iconPath, $"{basePath} {GetArgsString(applicationFilePath, applicationId, args)}");
}
[SupportedOSPlatform("macos")]
private static void CreateShortcutMacos(string appFilePath, string applicationId, byte[] iconData, string desktopPath, string cleanedAppName)
private static void CreateShortcutMacos(string appFilePath, string applicationId, byte[] iconData, string desktopPath, string cleanedAppName, string args = "")
{
string basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx");
string plistFile = EmbeddedResources.ReadAllText("Ryujinx/Assets/ShortcutFiles/shortcut-template.plist");
@@ -63,7 +63,7 @@ namespace Ryujinx.Ava.Utilities
string scriptPath = Path.Combine(scriptFolderPath, ScriptName);
using StreamWriter scriptFile = new(scriptPath);
scriptFile.Write(shortcutScript, basePath, GetArgsString(appFilePath, applicationId));
scriptFile.Write(shortcutScript, basePath, GetArgsString(appFilePath, applicationId, args));
// Set execute permission
FileInfo fileInfo = new(scriptPath);
@@ -87,7 +87,7 @@ namespace Ryujinx.Ava.Utilities
outputFile.Write(plistFile, ScriptName, cleanedAppName, IconName);
}
public static void CreateAppShortcut(string applicationFilePath, string applicationName, string applicationId, byte[] iconData)
public static void CreateAppShortcut(string applicationFilePath, string applicationName, string applicationId, byte[] iconData, string args = "")
{
string desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);
string cleanedAppName = string.Join("_", applicationName.Split(Path.GetInvalidFileNameChars()));
@@ -96,7 +96,7 @@ namespace Ryujinx.Ava.Utilities
{
string iconPath = Path.Combine(AppDataManager.BaseDirPath, "games", applicationId, "app");
CreateShortcutWindows(applicationFilePath, applicationId, iconData, iconPath, cleanedAppName, desktopPath);
CreateShortcutWindows(applicationFilePath, applicationId, iconData, iconPath, cleanedAppName, desktopPath, args);
return;
}
@@ -106,14 +106,14 @@ namespace Ryujinx.Ava.Utilities
string iconPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share", "icons", "Ryujinx");
Directory.CreateDirectory(iconPath);
CreateShortcutLinux(applicationFilePath, applicationId, iconData, Path.Combine(iconPath, applicationId), desktopPath, cleanedAppName);
CreateShortcutLinux(applicationFilePath, applicationId, iconData, Path.Combine(iconPath, applicationId), desktopPath, cleanedAppName, args);
return;
}
if (OperatingSystem.IsMacOS())
{
CreateShortcutMacos(applicationFilePath, applicationId, iconData, desktopPath, cleanedAppName);
CreateShortcutMacos(applicationFilePath, applicationId, iconData, desktopPath, cleanedAppName, args);
return;
}
@@ -121,7 +121,7 @@ namespace Ryujinx.Ava.Utilities
throw new NotImplementedException("Shortcut support has not been implemented yet for this OS.");
}
private static string GetArgsString(string appFilePath, string applicationId)
private static string GetArgsString(string appFilePath, string applicationId, string config = "")
{
// args are first defined as a list, for easier adjustments in the future
List<string> argsList = [];
@@ -132,6 +132,11 @@ namespace Ryujinx.Ava.Utilities
argsList.Add($"\"{CommandLineState.BaseDirPathArg}\"");
}
if (!string.IsNullOrEmpty(config))
{
argsList.Add(config);
}
if (appFilePath.ToLower().EndsWith(".xci"))
{
argsList.Add("--application-id");