UI: Remove title bar, put icon next to File button. Added icons to most dropdown menu actions. Title bar content is now displayed when hovering the logo in the title bar.

This commit is contained in:
Evan Husted 2024-10-17 01:24:16 -05:00
parent 235083ad75
commit 045f9a39bb
21 changed files with 381 additions and 390 deletions

View file

@ -19,6 +19,8 @@ namespace Ryujinx.UI.App.Common
{ {
public class ApplicationData public class ApplicationData
{ {
public static Func<string> LocalizedNever = () => "Never";
public bool Favorite { get; set; } public bool Favorite { get; set; }
public byte[] Icon { get; set; } public byte[] Icon { get; set; }
public string Name { get; set; } = "Unknown"; public string Name { get; set; } = "Unknown";
@ -34,7 +36,7 @@ namespace Ryujinx.UI.App.Common
public string TimePlayedString => ValueFormatUtils.FormatTimeSpan(TimePlayed); public string TimePlayedString => ValueFormatUtils.FormatTimeSpan(TimePlayed);
public string LastPlayedString => ValueFormatUtils.FormatDateTime(LastPlayed); public string LastPlayedString => ValueFormatUtils.FormatDateTime(LastPlayed) ?? LocalizedNever();
public string FileSizeString => ValueFormatUtils.FormatFileSize(FileSize); public string FileSizeString => ValueFormatUtils.FormatFileSize(FileSize);

View file

@ -1,3 +1,4 @@
using ARMeilleure;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common.Configuration.Hid;
@ -1645,6 +1646,8 @@ namespace Ryujinx.UI.Common.Configuration
} }
Instance = new ConfigurationState(); Instance = new ConfigurationState();
Instance.System.EnableLowPowerPtc.Event += (_, evnt) => Optimizations.LowPower = evnt.NewValue;
} }
} }
} }

View file

@ -75,13 +75,7 @@ namespace Ryujinx.UI.Common.Helper
{ {
culture ??= CultureInfo.CurrentCulture; culture ??= CultureInfo.CurrentCulture;
if (!utcDateTime.HasValue) return utcDateTime?.ToLocalTime().ToString(culture);
{
// In the Avalonia UI, this is turned into a localized version of "Never" by LocalizedNeverConverter.
return "Never";
}
return utcDateTime.Value.ToLocalTime().ToString(culture);
} }
/// <summary> /// <summary>

View file

@ -0,0 +1,26 @@
using Avalonia.Data.Core;
using Avalonia.Markup.Xaml;
using Avalonia.Markup.Xaml.MarkupExtensions;
using Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings;
using System;
namespace Ryujinx.Ava.Common.Icon
{
internal class IconExtension(string iconString) : MarkupExtension
{
private ClrPropertyInfo PropertyInfo
=> new(
"Item",
_ => new Projektanker.Icons.Avalonia.Icon { Value = iconString },
null,
typeof(Projektanker.Icons.Avalonia.Icon)
);
public override object ProvideValue(IServiceProvider serviceProvider) =>
new CompiledBindingExtension(
new CompiledBindingPathBuilder()
.Property(PropertyInfo, PropertyInfoAccessorFactory.CreateInpcPropertyAccessor)
.Build()
).ProvideValue(serviceProvider);
}
}

View file

@ -1,6 +1,11 @@
using ARMeilleure;
using Avalonia; using Avalonia;
using Avalonia.Threading; using Avalonia.Threading;
using DiscordRPC; using DiscordRPC;
using Projektanker.Icons.Avalonia;
using Projektanker.Icons.Avalonia.FontAwesome;
using Projektanker.Icons.Avalonia.MaterialDesign;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Windows; using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common; using Ryujinx.Common;
@ -11,6 +16,7 @@ using Ryujinx.Common.SystemInterop;
using Ryujinx.Graphics.Vulkan.MoltenVK; using Ryujinx.Graphics.Vulkan.MoltenVK;
using Ryujinx.Modules; using Ryujinx.Modules;
using Ryujinx.SDL2.Common; using Ryujinx.SDL2.Common;
using Ryujinx.UI.App.Common;
using Ryujinx.UI.Common; using Ryujinx.UI.Common;
using Ryujinx.UI.Common.Configuration; using Ryujinx.UI.Common.Configuration;
using Ryujinx.UI.Common.Helper; using Ryujinx.UI.Common.Helper;
@ -51,31 +57,32 @@ namespace Ryujinx.Ava
LoggerAdapter.Register(); LoggerAdapter.Register();
IconProvider.Current
.Register<FontAwesomeIconProvider>()
.Register<MaterialDesignIconProvider>();
return BuildAvaloniaApp().StartWithClassicDesktopLifetime(args); return BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
} }
public static AppBuilder BuildAvaloniaApp() public static AppBuilder BuildAvaloniaApp() =>
{ AppBuilder.Configure<App>()
return AppBuilder.Configure<App>()
.UsePlatformDetect() .UsePlatformDetect()
.With(new X11PlatformOptions .With(new X11PlatformOptions
{ {
EnableMultiTouch = true, EnableMultiTouch = true,
EnableIme = true, EnableIme = true,
EnableInputFocusProxy = Environment.GetEnvironmentVariable("XDG_CURRENT_DESKTOP") == "gamescope", EnableInputFocusProxy = Environment.GetEnvironmentVariable("XDG_CURRENT_DESKTOP") == "gamescope",
RenderingMode = UseHardwareAcceleration ? RenderingMode = UseHardwareAcceleration
new[] { X11RenderingMode.Glx, X11RenderingMode.Software } : ? [ X11RenderingMode.Glx, X11RenderingMode.Software ]
new[] { X11RenderingMode.Software }, : [ X11RenderingMode.Software ],
}) })
.With(new Win32PlatformOptions .With(new Win32PlatformOptions
{ {
WinUICompositionBackdropCornerRadius = 8.0f, WinUICompositionBackdropCornerRadius = 8.0f,
RenderingMode = UseHardwareAcceleration ? RenderingMode = UseHardwareAcceleration
new[] { Win32RenderingMode.AngleEgl, Win32RenderingMode.Software } : ? [ Win32RenderingMode.AngleEgl, Win32RenderingMode.Software ]
new[] { Win32RenderingMode.Software }, : [ Win32RenderingMode.Software ],
}) });
.UseSkia();
}
private static void Initialize(string[] args) private static void Initialize(string[] args)
{ {
@ -102,6 +109,9 @@ namespace Ryujinx.Ava
// Setup base data directory. // Setup base data directory.
AppDataManager.Initialize(CommandLineState.BaseDirPathArg); AppDataManager.Initialize(CommandLineState.BaseDirPathArg);
// Set the delegate for localizing the word "never" in the UI
ApplicationData.LocalizedNever = () => LocaleManager.Instance[LocaleKeys.Never];
// Initialize the configuration. // Initialize the configuration.
ConfigurationState.Initialize(); ConfigurationState.Initialize();
@ -218,14 +228,10 @@ namespace Ryujinx.Ava
Logger.Notice.Print(LogClass.Application, $"Logs Enabled: {(Logger.GetEnabledLevels().Count == 0 ? "<None>" : string.Join(", ", Logger.GetEnabledLevels()))}"); Logger.Notice.Print(LogClass.Application, $"Logs Enabled: {(Logger.GetEnabledLevels().Count == 0 ? "<None>" : string.Join(", ", Logger.GetEnabledLevels()))}");
if (AppDataManager.Mode == AppDataManager.LaunchMode.Custom) Logger.Notice.Print(LogClass.Application,
{ AppDataManager.Mode == AppDataManager.LaunchMode.Custom
Logger.Notice.Print(LogClass.Application, $"Launch Mode: Custom Path {AppDataManager.BaseDirPath}"); ? $"Launch Mode: Custom Path {AppDataManager.BaseDirPath}"
} : $"Launch Mode: {AppDataManager.Mode}");
else
{
Logger.Notice.Print(LogClass.Application, $"Launch Mode: {AppDataManager.Mode}");
}
} }
private static void ProcessUnhandledException(Exception ex, bool isTerminating) private static void ProcessUnhandledException(Exception ex, bool isTerminating)

View file

@ -44,7 +44,9 @@
<PackageReference Include="Avalonia.Svg.Skia" /> <PackageReference Include="Avalonia.Svg.Skia" />
<PackageReference Include="DynamicData" /> <PackageReference Include="DynamicData" />
<PackageReference Include="FluentAvaloniaUI" /> <PackageReference Include="FluentAvaloniaUI" />
<PackageReference Include="Projektanker.Icons.Avalonia" />
<PackageReference Include="Projektanker.Icons.Avalonia.FontAwesome" />
<PackageReference Include="Projektanker.Icons.Avalonia.MaterialDesign" />
<PackageReference Include="OpenTK.Core" /> <PackageReference Include="OpenTK.Core" />
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'" /> <PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'" />
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" /> <PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" />
@ -69,8 +71,7 @@
<ProjectReference Include="..\Ryujinx.Graphics.OpenGL\Ryujinx.Graphics.OpenGL.csproj" /> <ProjectReference Include="..\Ryujinx.Graphics.OpenGL\Ryujinx.Graphics.OpenGL.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.Gpu\Ryujinx.Graphics.Gpu.csproj" /> <ProjectReference Include="..\Ryujinx.Graphics.Gpu\Ryujinx.Graphics.Gpu.csproj" />
<ProjectReference Include="..\Ryujinx.UI.Common\Ryujinx.UI.Common.csproj" /> <ProjectReference Include="..\Ryujinx.UI.Common\Ryujinx.UI.Common.csproj" />
<ProjectReference Include="..\Ryujinx.UI.LocaleGenerator\Ryujinx.UI.LocaleGenerator.csproj" <ProjectReference Include="..\Ryujinx.UI.LocaleGenerator\Ryujinx.UI.LocaleGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View file

@ -122,7 +122,7 @@ namespace Ryujinx.Ava.UI.Applet
{ {
try try
{ {
_parent.ViewModel.AppHost.NpadManager.BlockInputUpdates(); MainWindow.ViewModel.AppHost.NpadManager.BlockInputUpdates();
var response = await SwkbdAppletDialog.ShowInputDialog(LocaleManager.Instance[LocaleKeys.SoftwareKeyboard], args); var response = await SwkbdAppletDialog.ShowInputDialog(LocaleManager.Instance[LocaleKeys.SoftwareKeyboard], args);
if (response.Result == UserResult.Ok) if (response.Result == UserResult.Ok)
@ -144,7 +144,7 @@ namespace Ryujinx.Ava.UI.Applet
}); });
dialogCloseEvent.WaitOne(); dialogCloseEvent.WaitOne();
_parent.ViewModel.AppHost.NpadManager.UnblockInputUpdates(); MainWindow.ViewModel.AppHost.NpadManager.UnblockInputUpdates();
userText = error ? null : inputText; userText = error ? null : inputText;
@ -154,7 +154,7 @@ namespace Ryujinx.Ava.UI.Applet
public void ExecuteProgram(Switch device, ProgramSpecifyKind kind, ulong value) public void ExecuteProgram(Switch device, ProgramSpecifyKind kind, ulong value)
{ {
device.Configuration.UserChannelPersistence.ExecuteProgram(kind, value); device.Configuration.UserChannelPersistence.ExecuteProgram(kind, value);
_parent.ViewModel.AppHost?.Stop(); MainWindow.ViewModel.AppHost?.Stop();
} }
public bool DisplayErrorAppletDialog(string title, string message, string[] buttons) public bool DisplayErrorAppletDialog(string title, string message, string[] buttons)

View file

@ -129,7 +129,7 @@ namespace Ryujinx.Ava.UI.Applet
Dispatcher.UIThread.Post(() => Dispatcher.UIThread.Post(() =>
{ {
_hiddenTextBox.Clear(); _hiddenTextBox.Clear();
_parent.ViewModel.RendererHostControl.Focus(); MainWindow.ViewModel.RendererHostControl.Focus();
_parent = null; _parent = null;
}); });

View file

@ -3,24 +3,29 @@
xmlns="https://github.com/avaloniaui" xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:icon="clr-namespace:Ryujinx.Ava.Common.Icon"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
x:DataType="viewModels:MainWindowViewModel"> x:DataType="viewModels:MainWindowViewModel">
<MenuItem <MenuItem
Click="RunApplication_Click" Click="RunApplication_Click"
Header="{locale:Locale GameListContextMenuRunApplication}" /> Header="{locale:Locale GameListContextMenuRunApplication}"
Icon="{icon:Icon fa-solid fa-play}"/>
<MenuItem <MenuItem
Click="ToggleFavorite_Click" Click="ToggleFavorite_Click"
Header="{locale:Locale GameListContextMenuToggleFavorite}" Header="{locale:Locale GameListContextMenuToggleFavorite}"
Icon="{icon:Icon fa-solid fa-star}"
ToolTip.Tip="{locale:Locale GameListContextMenuToggleFavoriteToolTip}" /> ToolTip.Tip="{locale:Locale GameListContextMenuToggleFavoriteToolTip}" />
<MenuItem <MenuItem
Click="CreateApplicationShortcut_Click" Click="CreateApplicationShortcut_Click"
Header="{locale:Locale GameListContextMenuCreateShortcut}" Header="{locale:Locale GameListContextMenuCreateShortcut}"
IsEnabled="{Binding CreateShortcutEnabled}" IsEnabled="{Binding CreateShortcutEnabled}"
ToolTip.Tip="{OnPlatform Default={locale:Locale GameListContextMenuCreateShortcutToolTip}, macOS={locale:Locale GameListContextMenuCreateShortcutToolTipMacOS}}" /> Icon="{icon:Icon fa-solid fa-bookmark}"
ToolTip.Tip="{OnPlatform Default={locale:Locale GameListContextMenuCreateShortcutToolTip}, macOS={locale:Locale GameListContextMenuCreateShortcutToolTipMacOS}}" />
<Separator /> <Separator />
<MenuItem <MenuItem
Click="OpenUserSaveDirectory_Click" Click="OpenUserSaveDirectory_Click"
Header="{locale:Locale GameListContextMenuOpenUserSaveDirectory}" Header="{locale:Locale GameListContextMenuOpenUserSaveDirectory}"
Icon="{icon:Icon mdi-folder-account}"
IsEnabled="{Binding OpenUserSaveDirectoryEnabled}" IsEnabled="{Binding OpenUserSaveDirectoryEnabled}"
ToolTip.Tip="{locale:Locale GameListContextMenuOpenUserSaveDirectoryToolTip}" /> ToolTip.Tip="{locale:Locale GameListContextMenuOpenUserSaveDirectoryToolTip}" />
<MenuItem <MenuItem
@ -37,45 +42,55 @@
<MenuItem <MenuItem
Click="OpenTitleUpdateManager_Click" Click="OpenTitleUpdateManager_Click"
Header="{locale:Locale GameListContextMenuManageTitleUpdates}" Header="{locale:Locale GameListContextMenuManageTitleUpdates}"
Icon="{icon:Icon fa-solid fa-code-compare}"
ToolTip.Tip="{locale:Locale GameListContextMenuManageTitleUpdatesToolTip}" /> ToolTip.Tip="{locale:Locale GameListContextMenuManageTitleUpdatesToolTip}" />
<MenuItem <MenuItem
Click="OpenDownloadableContentManager_Click" Click="OpenDownloadableContentManager_Click"
Header="{locale:Locale GameListContextMenuManageDlc}" Header="{locale:Locale GameListContextMenuManageDlc}"
Icon="{icon:Icon fa-solid fa-download}"
ToolTip.Tip="{locale:Locale GameListContextMenuManageDlcToolTip}" /> ToolTip.Tip="{locale:Locale GameListContextMenuManageDlcToolTip}" />
<MenuItem <MenuItem
Click="OpenCheatManager_Click" Click="OpenCheatManager_Click"
Header="{locale:Locale GameListContextMenuManageCheat}" Header="{locale:Locale GameListContextMenuManageCheat}"
Icon="{icon:Icon fa-solid fa-code}"
ToolTip.Tip="{locale:Locale GameListContextMenuManageCheatToolTip}" /> ToolTip.Tip="{locale:Locale GameListContextMenuManageCheatToolTip}" />
<MenuItem <MenuItem
Click="OpenModManager_Click" Click="OpenModManager_Click"
Header="{locale:Locale GameListContextMenuManageMod}" Header="{locale:Locale GameListContextMenuManageMod}"
Icon="{icon:Icon mdi-view-module}"
ToolTip.Tip="{locale:Locale GameListContextMenuManageModToolTip}" /> ToolTip.Tip="{locale:Locale GameListContextMenuManageModToolTip}" />
<Separator /> <Separator />
<MenuItem <MenuItem
Click="OpenModsDirectory_Click" Click="OpenModsDirectory_Click"
Header="{locale:Locale GameListContextMenuOpenModsDirectory}" Header="{locale:Locale GameListContextMenuOpenModsDirectory}"
Icon="{icon:Icon mdi-folder-file}"
ToolTip.Tip="{locale:Locale GameListContextMenuOpenModsDirectoryToolTip}" /> ToolTip.Tip="{locale:Locale GameListContextMenuOpenModsDirectoryToolTip}" />
<MenuItem <MenuItem
Click="OpenSdModsDirectory_Click" Click="OpenSdModsDirectory_Click"
Header="{locale:Locale GameListContextMenuOpenSdModsDirectory}" Header="{locale:Locale GameListContextMenuOpenSdModsDirectory}"
Icon="{icon:Icon mdi-folder-file}"
ToolTip.Tip="{locale:Locale GameListContextMenuOpenSdModsDirectoryToolTip}" /> ToolTip.Tip="{locale:Locale GameListContextMenuOpenSdModsDirectoryToolTip}" />
<Separator /> <Separator />
<MenuItem Header="{locale:Locale GameListContextMenuCacheManagement}"> <MenuItem Header="{locale:Locale GameListContextMenuCacheManagement}" Icon="{icon:Icon mdi-cached}">
<MenuItem <MenuItem
Click="PurgePtcCache_Click" Click="PurgePtcCache_Click"
Header="{locale:Locale GameListContextMenuCacheManagementPurgePptc}" Header="{locale:Locale GameListContextMenuCacheManagementPurgePptc}"
Icon="{icon:Icon mdi-refresh}"
ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementPurgePptcToolTip}" /> ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementPurgePptcToolTip}" />
<MenuItem <MenuItem
Click="PurgeShaderCache_Click" Click="PurgeShaderCache_Click"
Header="{locale:Locale GameListContextMenuCacheManagementPurgeShaderCache}" Header="{locale:Locale GameListContextMenuCacheManagementPurgeShaderCache}"
Icon="{icon:Icon mdi-delete-alert}"
ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementPurgeShaderCacheToolTip}" /> ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementPurgeShaderCacheToolTip}" />
<MenuItem <MenuItem
Click="OpenPtcDirectory_Click" Click="OpenPtcDirectory_Click"
Header="{locale:Locale GameListContextMenuCacheManagementOpenPptcDirectory}" Header="{locale:Locale GameListContextMenuCacheManagementOpenPptcDirectory}"
Icon="{icon:Icon mdi-folder-arrow-up-down}"
ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementOpenPptcDirectoryToolTip}" /> ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementOpenPptcDirectoryToolTip}" />
<MenuItem <MenuItem
Click="OpenShaderCacheDirectory_Click" Click="OpenShaderCacheDirectory_Click"
Header="{locale:Locale GameListContextMenuCacheManagementOpenShaderCacheDirectory}" Header="{locale:Locale GameListContextMenuCacheManagementOpenShaderCacheDirectory}"
Icon="{icon:Icon mdi-folder-arrow-up-down}"
ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip}" /> ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip}" />
</MenuItem> </MenuItem>
<MenuItem Header="{locale:Locale GameListContextMenuExtractData}"> <MenuItem Header="{locale:Locale GameListContextMenuExtractData}">

View file

@ -13,6 +13,7 @@ using Ryujinx.Common.Configuration;
using Ryujinx.HLE.HOS; using Ryujinx.HLE.HOS;
using Ryujinx.UI.App.Common; using Ryujinx.UI.App.Common;
using Ryujinx.UI.Common.Helper; using Ryujinx.UI.Common.Helper;
using SkiaSharp;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
@ -34,165 +35,132 @@ namespace Ryujinx.Ava.UI.Controls
public void ToggleFavorite_Click(object sender, RoutedEventArgs args) public void ToggleFavorite_Click(object sender, RoutedEventArgs args)
{ {
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; if (sender is not MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
return;
if (viewModel?.SelectedApplication != null) viewModel.SelectedApplication.Favorite = !viewModel.SelectedApplication.Favorite;
ApplicationLibrary.LoadAndSaveMetaData(viewModel.SelectedApplication.IdString, appMetadata =>
{ {
viewModel.SelectedApplication.Favorite = !viewModel.SelectedApplication.Favorite; appMetadata.Favorite = viewModel.SelectedApplication.Favorite;
});
ApplicationLibrary.LoadAndSaveMetaData(viewModel.SelectedApplication.IdString, appMetadata => viewModel.RefreshView();
{
appMetadata.Favorite = viewModel.SelectedApplication.Favorite;
});
viewModel.RefreshView();
}
} }
public void OpenUserSaveDirectory_Click(object sender, RoutedEventArgs args) public void OpenUserSaveDirectory_Click(object sender, RoutedEventArgs args)
{ {
if (sender is MenuItem { DataContext: MainWindowViewModel viewModel }) if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
{
OpenSaveDirectory(viewModel, SaveDataType.Account, new UserId((ulong)viewModel.AccountManager.LastOpenedUser.UserId.High, (ulong)viewModel.AccountManager.LastOpenedUser.UserId.Low)); OpenSaveDirectory(viewModel, SaveDataType.Account, new UserId((ulong)viewModel.AccountManager.LastOpenedUser.UserId.High, (ulong)viewModel.AccountManager.LastOpenedUser.UserId.Low));
}
} }
public void OpenDeviceSaveDirectory_Click(object sender, RoutedEventArgs args) public void OpenDeviceSaveDirectory_Click(object sender, RoutedEventArgs args)
{ {
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
OpenSaveDirectory(viewModel, SaveDataType.Device, default);
OpenSaveDirectory(viewModel, SaveDataType.Device, default);
} }
public void OpenBcatSaveDirectory_Click(object sender, RoutedEventArgs args) public void OpenBcatSaveDirectory_Click(object sender, RoutedEventArgs args)
{ {
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
OpenSaveDirectory(viewModel, SaveDataType.Bcat, default);
OpenSaveDirectory(viewModel, SaveDataType.Bcat, default);
} }
private static void OpenSaveDirectory(MainWindowViewModel viewModel, SaveDataType saveDataType, UserId userId) private static void OpenSaveDirectory(MainWindowViewModel viewModel, SaveDataType saveDataType, UserId userId)
{ {
if (viewModel?.SelectedApplication != null) var saveDataFilter = SaveDataFilter.Make(viewModel.SelectedApplication.Id, saveDataType, userId, saveDataId: default, index: default);
{
var saveDataFilter = SaveDataFilter.Make(viewModel.SelectedApplication.Id, saveDataType, userId, saveDataId: default, index: default);
ApplicationHelper.OpenSaveDir(in saveDataFilter, viewModel.SelectedApplication.Id, viewModel.SelectedApplication.ControlHolder, viewModel.SelectedApplication.Name); ApplicationHelper.OpenSaveDir(in saveDataFilter, viewModel.SelectedApplication.Id, viewModel.SelectedApplication.ControlHolder, viewModel.SelectedApplication.Name);
}
} }
public async void OpenTitleUpdateManager_Click(object sender, RoutedEventArgs args) public async void OpenTitleUpdateManager_Click(object sender, RoutedEventArgs args)
{ {
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
if (viewModel?.SelectedApplication != null)
{
await TitleUpdateWindow.Show(viewModel.ApplicationLibrary, viewModel.SelectedApplication); await TitleUpdateWindow.Show(viewModel.ApplicationLibrary, viewModel.SelectedApplication);
}
} }
public async void OpenDownloadableContentManager_Click(object sender, RoutedEventArgs args) public async void OpenDownloadableContentManager_Click(object sender, RoutedEventArgs args)
{ {
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
if (viewModel?.SelectedApplication != null)
{
await DownloadableContentManagerWindow.Show(viewModel.ApplicationLibrary, viewModel.SelectedApplication); await DownloadableContentManagerWindow.Show(viewModel.ApplicationLibrary, viewModel.SelectedApplication);
}
} }
public async void OpenCheatManager_Click(object sender, RoutedEventArgs args) public async void OpenCheatManager_Click(object sender, RoutedEventArgs args)
{ {
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
if (viewModel?.SelectedApplication != null)
{
await new CheatWindow( await new CheatWindow(
viewModel.VirtualFileSystem, viewModel.VirtualFileSystem,
viewModel.SelectedApplication.IdString, viewModel.SelectedApplication.IdString,
viewModel.SelectedApplication.Name, viewModel.SelectedApplication.Name,
viewModel.SelectedApplication.Path).ShowDialog(viewModel.TopLevel as Window); viewModel.SelectedApplication.Path).ShowDialog((Window)viewModel.TopLevel);
}
} }
public void OpenModsDirectory_Click(object sender, RoutedEventArgs args) public void OpenModsDirectory_Click(object sender, RoutedEventArgs args)
{ {
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; if (sender is not MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
return;
if (viewModel?.SelectedApplication != null) string modsBasePath = ModLoader.GetModsBasePath();
{ string titleModsPath = ModLoader.GetApplicationDir(modsBasePath, viewModel.SelectedApplication.IdString);
string modsBasePath = ModLoader.GetModsBasePath();
string titleModsPath = ModLoader.GetApplicationDir(modsBasePath, viewModel.SelectedApplication.IdString);
OpenHelper.OpenFolder(titleModsPath); OpenHelper.OpenFolder(titleModsPath);
}
} }
public void OpenSdModsDirectory_Click(object sender, RoutedEventArgs args) public void OpenSdModsDirectory_Click(object sender, RoutedEventArgs args)
{ {
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; if (sender is not MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
return;
if (viewModel?.SelectedApplication != null) string sdModsBasePath = ModLoader.GetSdModsBasePath();
{ string titleModsPath = ModLoader.GetApplicationDir(sdModsBasePath, viewModel.SelectedApplication.IdString);
string sdModsBasePath = ModLoader.GetSdModsBasePath();
string titleModsPath = ModLoader.GetApplicationDir(sdModsBasePath, viewModel.SelectedApplication.IdString);
OpenHelper.OpenFolder(titleModsPath); OpenHelper.OpenFolder(titleModsPath);
}
} }
public async void OpenModManager_Click(object sender, RoutedEventArgs args) public async void OpenModManager_Click(object sender, RoutedEventArgs args)
{ {
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
if (viewModel?.SelectedApplication != null)
{
await ModManagerWindow.Show(viewModel.SelectedApplication.Id, viewModel.SelectedApplication.Name); await ModManagerWindow.Show(viewModel.SelectedApplication.Id, viewModel.SelectedApplication.Name);
}
} }
public async void PurgePtcCache_Click(object sender, RoutedEventArgs args) public async void PurgePtcCache_Click(object sender, RoutedEventArgs args)
{ {
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; if (sender is not MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
return;
if (viewModel?.SelectedApplication != null) UserResult result = await ContentDialogHelper.CreateLocalizedConfirmationDialog(
LocaleManager.Instance[LocaleKeys.DialogWarning],
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionMessage, viewModel.SelectedApplication.Name)
);
if (result == UserResult.Yes)
{ {
UserResult result = await ContentDialogHelper.CreateConfirmationDialog( DirectoryInfo mainDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "cpu", "0"));
LocaleManager.Instance[LocaleKeys.DialogWarning], DirectoryInfo backupDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "cpu", "1"));
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionMessage, viewModel.SelectedApplication.Name),
LocaleManager.Instance[LocaleKeys.InputDialogYes],
LocaleManager.Instance[LocaleKeys.InputDialogNo],
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
if (result == UserResult.Yes) List<FileInfo> cacheFiles = new();
if (mainDir.Exists)
{ {
DirectoryInfo mainDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "cpu", "0")); cacheFiles.AddRange(mainDir.EnumerateFiles("*.cache"));
DirectoryInfo backupDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "cpu", "1")); }
List<FileInfo> cacheFiles = new(); if (backupDir.Exists)
{
cacheFiles.AddRange(backupDir.EnumerateFiles("*.cache"));
}
if (mainDir.Exists) if (cacheFiles.Count > 0)
{
foreach (FileInfo file in cacheFiles)
{ {
cacheFiles.AddRange(mainDir.EnumerateFiles("*.cache")); try
}
if (backupDir.Exists)
{
cacheFiles.AddRange(backupDir.EnumerateFiles("*.cache"));
}
if (cacheFiles.Count > 0)
{
foreach (FileInfo file in cacheFiles)
{ {
try file.Delete();
{ }
file.Delete(); catch (Exception ex)
} {
catch (Exception ex) await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionErrorMessage, file.Name, ex));
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionErrorMessage, file.Name, ex));
}
} }
} }
} }
@ -201,55 +169,51 @@ namespace Ryujinx.Ava.UI.Controls
public async void PurgeShaderCache_Click(object sender, RoutedEventArgs args) public async void PurgeShaderCache_Click(object sender, RoutedEventArgs args)
{ {
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; if (sender is not MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
return;
if (viewModel?.SelectedApplication != null) UserResult result = await ContentDialogHelper.CreateLocalizedConfirmationDialog(
LocaleManager.Instance[LocaleKeys.DialogWarning],
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogShaderDeletionMessage, viewModel.SelectedApplication.Name)
);
if (result == UserResult.Yes)
{ {
UserResult result = await ContentDialogHelper.CreateConfirmationDialog( DirectoryInfo shaderCacheDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "shader"));
LocaleManager.Instance[LocaleKeys.DialogWarning],
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogShaderDeletionMessage, viewModel.SelectedApplication.Name),
LocaleManager.Instance[LocaleKeys.InputDialogYes],
LocaleManager.Instance[LocaleKeys.InputDialogNo],
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
if (result == UserResult.Yes) List<DirectoryInfo> oldCacheDirectories = new();
List<FileInfo> newCacheFiles = new();
if (shaderCacheDir.Exists)
{ {
DirectoryInfo shaderCacheDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "shader")); oldCacheDirectories.AddRange(shaderCacheDir.EnumerateDirectories("*"));
newCacheFiles.AddRange(shaderCacheDir.GetFiles("*.toc"));
newCacheFiles.AddRange(shaderCacheDir.GetFiles("*.data"));
}
List<DirectoryInfo> oldCacheDirectories = new(); if ((oldCacheDirectories.Count > 0 || newCacheFiles.Count > 0))
List<FileInfo> newCacheFiles = new(); {
foreach (DirectoryInfo directory in oldCacheDirectories)
if (shaderCacheDir.Exists)
{ {
oldCacheDirectories.AddRange(shaderCacheDir.EnumerateDirectories("*")); try
newCacheFiles.AddRange(shaderCacheDir.GetFiles("*.toc")); {
newCacheFiles.AddRange(shaderCacheDir.GetFiles("*.data")); directory.Delete(true);
}
catch (Exception ex)
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionErrorMessage, directory.Name, ex));
}
} }
if ((oldCacheDirectories.Count > 0 || newCacheFiles.Count > 0)) foreach (FileInfo file in newCacheFiles)
{ {
foreach (DirectoryInfo directory in oldCacheDirectories) try
{ {
try file.Delete();
{
directory.Delete(true);
}
catch (Exception ex)
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionErrorMessage, directory.Name, ex));
}
} }
catch (Exception ex)
foreach (FileInfo file in newCacheFiles)
{ {
try await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.ShaderCachePurgeError, file.Name, ex));
{
file.Delete();
}
catch (Exception ex)
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.ShaderCachePurgeError, file.Name, ex));
}
} }
} }
} }
@ -258,30 +222,26 @@ namespace Ryujinx.Ava.UI.Controls
public void OpenPtcDirectory_Click(object sender, RoutedEventArgs args) public void OpenPtcDirectory_Click(object sender, RoutedEventArgs args)
{ {
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; if (sender is not MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
return;
string ptcDir = Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "cpu");
string mainDir = Path.Combine(ptcDir, "0");
string backupDir = Path.Combine(ptcDir, "1");
if (viewModel?.SelectedApplication != null) if (!Directory.Exists(ptcDir))
{ {
string ptcDir = Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "cpu"); Directory.CreateDirectory(ptcDir);
string mainDir = Path.Combine(ptcDir, "0"); Directory.CreateDirectory(mainDir);
string backupDir = Path.Combine(ptcDir, "1"); Directory.CreateDirectory(backupDir);
if (!Directory.Exists(ptcDir))
{
Directory.CreateDirectory(ptcDir);
Directory.CreateDirectory(mainDir);
Directory.CreateDirectory(backupDir);
}
OpenHelper.OpenFolder(ptcDir);
} }
OpenHelper.OpenFolder(ptcDir);
} }
public void OpenShaderCacheDirectory_Click(object sender, RoutedEventArgs args) public void OpenShaderCacheDirectory_Click(object sender, RoutedEventArgs args)
{ {
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
if (viewModel?.SelectedApplication != null)
{ {
string shaderCacheDir = Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "shader"); string shaderCacheDir = Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "shader");
@ -296,9 +256,7 @@ namespace Ryujinx.Ava.UI.Controls
public async void ExtractApplicationExeFs_Click(object sender, RoutedEventArgs args) public async void ExtractApplicationExeFs_Click(object sender, RoutedEventArgs args)
{ {
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
if (viewModel?.SelectedApplication != null)
{ {
await ApplicationHelper.ExtractSection( await ApplicationHelper.ExtractSection(
viewModel.StorageProvider, viewModel.StorageProvider,
@ -310,67 +268,60 @@ namespace Ryujinx.Ava.UI.Controls
public async void ExtractApplicationRomFs_Click(object sender, RoutedEventArgs args) public async void ExtractApplicationRomFs_Click(object sender, RoutedEventArgs args)
{ {
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
if (viewModel?.SelectedApplication != null)
{
await ApplicationHelper.ExtractSection( await ApplicationHelper.ExtractSection(
viewModel.StorageProvider, viewModel.StorageProvider,
NcaSectionType.Data, NcaSectionType.Data,
viewModel.SelectedApplication.Path, viewModel.SelectedApplication.Path,
viewModel.SelectedApplication.Name); viewModel.SelectedApplication.Name);
}
} }
public async void ExtractApplicationLogo_Click(object sender, RoutedEventArgs args) public async void ExtractApplicationLogo_Click(object sender, RoutedEventArgs args)
{ {
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; if (sender is not MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
return;
if (viewModel?.SelectedApplication is { } selectedApp) var result = await viewModel.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
{ {
var result = await viewModel.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions Title = LocaleManager.Instance[LocaleKeys.FolderDialogExtractTitle],
{ AllowMultiple = false,
Title = LocaleManager.Instance[LocaleKeys.FolderDialogExtractTitle], });
AllowMultiple = false,
});
if (result.Count == 0) if (result.Count == 0)
{ return;
return;
}
ApplicationHelper.ExtractSection( ApplicationHelper.ExtractSection(
result[0].Path.LocalPath, result[0].Path.LocalPath,
NcaSectionType.Logo, NcaSectionType.Logo,
viewModel.SelectedApplication.Path, viewModel.SelectedApplication.Path,
viewModel.SelectedApplication.Name); viewModel.SelectedApplication.Name);
var iconFile = await result[0].CreateFileAsync(selectedApp.IdString + ".png"); var iconFile = await result[0].CreateFileAsync($"{viewModel.SelectedApplication.IdString}.png");
await using var fileStream = await iconFile.OpenWriteAsync(); await using var fileStream = await iconFile.OpenWriteAsync();
fileStream.Write(selectedApp.Icon); using var bitmap = SKBitmap.Decode(viewModel.SelectedApplication.Icon)
} .Resize(new SKSizeI(512, 512), SKFilterQuality.High);
using var png = bitmap.Encode(SKEncodedImageFormat.Png, 100);
png.SaveTo(fileStream);
} }
public void CreateApplicationShortcut_Click(object sender, RoutedEventArgs args) public void CreateApplicationShortcut_Click(object sender, RoutedEventArgs args)
{ {
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
ShortcutHelper.CreateAppShortcut(
if (viewModel?.SelectedApplication != null) viewModel.SelectedApplication.Path,
{ viewModel.SelectedApplication.Name,
ApplicationData selectedApplication = viewModel.SelectedApplication; viewModel.SelectedApplication.IdString,
ShortcutHelper.CreateAppShortcut(selectedApplication.Path, selectedApplication.Name, selectedApplication.IdString, selectedApplication.Icon); viewModel.SelectedApplication.Icon
} );
} }
public async void RunApplication_Click(object sender, RoutedEventArgs args) public async void RunApplication_Click(object sender, RoutedEventArgs args)
{ {
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
if (viewModel?.SelectedApplication != null)
{
await viewModel.LoadApplication(viewModel.SelectedApplication); await viewModel.LoadApplication(viewModel.SelectedApplication);
}
} }
} }
} }

View file

@ -15,37 +15,22 @@ namespace Ryujinx.Ava.UI.Controls
public event EventHandler<ApplicationOpenedEventArgs> ApplicationOpened public event EventHandler<ApplicationOpenedEventArgs> ApplicationOpened
{ {
add { AddHandler(ApplicationOpenedEvent, value); } add => AddHandler(ApplicationOpenedEvent, value);
remove { RemoveHandler(ApplicationOpenedEvent, value); } remove => RemoveHandler(ApplicationOpenedEvent, value);
} }
public ApplicationGridView() public ApplicationGridView() => InitializeComponent();
{
InitializeComponent();
}
public void GameList_DoubleTapped(object sender, TappedEventArgs args) public void GameList_DoubleTapped(object sender, TappedEventArgs args)
{ {
if (sender is ListBox listBox) if (sender is ListBox { SelectedItem: ApplicationData selected })
{ RaiseEvent(new ApplicationOpenedEventArgs(selected, ApplicationOpenedEvent));
if (listBox.SelectedItem is ApplicationData selected)
{
RaiseEvent(new ApplicationOpenedEventArgs(selected, ApplicationOpenedEvent));
}
}
} }
public void GameList_SelectionChanged(object sender, SelectionChangedEventArgs args) public void GameList_SelectionChanged(object sender, SelectionChangedEventArgs args)
{ {
if (sender is ListBox listBox) if (DataContext is MainWindowViewModel viewModel && sender is ListBox { SelectedItem: ApplicationData selected })
{ viewModel.GridSelectedApplication = selected;
(DataContext as MainWindowViewModel).GridSelectedApplication = listBox.SelectedItem as ApplicationData;
}
}
private void SearchBox_OnKeyUp(object sender, KeyEventArgs args)
{
(DataContext as MainWindowViewModel).SearchText = (sender as TextBox).Text;
} }
} }
} }

View file

@ -109,7 +109,7 @@
Spacing="5"> Spacing="5">
<TextBlock <TextBlock
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
Text="{Binding Id, StringFormat=X16}" Text="{Binding IdString}"
TextAlignment="Start" TextAlignment="Start"
TextWrapping="Wrap" /> TextWrapping="Wrap" />
<TextBlock <TextBlock
@ -131,7 +131,7 @@
TextWrapping="Wrap" /> TextWrapping="Wrap" />
<TextBlock <TextBlock
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
Text="{Binding LastPlayedString, Converter={helpers:LocalizedNeverConverter}}" Text="{Binding LastPlayedString}"
TextAlignment="End" TextAlignment="End"
TextWrapping="Wrap" /> TextWrapping="Wrap" />
<TextBlock <TextBlock

View file

@ -15,37 +15,22 @@ namespace Ryujinx.Ava.UI.Controls
public event EventHandler<ApplicationOpenedEventArgs> ApplicationOpened public event EventHandler<ApplicationOpenedEventArgs> ApplicationOpened
{ {
add { AddHandler(ApplicationOpenedEvent, value); } add => AddHandler(ApplicationOpenedEvent, value);
remove { RemoveHandler(ApplicationOpenedEvent, value); } remove => RemoveHandler(ApplicationOpenedEvent, value);
} }
public ApplicationListView() public ApplicationListView() => InitializeComponent();
{
InitializeComponent();
}
public void GameList_DoubleTapped(object sender, TappedEventArgs args) public void GameList_DoubleTapped(object sender, TappedEventArgs args)
{ {
if (sender is ListBox listBox) if (sender is ListBox { SelectedItem: ApplicationData selected })
{ RaiseEvent(new ApplicationOpenedEventArgs(selected, ApplicationOpenedEvent));
if (listBox.SelectedItem is ApplicationData selected)
{
RaiseEvent(new ApplicationOpenedEventArgs(selected, ApplicationOpenedEvent));
}
}
} }
public void GameList_SelectionChanged(object sender, SelectionChangedEventArgs args) public void GameList_SelectionChanged(object sender, SelectionChangedEventArgs args)
{ {
if (sender is ListBox listBox) if (DataContext is MainWindowViewModel viewModel && sender is ListBox { SelectedItem: ApplicationData selected })
{ viewModel.ListSelectedApplication = selected;
(DataContext as MainWindowViewModel).ListSelectedApplication = listBox.SelectedItem as ApplicationData;
}
}
private void SearchBox_OnKeyUp(object sender, KeyEventArgs args)
{
(DataContext as MainWindowViewModel).SearchText = (sender as TextBox).Text;
} }
} }
} }

View file

@ -244,7 +244,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
_mainWindow.InputManager.GamepadDriver.OnGamepadConnected += HandleOnGamepadConnected; _mainWindow.InputManager.GamepadDriver.OnGamepadConnected += HandleOnGamepadConnected;
_mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected += HandleOnGamepadDisconnected; _mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected += HandleOnGamepadDisconnected;
_mainWindow.ViewModel.AppHost?.NpadManager.BlockInputUpdates(); MainWindow.ViewModel.AppHost?.NpadManager.BlockInputUpdates();
_isLoaded = false; _isLoaded = false;
@ -847,7 +847,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
} }
} }
_mainWindow.ViewModel.AppHost?.NpadManager.ReloadConfiguration(newConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse); MainWindow.ViewModel.AppHost?.NpadManager.ReloadConfiguration(newConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse);
// Atomically replace and signal input change. // Atomically replace and signal input change.
// NOTE: Do not modify InputConfig.Value directly as other code depends on the on-change event. // NOTE: Do not modify InputConfig.Value directly as other code depends on the on-change event.
@ -879,7 +879,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
_mainWindow.InputManager.GamepadDriver.OnGamepadConnected -= HandleOnGamepadConnected; _mainWindow.InputManager.GamepadDriver.OnGamepadConnected -= HandleOnGamepadConnected;
_mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected -= HandleOnGamepadDisconnected; _mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected -= HandleOnGamepadDisconnected;
_mainWindow.ViewModel.AppHost?.NpadManager.UnblockInputUpdates(); MainWindow.ViewModel.AppHost?.NpadManager.UnblockInputUpdates();
SelectedGamepad?.Dispose(); SelectedGamepad?.Dispose();

View file

@ -4,6 +4,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:icon="clr-namespace:Ryujinx.Ava.Common.Icon"
mc:Ignorable="d" mc:Ignorable="d"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
x:DataType="viewModels:MainWindowViewModel" x:DataType="viewModels:MainWindowViewModel"
@ -12,6 +13,13 @@
<viewModels:MainWindowViewModel /> <viewModels:MainWindowViewModel />
</Design.DataContext> </Design.DataContext>
<DockPanel HorizontalAlignment="Stretch"> <DockPanel HorizontalAlignment="Stretch">
<Border Padding="7, 0, 0, 0" VerticalAlignment="Center" HorizontalAlignment="Center">
<Image
ToolTip.Tip="{Binding Title}"
Height="25"
Width="25"
Source="resm:Ryujinx.UI.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.UI.Common" />
</Border>
<Menu <Menu
Name="Menu" Name="Menu"
Height="35" Height="35"
@ -27,27 +35,32 @@
<MenuItem <MenuItem
Command="{Binding OpenFile}" Command="{Binding OpenFile}"
Header="{locale:Locale MenuBarFileOpenFromFile}" Header="{locale:Locale MenuBarFileOpenFromFile}"
Icon="{icon:Icon fa-solid fa-file}"
IsEnabled="{Binding EnableNonGameRunningControls}" IsEnabled="{Binding EnableNonGameRunningControls}"
ToolTip.Tip="{locale:Locale LoadApplicationFileTooltip}" /> ToolTip.Tip="{locale:Locale LoadApplicationFileTooltip}" />
<MenuItem <MenuItem
Command="{Binding OpenFolder}" Command="{Binding OpenFolder}"
Header="{locale:Locale MenuBarFileOpenUnpacked}" Header="{locale:Locale MenuBarFileOpenUnpacked}"
Icon="{icon:Icon fa-solid fa-folder}"
IsEnabled="{Binding EnableNonGameRunningControls}" IsEnabled="{Binding EnableNonGameRunningControls}"
ToolTip.Tip="{locale:Locale LoadApplicationFolderTooltip}" /> ToolTip.Tip="{locale:Locale LoadApplicationFolderTooltip}" />
<MenuItem <MenuItem
Command="{Binding LoadDlcFromFolder}" Command="{Binding LoadDlcFromFolder}"
Header="{locale:Locale MenuBarFileLoadDlcFromFolder}" Header="{locale:Locale MenuBarFileLoadDlcFromFolder}"
Icon="{icon:Icon fa-solid fa-download}"
IsEnabled="{Binding EnableNonGameRunningControls}" IsEnabled="{Binding EnableNonGameRunningControls}"
ToolTip.Tip="{locale:Locale LoadDlcFromFolderTooltip}" /> ToolTip.Tip="{locale:Locale LoadDlcFromFolderTooltip}" />
<MenuItem <MenuItem
Command="{Binding LoadTitleUpdatesFromFolder}" Command="{Binding LoadTitleUpdatesFromFolder}"
Header="{locale:Locale MenuBarFileLoadTitleUpdatesFromFolder}" Header="{locale:Locale MenuBarFileLoadTitleUpdatesFromFolder}"
Icon="{icon:Icon fa-solid fa-code-compare}"
IsEnabled="{Binding EnableNonGameRunningControls}" IsEnabled="{Binding EnableNonGameRunningControls}"
ToolTip.Tip="{locale:Locale LoadTitleUpdatesFromFolderTooltip}" /> ToolTip.Tip="{locale:Locale LoadTitleUpdatesFromFolderTooltip}" />
<MenuItem Header="{locale:Locale MenuBarFileOpenApplet}" IsEnabled="{Binding IsAppletMenuActive}"> <MenuItem Header="{locale:Locale MenuBarFileOpenApplet}" IsEnabled="{Binding IsAppletMenuActive}" Icon="{icon:Icon mdi-launch}">
<MenuItem <MenuItem
Click="OpenMiiApplet" Click="OpenMiiApplet"
Header="Mii Edit Applet" Header="Mii Edit Applet"
Icon="{icon:Icon fa-solid fa-person}"
ToolTip.Tip="{locale:Locale MenuBarFileOpenAppletOpenMiiAppletToolTip}" /> ToolTip.Tip="{locale:Locale MenuBarFileOpenAppletOpenMiiAppletToolTip}" />
</MenuItem> </MenuItem>
<Separator /> <Separator />
@ -63,14 +76,28 @@
<MenuItem <MenuItem
Click="CloseWindow" Click="CloseWindow"
Header="{locale:Locale MenuBarFileExit}" Header="{locale:Locale MenuBarFileExit}"
Icon="{icon:Icon fa-solid fa-xmark}"
ToolTip.Tip="{locale:Locale ExitTooltip}" /> ToolTip.Tip="{locale:Locale ExitTooltip}" />
</MenuItem> </MenuItem>
<MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarOptions}"> <MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarOptions}">
<MenuItem <MenuItem
Padding="-10,0,0,0"
Command="{Binding ToggleFullscreen}" Command="{Binding ToggleFullscreen}"
Header="{locale:Locale MenuBarOptionsToggleFullscreen}" Header="{locale:Locale MenuBarOptionsToggleFullscreen}"
InputGesture="F11" /> Padding="0"
Icon="{icon:Icon fa-solid fa-expand}"
InputGesture="F11">
<MenuItem.Styles>
<Style Selector="Viewbox#PART_IconPresenter">
<Setter Property="MaxHeight" Value="36" />
<Setter Property="MinHeight" Value="36" />
<Setter Property="MaxWidth" Value="36" />
<Setter Property="MinWidth" Value="36" />
</Style>
<Style Selector="ContentPresenter#PART_HeaderPresenter">
<Setter Property="Padding" Value="-10,0,0,0" />
</Style>
</MenuItem.Styles>
</MenuItem>
<MenuItem <MenuItem
Padding="0" Padding="0"
Command="{Binding ToggleStartGamesInFullscreen}" Command="{Binding ToggleStartGamesInFullscreen}"
@ -100,7 +127,7 @@
Command="{Binding ToggleShowConsole}" Command="{Binding ToggleShowConsole}"
Header="{locale:Locale MenuBarOptionsShowConsole}"> Header="{locale:Locale MenuBarOptionsShowConsole}">
<MenuItem.Icon> <MenuItem.Icon>
<CheckBox <CheckBox
MinWidth="{DynamicResource CheckBoxSize}" MinWidth="{DynamicResource CheckBoxSize}"
MinHeight="{DynamicResource CheckBoxSize}" MinHeight="{DynamicResource CheckBoxSize}"
IsChecked="{Binding ShowConsole, Mode=TwoWay}" IsChecked="{Binding ShowConsole, Mode=TwoWay}"
@ -118,11 +145,24 @@
</Style> </Style>
</MenuItem.Styles> </MenuItem.Styles>
</MenuItem> </MenuItem>
<Separator /> <Separator/>
<MenuItem <MenuItem
Name="ChangeLanguageMenuItem" Name="ChangeLanguageMenuItem"
Padding="-10,0,0,0" Padding="0"
Header="{locale:Locale MenuBarOptionsChangeLanguage}" /> Header="{locale:Locale MenuBarOptionsChangeLanguage}"
Icon="{icon:Icon fa-solid fa-language}">
<MenuItem.Styles>
<Style Selector="Viewbox#PART_IconPresenter">
<Setter Property="MaxHeight" Value="36" />
<Setter Property="MinHeight" Value="36" />
<Setter Property="MaxWidth" Value="36" />
<Setter Property="MinWidth" Value="36" />
</Style>
<Style Selector="ContentPresenter#PART_HeaderPresenter">
<Setter Property="Padding" Value="-10,0,0,0" />
</Style>
</MenuItem.Styles>
</MenuItem>
<MenuItem <MenuItem
Name="ToggleFileTypesMenuItem" Name="ToggleFileTypesMenuItem"
Padding="-10,0,0,0" Padding="-10,0,0,0"
@ -130,15 +170,41 @@
<Separator /> <Separator />
<MenuItem <MenuItem
Click="OpenSettings" Click="OpenSettings"
Padding="-10,0,0,0" Padding="0"
Header="{locale:Locale MenuBarOptionsSettings}" Header="{locale:Locale MenuBarOptionsSettings}"
ToolTip.Tip="{locale:Locale OpenSettingsTooltip}" /> Icon="{icon:Icon fa-solid fa-gear}"
ToolTip.Tip="{locale:Locale OpenSettingsTooltip}">
<MenuItem.Styles>
<Style Selector="Viewbox#PART_IconPresenter">
<Setter Property="MaxHeight" Value="36" />
<Setter Property="MinHeight" Value="36" />
<Setter Property="MaxWidth" Value="36" />
<Setter Property="MinWidth" Value="36" />
</Style>
<Style Selector="ContentPresenter#PART_HeaderPresenter">
<Setter Property="Padding" Value="-10,0,0,0" />
</Style>
</MenuItem.Styles>
</MenuItem>
<MenuItem <MenuItem
Command="{Binding ManageProfiles}" Command="{Binding ManageProfiles}"
Padding="-10,0,0,0" Padding="0"
Header="{locale:Locale MenuBarOptionsManageUserProfiles}" Header="{locale:Locale MenuBarOptionsManageUserProfiles}"
Icon="{icon:Icon mdi-account}"
IsEnabled="{Binding EnableNonGameRunningControls}" IsEnabled="{Binding EnableNonGameRunningControls}"
ToolTip.Tip="{locale:Locale OpenProfileManagerTooltip}" /> ToolTip.Tip="{locale:Locale OpenProfileManagerTooltip}">
<MenuItem.Styles>
<Style Selector="Viewbox#PART_IconPresenter">
<Setter Property="MaxHeight" Value="36" />
<Setter Property="MinHeight" Value="36" />
<Setter Property="MaxWidth" Value="36" />
<Setter Property="MinWidth" Value="36" />
</Style>
<Style Selector="ContentPresenter#PART_HeaderPresenter">
<Setter Property="Padding" Value="-10,0,0,0" />
</Style>
</MenuItem.Styles>
</MenuItem>
</MenuItem> </MenuItem>
<MenuItem <MenuItem
Name="ActionsMenuItem" Name="ActionsMenuItem"
@ -148,18 +214,21 @@
<MenuItem <MenuItem
Click="PauseEmulation_Click" Click="PauseEmulation_Click"
Header="{locale:Locale MenuBarOptionsPauseEmulation}" Header="{locale:Locale MenuBarOptionsPauseEmulation}"
Icon="{icon:Icon fa-solid fa-pause}"
InputGesture="{Binding PauseKey}" InputGesture="{Binding PauseKey}"
IsEnabled="{Binding !IsPaused}" IsEnabled="{Binding !IsPaused}"
IsVisible="{Binding !IsPaused}" /> IsVisible="{Binding !IsPaused}" />
<MenuItem <MenuItem
Click="ResumeEmulation_Click" Click="ResumeEmulation_Click"
Header="{locale:Locale MenuBarOptionsResumeEmulation}" Header="{locale:Locale MenuBarOptionsResumeEmulation}"
Icon="{icon:Icon fa-solid fa-play}"
InputGesture="{Binding PauseKey}" InputGesture="{Binding PauseKey}"
IsEnabled="{Binding IsPaused}" IsEnabled="{Binding IsPaused}"
IsVisible="{Binding IsPaused}" /> IsVisible="{Binding IsPaused}" />
<MenuItem <MenuItem
Click="StopEmulation_Click" Click="StopEmulation_Click"
Header="{locale:Locale MenuBarOptionsStopEmulation}" Header="{locale:Locale MenuBarOptionsStopEmulation}"
Icon="{icon:Icon fa-solid fa-stop}"
InputGesture="Escape" InputGesture="Escape"
IsEnabled="{Binding IsGameRunning}" IsEnabled="{Binding IsGameRunning}"
ToolTip.Tip="{locale:Locale StopEmulationTooltip}" /> ToolTip.Tip="{locale:Locale StopEmulationTooltip}" />
@ -170,26 +239,30 @@
AttachedToVisualTree="ScanAmiiboMenuItem_AttachedToVisualTree" AttachedToVisualTree="ScanAmiiboMenuItem_AttachedToVisualTree"
Click="OpenAmiiboWindow" Click="OpenAmiiboWindow"
Header="{locale:Locale MenuBarActionsScanAmiibo}" Header="{locale:Locale MenuBarActionsScanAmiibo}"
Icon="{icon:Icon mdi-cube-scan}"
IsEnabled="{Binding IsAmiiboRequested}" /> IsEnabled="{Binding IsAmiiboRequested}" />
<MenuItem <MenuItem
Command="{Binding TakeScreenshot}" Command="{Binding TakeScreenshot}"
Header="{locale:Locale MenuBarFileToolsTakeScreenshot}" Header="{locale:Locale MenuBarFileToolsTakeScreenshot}"
Icon="{icon:Icon mdi-monitor-screenshot}"
InputGesture="{Binding ScreenshotKey}" InputGesture="{Binding ScreenshotKey}"
IsEnabled="{Binding IsGameRunning}" /> IsEnabled="{Binding IsGameRunning}" />
<MenuItem <MenuItem
Command="{Binding HideUi}" Command="{Binding HideUi}"
Header="{locale:Locale MenuBarFileToolsHideUi}" Header="{locale:Locale MenuBarFileToolsHideUi}"
Icon="{icon:Icon mdi-eye-off}"
InputGesture="{Binding ShowUiKey}" InputGesture="{Binding ShowUiKey}"
IsEnabled="{Binding IsGameRunning}" /> IsEnabled="{Binding IsGameRunning}" />
<MenuItem <MenuItem
Click="OpenCheatManagerForCurrentApp" Click="OpenCheatManagerForCurrentApp"
Header="{locale:Locale GameListContextMenuManageCheat}" Header="{locale:Locale GameListContextMenuManageCheat}"
Icon="{icon:Icon fa-solid fa-code}"
IsEnabled="{Binding IsGameRunning}" /> IsEnabled="{Binding IsGameRunning}" />
</MenuItem> </MenuItem>
<MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarTools}"> <MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarTools}">
<MenuItem Header="{locale:Locale MenuBarToolsInstallFirmware}" IsEnabled="{Binding EnableNonGameRunningControls}"> <MenuItem Header="{locale:Locale MenuBarToolsInstallFirmware}" Icon="{icon:Icon fa-solid fa-download}" IsEnabled="{Binding EnableNonGameRunningControls}">
<MenuItem Command="{Binding InstallFirmwareFromFile}" Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromFile}" /> <MenuItem Command="{Binding InstallFirmwareFromFile}" Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromFile}" Icon="{icon:Icon mdi-file-cog}" />
<MenuItem Command="{Binding InstallFirmwareFromFolder}" Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromDirectory}" /> <MenuItem Command="{Binding InstallFirmwareFromFolder}" Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromDirectory}" Icon="{icon:Icon mdi-folder-cog}" />
</MenuItem> </MenuItem>
<MenuItem Header="{locale:Locale MenuBarToolsManageFileTypes}" IsVisible="{Binding ManageFileTypesVisible}"> <MenuItem Header="{locale:Locale MenuBarToolsManageFileTypes}" IsVisible="{Binding ManageFileTypesVisible}">
<MenuItem Header="{locale:Locale MenuBarToolsInstallFileTypes}" Click="InstallFileTypes_Click"/> <MenuItem Header="{locale:Locale MenuBarToolsInstallFileTypes}" Click="InstallFileTypes_Click"/>
@ -198,8 +271,8 @@
</MenuItem> </MenuItem>
<MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarView}"> <MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarView}">
<MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarViewWindow}"> <MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarViewWindow}">
<MenuItem Header="{locale:Locale MenuBarViewWindow720}" Tag="720" Click="ChangeWindowSize_Click" /> <MenuItem Header="{locale:Locale MenuBarViewWindow720}" Tag="720 1280" Click="ChangeWindowSize_Click" />
<MenuItem Header="{locale:Locale MenuBarViewWindow1080}" Tag="1080" Click="ChangeWindowSize_Click" /> <MenuItem Header="{locale:Locale MenuBarViewWindow1080}" Tag="1080 1920" Click="ChangeWindowSize_Click" />
</MenuItem> </MenuItem>
</MenuItem> </MenuItem>
<MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarHelp}"> <MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarHelp}">
@ -208,11 +281,13 @@
IsEnabled="{Binding CanUpdate}" IsEnabled="{Binding CanUpdate}"
Click="CheckForUpdates" Click="CheckForUpdates"
Header="{locale:Locale MenuBarHelpCheckForUpdates}" Header="{locale:Locale MenuBarHelpCheckForUpdates}"
Icon="{icon:Icon mdi-update}"
ToolTip.Tip="{locale:Locale CheckUpdatesTooltip}" /> ToolTip.Tip="{locale:Locale CheckUpdatesTooltip}" />
<Separator /> <Separator />
<MenuItem <MenuItem
Click="OpenAboutWindow" Click="OpenAboutWindow"
Header="{locale:Locale MenuBarHelpAbout}" Header="{locale:Locale MenuBarHelpAbout}"
Icon="{icon:Icon fa-solid fa-circle-info}"
ToolTip.Tip="{locale:Locale OpenAboutTooltip}" /> ToolTip.Tip="{locale:Locale OpenAboutTooltip}" />
</MenuItem> </MenuItem>
</Menu> </Menu>

View file

@ -2,6 +2,7 @@ using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Threading; using Avalonia.Threading;
using Gommon;
using LibHac.Ncm; using LibHac.Ncm;
using LibHac.Tools.FsSystem.NcaUtils; using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
@ -77,11 +78,9 @@ namespace Ryujinx.Ava.UI.Views.Main
MenuItem menuItem = new() MenuItem menuItem = new()
{ {
Header = languageName, Padding = new Thickness(10, 0, 0, 0),
Command = MiniCommand.Create(() => Header = " " + languageName,
{ Command = MiniCommand.Create(() => MainWindowViewModel.ChangeLanguage(languageCode)),
MainWindowViewModel.ChangeLanguage(languageCode);
}),
}; };
menuItems.Add(menuItem); menuItems.Add(menuItem);
@ -99,23 +98,23 @@ namespace Ryujinx.Ava.UI.Views.Main
Window = window; Window = window;
} }
ViewModel = Window.ViewModel; ViewModel = MainWindow.ViewModel;
DataContext = ViewModel; DataContext = ViewModel;
} }
private async void StopEmulation_Click(object sender, RoutedEventArgs e) private async void StopEmulation_Click(object sender, RoutedEventArgs e)
{ {
await Window.ViewModel.AppHost?.ShowExitPrompt(); await MainWindow.ViewModel.AppHost?.ShowExitPrompt().OrCompleted()!;
} }
private void PauseEmulation_Click(object sender, RoutedEventArgs e) private void PauseEmulation_Click(object sender, RoutedEventArgs e)
{ {
Window.ViewModel.AppHost?.Pause(); MainWindow.ViewModel.AppHost?.Pause();
} }
private void ResumeEmulation_Click(object sender, RoutedEventArgs e) private void ResumeEmulation_Click(object sender, RoutedEventArgs e)
{ {
Window.ViewModel.AppHost?.Resume(); MainWindow.ViewModel.AppHost?.Resume();
} }
public async void OpenSettings(object sender, RoutedEventArgs e) public async void OpenSettings(object sender, RoutedEventArgs e)
@ -149,9 +148,7 @@ namespace Ryujinx.Ava.UI.Views.Main
public async void OpenAmiiboWindow(object sender, RoutedEventArgs e) public async void OpenAmiiboWindow(object sender, RoutedEventArgs e)
{ {
if (!ViewModel.IsAmiiboRequested) if (!ViewModel.IsAmiiboRequested)
{
return; return;
}
if (ViewModel.AppHost.Device.System.SearchingForAmiibo(out int deviceId)) if (ViewModel.AppHost.Device.System.SearchingForAmiibo(out int deviceId))
{ {
@ -173,17 +170,15 @@ namespace Ryujinx.Ava.UI.Views.Main
public async void OpenCheatManagerForCurrentApp(object sender, RoutedEventArgs e) public async void OpenCheatManagerForCurrentApp(object sender, RoutedEventArgs e)
{ {
if (!ViewModel.IsGameRunning) if (!ViewModel.IsGameRunning)
{
return; return;
}
string name = ViewModel.AppHost.Device.Processes.ActiveApplication.ApplicationControlProperties.Title[(int)ViewModel.AppHost.Device.System.State.DesiredTitleLanguage].NameString.ToString(); string name = ViewModel.AppHost.Device.Processes.ActiveApplication.ApplicationControlProperties.Title[(int)ViewModel.AppHost.Device.System.State.DesiredTitleLanguage].NameString.ToString();
await new CheatWindow( await new CheatWindow(
Window.VirtualFileSystem, Window.VirtualFileSystem,
ViewModel.AppHost.Device.Processes.ActiveApplication.ProgramIdText, ViewModel.AppHost.Device.Processes.ActiveApplication.ProgramIdText,
name, name,
Window.ViewModel.SelectedApplication.Path).ShowDialog(Window); MainWindow.ViewModel.SelectedApplication.Path).ShowDialog(Window);
ViewModel.AppHost.Device.EnableCheats(); ViewModel.AppHost.Device.EnableCheats();
} }
@ -191,85 +186,50 @@ namespace Ryujinx.Ava.UI.Views.Main
private void ScanAmiiboMenuItem_AttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e) private void ScanAmiiboMenuItem_AttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e)
{ {
if (sender is MenuItem) if (sender is MenuItem)
{ ViewModel.IsAmiiboRequested = MainWindow.ViewModel.AppHost.Device.System.SearchingForAmiibo(out _);
ViewModel.IsAmiiboRequested = Window.ViewModel.AppHost.Device.System.SearchingForAmiibo(out _);
}
} }
private async void InstallFileTypes_Click(object sender, RoutedEventArgs e) private async void InstallFileTypes_Click(object sender, RoutedEventArgs e)
{ {
if (FileAssociationHelper.Install()) if (FileAssociationHelper.Install())
{
await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogInstallFileTypesSuccessMessage], string.Empty, LocaleManager.Instance[LocaleKeys.InputDialogOk], string.Empty, string.Empty); await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogInstallFileTypesSuccessMessage], string.Empty, LocaleManager.Instance[LocaleKeys.InputDialogOk], string.Empty, string.Empty);
}
else else
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogInstallFileTypesErrorMessage]); await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogInstallFileTypesErrorMessage]);
}
} }
private async void UninstallFileTypes_Click(object sender, RoutedEventArgs e) private async void UninstallFileTypes_Click(object sender, RoutedEventArgs e)
{ {
if (FileAssociationHelper.Uninstall()) if (FileAssociationHelper.Uninstall())
{
await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogUninstallFileTypesSuccessMessage], string.Empty, LocaleManager.Instance[LocaleKeys.InputDialogOk], string.Empty, string.Empty); await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogUninstallFileTypesSuccessMessage], string.Empty, LocaleManager.Instance[LocaleKeys.InputDialogOk], string.Empty, string.Empty);
}
else else
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUninstallFileTypesErrorMessage]); await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUninstallFileTypesErrorMessage]);
}
} }
private async void ChangeWindowSize_Click(object sender, RoutedEventArgs e) private async void ChangeWindowSize_Click(object sender, RoutedEventArgs e)
{ {
if (sender is MenuItem item) if (sender is not MenuItem { Tag: string resolution }) return;
(int height, int width) = resolution.Split(' ')
.Into(parts => (int.Parse(parts[0]), int.Parse(parts[1])));
await Dispatcher.UIThread.InvokeAsync(() =>
{ {
int height; ViewModel.WindowState = WindowState.Normal;
int width;
switch (item.Tag) height += (int)Window.StatusBarHeight + (int)Window.MenuBarHeight;
{
case "720":
height = 720;
width = 1280;
break;
case "1080": Window.Arrange(new Rect(Window.Position.X, Window.Position.Y, width, height));
height = 1080; });
width = 1920;
break;
default:
throw new ArgumentNullException($"Invalid Tag for {item}");
}
await Dispatcher.UIThread.InvokeAsync(() =>
{
ViewModel.WindowState = WindowState.Normal;
height += (int)Window.StatusBarHeight + (int)Window.MenuBarHeight;
Window.Arrange(new Rect(Window.Position.X, Window.Position.Y, width, height));
});
}
} }
public async void CheckForUpdates(object sender, RoutedEventArgs e) public async void CheckForUpdates(object sender, RoutedEventArgs e)
{ {
if (Updater.CanUpdate(true)) if (Updater.CanUpdate(true))
{
await Updater.BeginParse(Window, true); await Updater.BeginParse(Window, true);
}
} }
public async void OpenAboutWindow(object sender, RoutedEventArgs e) public async void OpenAboutWindow(object sender, RoutedEventArgs e) => await AboutWindow.Show();
{
await AboutWindow.Show();
}
public void CloseWindow(object sender, RoutedEventArgs e) public void CloseWindow(object sender, RoutedEventArgs e) => Window.Close();
{
Window.Close();
}
} }
} }

View file

@ -28,14 +28,14 @@ namespace Ryujinx.Ava.UI.Views.Main
Window = window; Window = window;
} }
DataContext = Window.ViewModel; DataContext = MainWindow.ViewModel;
} }
private void VsyncStatus_PointerReleased(object sender, PointerReleasedEventArgs e) private void VsyncStatus_PointerReleased(object sender, PointerReleasedEventArgs e)
{ {
Window.ViewModel.AppHost.ToggleVSync(); MainWindow.ViewModel.AppHost.ToggleVSync();
Logger.Info?.Print(LogClass.Application, $"VSync toggled to: {Window.ViewModel.AppHost.Device.EnableDeviceVsync}"); Logger.Info?.Print(LogClass.Application, $"VSync toggled to: {MainWindow.ViewModel.AppHost.Device.EnableDeviceVsync}");
} }
private void DockedStatus_PointerReleased(object sender, PointerReleasedEventArgs e) private void DockedStatus_PointerReleased(object sender, PointerReleasedEventArgs e)
@ -57,9 +57,9 @@ namespace Ryujinx.Ava.UI.Views.Main
private void VolumeStatus_OnPointerWheelChanged(object sender, PointerWheelEventArgs e) private void VolumeStatus_OnPointerWheelChanged(object sender, PointerWheelEventArgs e)
{ {
// Change the volume by 5% at a time // Change the volume by 5% at a time
float newValue = Window.ViewModel.Volume + (float)e.Delta.Y * 0.05f; float newValue = MainWindow.ViewModel.Volume + (float)e.Delta.Y * 0.05f;
Window.ViewModel.Volume = newValue switch MainWindow.ViewModel.Volume = newValue switch
{ {
< 0 => 0, < 0 => 0,
> 1 => 1, > 1 => 1,

View file

@ -1,4 +1,4 @@
<UserControl <UserControl
xmlns="https://github.com/avaloniaui" xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

View file

@ -22,9 +22,9 @@ namespace Ryujinx.Ava.UI.Views.Main
{ {
base.OnAttachedToVisualTree(e); base.OnAttachedToVisualTree(e);
if (VisualRoot is MainWindow window) if (VisualRoot is MainWindow)
{ {
ViewModel = window.ViewModel; ViewModel = MainWindow.ViewModel;
} }
DataContext = ViewModel; DataContext = ViewModel;
@ -32,18 +32,14 @@ namespace Ryujinx.Ava.UI.Views.Main
public void Sort_Checked(object sender, RoutedEventArgs args) public void Sort_Checked(object sender, RoutedEventArgs args)
{ {
if (sender is RadioButton button) if (sender is RadioButton { Tag: string sortStrategy })
{ ViewModel.Sort(Enum.Parse<ApplicationSort>(sortStrategy));
ViewModel.Sort(Enum.Parse<ApplicationSort>(button.Tag.ToString()));
}
} }
public void Order_Checked(object sender, RoutedEventArgs args) public void Order_Checked(object sender, RoutedEventArgs args)
{ {
if (sender is RadioButton button) if (sender is RadioButton { Tag: string sortOrder })
{ ViewModel.Sort(sortOrder is not "Descending");
ViewModel.Sort(button.Tag.ToString() != "Descending");
}
} }
private void SearchBox_OnKeyUp(object sender, KeyEventArgs e) private void SearchBox_OnKeyUp(object sender, KeyEventArgs e)

View file

@ -6,6 +6,7 @@ using Avalonia.Platform;
using Avalonia.Threading; using Avalonia.Threading;
using DynamicData; using DynamicData;
using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Controls;
using FluentAvalonia.UI.Windowing;
using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem;
using Ryujinx.Ava.Common; using Ryujinx.Ava.Common;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
@ -36,7 +37,9 @@ namespace Ryujinx.Ava.UI.Windows
{ {
public partial class MainWindow : StyleableWindow public partial class MainWindow : StyleableWindow
{ {
internal static MainWindowViewModel MainWindowViewModel { get; private set; } internal static MainWindowViewModel ViewModel { get; private set; }
internal readonly AvaHostUIHandler UiHandler;
private bool _isLoading; private bool _isLoading;
private bool _applicationsLoadedOnce; private bool _applicationsLoadedOnce;
@ -46,7 +49,6 @@ namespace Ryujinx.Ava.UI.Windows
private static string _launchPath; private static string _launchPath;
private static string _launchApplicationId; private static string _launchApplicationId;
private static bool _startFullscreen; private static bool _startFullscreen;
internal readonly AvaHostUIHandler UiHandler;
private IDisposable _appLibraryAppsSubscription; private IDisposable _appLibraryAppsSubscription;
public VirtualFileSystem VirtualFileSystem { get; private set; } public VirtualFileSystem VirtualFileSystem { get; private set; }
@ -57,7 +59,6 @@ namespace Ryujinx.Ava.UI.Windows
public InputManager InputManager { get; private set; } public InputManager InputManager { get; private set; }
internal MainWindowViewModel ViewModel { get; private set; }
public SettingsWindow SettingsWindow { get; set; } public SettingsWindow SettingsWindow { get; set; }
public static bool ShowKeyErrorOnLoad { get; set; } public static bool ShowKeyErrorOnLoad { get; set; }
@ -68,11 +69,7 @@ namespace Ryujinx.Ava.UI.Windows
public MainWindow() public MainWindow()
{ {
ViewModel = new MainWindowViewModel(); DataContext = ViewModel = new MainWindowViewModel();
MainWindowViewModel = ViewModel;
DataContext = ViewModel;
InitializeComponent(); InitializeComponent();
Load(); Load();
@ -81,6 +78,10 @@ namespace Ryujinx.Ava.UI.Windows
ViewModel.Title = App.FormatTitle(); ViewModel.Title = App.FormatTitle();
TitleBar.ExtendsContentIntoTitleBar = true;
TitleBar.TitleBarHitTestType = TitleBarHitTestType.Complex;
// NOTE: Height of MenuBar and StatusBar is not usable here, since it would still be 0 at this point. // NOTE: Height of MenuBar and StatusBar is not usable here, since it would still be 0 at this point.
StatusBarHeight = StatusBarView.StatusBar.MinHeight; StatusBarHeight = StatusBarView.StatusBar.MinHeight;
MenuBarHeight = MenuBar.MinHeight; MenuBarHeight = MenuBar.MinHeight;
@ -105,9 +106,7 @@ namespace Ryujinx.Ava.UI.Windows
private void OnPlatformColorValuesChanged(object sender, PlatformColorValues e) private void OnPlatformColorValuesChanged(object sender, PlatformColorValues e)
{ {
if (Application.Current is App app) if (Application.Current is App app)
{
app.ApplyConfiguredTheme(); app.ApplyConfiguredTheme();
}
} }
protected override void OnClosed(EventArgs e) protected override void OnClosed(EventArgs e)

View file

@ -3,6 +3,7 @@ using Avalonia.Controls.Primitives;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Media.Imaging; using Avalonia.Media.Imaging;
using Avalonia.Platform; using Avalonia.Platform;
using FluentAvalonia.UI.Windowing;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.UI.Common.Configuration; using Ryujinx.UI.Common.Configuration;
using System.IO; using System.IO;
@ -10,21 +11,13 @@ using System.Reflection;
namespace Ryujinx.Ava.UI.Windows namespace Ryujinx.Ava.UI.Windows
{ {
public class StyleableWindow : Window public class StyleableWindow : AppWindow
{ {
public Bitmap IconImage { get; set; }
public StyleableWindow() public StyleableWindow()
{ {
WindowStartupLocation = WindowStartupLocation.CenterOwner; WindowStartupLocation = WindowStartupLocation.CenterOwner;
TransparencyLevelHint = [WindowTransparencyLevel.None]; TransparencyLevelHint = [WindowTransparencyLevel.None];
using Stream stream = Assembly.GetAssembly(typeof(ConfigurationState))!.GetManifestResourceStream("Ryujinx.UI.Common.Resources.Logo_Ryujinx.png")!;
Icon = new WindowIcon(stream);
stream.Position = 0;
IconImage = new Bitmap(stream);
LocaleManager.Instance.LocaleChanged += LocaleChanged; LocaleManager.Instance.LocaleChanged += LocaleChanged;
LocaleChanged(); LocaleChanged();
} }