ryujinx/src/Ryujinx.Ava/UI/Windows/MainWindow.axaml.cs
TSRBerry 6c515e1822
[Ryujinx.Ava] Address dotnet-format issues (#5361)
* dotnet format style --severity info

Some changes were manually reverted.

* dotnet format analyzers --serverity info

Some changes have been minimally adapted.

* Restore a few unused methods and variables

* Silence dotnet format IDE0060 warnings

* Silence dotnet format IDE0052 warnings

* Silence dotnet format IDE0059 warnings

* Address or silence dotnet format IDE1006 warnings

* Address dotnet format CA1816 warnings

* Address dotnet format CA1822 warnings

* Address or silence dotnet format CA1069 warnings

* Make dotnet format succeed in style mode

* Address dotnet format CA1401 warnings

* Address remaining dotnet format analyzer warnings

* Address review comments

* dotnet-format fixes after rebase

* Address most dotnet format whitespace warnings

* Apply dotnet format whitespace formatting

A few of them have been manually reverted and the corresponding warning was silenced

* Format if-blocks correctly

* Another rebase, another dotnet format run

* Run dotnet format whitespace after rebase

* Run dotnet format style after rebase

* Run dotnet format after rebase and remove unused usings

- analyzers
- style
- whitespace

* Add comments to disabled warnings

* Remove a few unused parameters

* Simplify properties and array initialization, Use const when possible, Remove trailing commas

* Start working on disabled warnings

* Fix and silence a few dotnet-format warnings again

* Address IDE0260 warnings

* Address a few disabled IDE0060 warnings

* Silence IDE0060 in .editorconfig

* Revert "Simplify properties and array initialization, Use const when possible, Remove trailing commas"

This reverts commit 9462e4136c0a2100dc28b20cf9542e06790aa67e.

* dotnet format whitespace after rebase

* dotnet format pass with new editorconfig

* Fix naming style issues

* Apply suggestions from code review

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Revert one suggestion

* Second dotnet format pass and fix build issues

* Final pass of dotnet format

* Add trailing commas

* Fix formatting issues

* Keep unnecessary assignment in IconColorPicker.cs

* Use using declarations and extend resource lifetimes

* Fix rebase issues

* Adjust comment spacing

* Fix typo

* Fix naming issues

* Apply suggestions from code review

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Revert unintentional change

* Remove unused file

* Remove static keyword from ViewModels

Binding of static members doesn't work and is silently ignored.

---------

Co-authored-by: Ac_K <Acoustik666@gmail.com>
2023-07-07 23:03:27 +02:00

565 lines
20 KiB
C#

using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Threading;
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Input;
using Ryujinx.Ava.UI.Applet;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.Gpu;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS;
using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.Input.SDL2;
using Ryujinx.Modules;
using Ryujinx.Ui.App.Common;
using Ryujinx.Ui.Common;
using Ryujinx.Ui.Common.Configuration;
using Ryujinx.Ui.Common.Helper;
using System;
using System.ComponentModel;
using System.IO;
using System.Runtime.Versioning;
using System.Threading.Tasks;
using InputManager = Ryujinx.Input.HLE.InputManager;
namespace Ryujinx.Ava.UI.Windows
{
public partial class MainWindow : StyleableWindow
{
internal static MainWindowViewModel MainWindowViewModel { get; private set; }
private bool _isLoading;
private UserChannelPersistence _userChannelPersistence;
private static bool _deferLoad;
private static string _launchPath;
private static bool _startFullscreen;
internal readonly AvaHostUiHandler UiHandler;
public VirtualFileSystem VirtualFileSystem { get; private set; }
public ContentManager ContentManager { get; private set; }
public AccountManager AccountManager { get; private set; }
public LibHacHorizonManager LibHacHorizonManager { get; private set; }
public InputManager InputManager { get; private set; }
internal MainWindowViewModel ViewModel { get; private set; }
public SettingsWindow SettingsWindow { get; set; }
public static bool ShowKeyErrorOnLoad { get; set; }
public ApplicationLibrary ApplicationLibrary { get; set; }
public MainWindow()
{
ViewModel = new MainWindowViewModel();
MainWindowViewModel = ViewModel;
DataContext = ViewModel;
SetWindowSizePosition();
InitializeComponent();
Load();
UiHandler = new AvaHostUiHandler(this);
ViewModel.Title = $"Ryujinx {Program.Version}";
// NOTE: Height of MenuBar and StatusBar is not usable here, since it would still be 0 at this point.
double barHeight = MenuBar.MinHeight + StatusBarView.StatusBar.MinHeight;
Height = ((Height - barHeight) / Program.WindowScaleFactor) + barHeight;
Width /= Program.WindowScaleFactor;
if (Program.PreviewerDetached)
{
Initialize();
InputManager = new InputManager(new AvaloniaKeyboardDriver(this), new SDL2GamepadDriver());
ViewModel.Initialize(
ContentManager,
ApplicationLibrary,
VirtualFileSystem,
AccountManager,
InputManager,
_userChannelPersistence,
LibHacHorizonManager,
UiHandler,
ShowLoading,
SwitchToGameControl,
SetMainContent,
this);
ViewModel.RefreshFirmwareStatus();
LoadGameList();
this.GetObservable(IsActiveProperty).Subscribe(IsActiveChanged);
}
ApplicationLibrary.ApplicationCountUpdated += ApplicationLibrary_ApplicationCountUpdated;
ApplicationLibrary.ApplicationAdded += ApplicationLibrary_ApplicationAdded;
ViewModel.ReloadGameList += ReloadGameList;
NotificationHelper.SetNotificationManager(this);
}
private void IsActiveChanged(bool obj)
{
ViewModel.IsActive = obj;
}
public void LoadGameList()
{
if (_isLoading)
{
return;
}
_isLoading = true;
LoadApplications();
_isLoading = false;
}
protected override void HandleScalingChanged(double scale)
{
Program.DesktopScaleFactor = scale;
base.HandleScalingChanged(scale);
}
public void AddApplication(ApplicationData applicationData)
{
Dispatcher.UIThread.InvokeAsync(() =>
{
ViewModel.Applications.Add(applicationData);
});
}
private void ApplicationLibrary_ApplicationAdded(object sender, ApplicationAddedEventArgs e)
{
AddApplication(e.AppData);
}
private void ApplicationLibrary_ApplicationCountUpdated(object sender, ApplicationCountUpdatedEventArgs e)
{
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarGamesLoaded, e.NumAppsLoaded, e.NumAppsFound);
Dispatcher.UIThread.Post(() =>
{
ViewModel.StatusBarProgressValue = e.NumAppsLoaded;
ViewModel.StatusBarProgressMaximum = e.NumAppsFound;
if (e.NumAppsFound == 0)
{
StatusBarView.LoadProgressBar.IsVisible = false;
}
if (e.NumAppsLoaded == e.NumAppsFound)
{
StatusBarView.LoadProgressBar.IsVisible = false;
}
});
}
public void Application_Opened(object sender, ApplicationOpenedEventArgs args)
{
if (args.Application != null)
{
ViewModel.SelectedIcon = args.Application.Icon;
string path = new FileInfo(args.Application.Path).FullName;
ViewModel.LoadApplication(path);
}
args.Handled = true;
}
internal static void DeferLoadApplication(string launchPathArg, bool startFullscreenArg)
{
_deferLoad = true;
_launchPath = launchPathArg;
_startFullscreen = startFullscreenArg;
}
public void SwitchToGameControl(bool startFullscreen = false)
{
ViewModel.ShowLoadProgress = false;
ViewModel.ShowContent = true;
ViewModel.IsLoadingIndeterminate = false;
Dispatcher.UIThread.InvokeAsync(() =>
{
if (startFullscreen && ViewModel.WindowState != WindowState.FullScreen)
{
ViewModel.ToggleFullscreen();
}
});
}
public void ShowLoading(bool startFullscreen = false)
{
ViewModel.ShowContent = false;
ViewModel.ShowLoadProgress = true;
ViewModel.IsLoadingIndeterminate = true;
Dispatcher.UIThread.InvokeAsync(() =>
{
if (startFullscreen && ViewModel.WindowState != WindowState.FullScreen)
{
ViewModel.ToggleFullscreen();
}
});
}
protected override void HandleWindowStateChanged(WindowState state)
{
ViewModel.WindowState = state;
if (state != WindowState.Minimized)
{
Renderer.Start();
}
}
private void Initialize()
{
_userChannelPersistence = new UserChannelPersistence();
VirtualFileSystem = VirtualFileSystem.CreateInstance();
LibHacHorizonManager = new LibHacHorizonManager();
ContentManager = new ContentManager(VirtualFileSystem);
LibHacHorizonManager.InitializeFsServer(VirtualFileSystem);
LibHacHorizonManager.InitializeArpServer();
LibHacHorizonManager.InitializeBcatServer();
LibHacHorizonManager.InitializeSystemClients();
ApplicationLibrary = new ApplicationLibrary(VirtualFileSystem);
// Save data created before we supported extra data in directory save data will not work properly if
// given empty extra data. Luckily some of that extra data can be created using the data from the
// save data indexer, which should be enough to check access permissions for user saves.
// Every single save data's extra data will be checked and fixed if needed each time the emulator is opened.
// Consider removing this at some point in the future when we don't need to worry about old saves.
VirtualFileSystem.FixExtraData(LibHacHorizonManager.RyujinxClient);
AccountManager = new AccountManager(LibHacHorizonManager.RyujinxClient, CommandLineState.Profile);
VirtualFileSystem.ReloadKeySet();
ApplicationHelper.Initialize(VirtualFileSystem, AccountManager, LibHacHorizonManager.RyujinxClient, this);
}
[SupportedOSPlatform("linux")]
private static async void ShowVmMaxMapCountWarning()
{
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LinuxVmMaxMapCountWarningTextSecondary,
LinuxHelper.VmMaxMapCount, LinuxHelper.RecommendedVmMaxMapCount);
await ContentDialogHelper.CreateWarningDialog(
LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountWarningTextPrimary],
LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountWarningTextSecondary]
);
}
[SupportedOSPlatform("linux")]
private static async void ShowVmMaxMapCountDialog()
{
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LinuxVmMaxMapCountDialogTextPrimary,
LinuxHelper.RecommendedVmMaxMapCount);
UserResult response = await ContentDialogHelper.ShowTextDialog(
$"Ryujinx - {LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountDialogTitle]}",
LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountDialogTextPrimary],
LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountDialogTextSecondary],
LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountDialogButtonUntilRestart],
LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountDialogButtonPersistent],
LocaleManager.Instance[LocaleKeys.InputDialogNo],
(int)Symbol.Help
);
int rc;
switch (response)
{
case UserResult.Ok:
rc = LinuxHelper.RunPkExec($"echo {LinuxHelper.RecommendedVmMaxMapCount} > {LinuxHelper.VmMaxMapCountPath}");
if (rc == 0)
{
Logger.Info?.Print(LogClass.Application, $"vm.max_map_count set to {LinuxHelper.VmMaxMapCount} until the next restart.");
}
else
{
Logger.Error?.Print(LogClass.Application, $"Unable to change vm.max_map_count. Process exited with code: {rc}");
}
break;
case UserResult.No:
rc = LinuxHelper.RunPkExec($"echo \"vm.max_map_count = {LinuxHelper.RecommendedVmMaxMapCount}\" > {LinuxHelper.SysCtlConfigPath} && sysctl -p {LinuxHelper.SysCtlConfigPath}");
if (rc == 0)
{
Logger.Info?.Print(LogClass.Application, $"vm.max_map_count set to {LinuxHelper.VmMaxMapCount}. Written to config: {LinuxHelper.SysCtlConfigPath}");
}
else
{
Logger.Error?.Print(LogClass.Application, $"Unable to write new value for vm.max_map_count to config. Process exited with code: {rc}");
}
break;
}
}
private void CheckLaunchState()
{
if (ShowKeyErrorOnLoad)
{
ShowKeyErrorOnLoad = false;
Dispatcher.UIThread.Post(async () => await
UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys));
}
if (OperatingSystem.IsLinux() && LinuxHelper.VmMaxMapCount < LinuxHelper.RecommendedVmMaxMapCount)
{
Logger.Warning?.Print(LogClass.Application, $"The value of vm.max_map_count is lower than {LinuxHelper.RecommendedVmMaxMapCount}. ({LinuxHelper.VmMaxMapCount})");
if (LinuxHelper.PkExecPath is not null)
{
Dispatcher.UIThread.Post(ShowVmMaxMapCountDialog);
}
else
{
Dispatcher.UIThread.Post(ShowVmMaxMapCountWarning);
}
}
if (_deferLoad)
{
_deferLoad = false;
ViewModel.LoadApplication(_launchPath, _startFullscreen);
}
if (ConfigurationState.Instance.CheckUpdatesOnStart.Value && Updater.CanUpdate(false))
{
Updater.BeginParse(this, false).ContinueWith(task =>
{
Logger.Error?.Print(LogClass.Application, $"Updater Error: {task.Exception}");
}, TaskContinuationOptions.OnlyOnFaulted);
}
}
private void Load()
{
StatusBarView.VolumeStatus.Click += VolumeStatus_CheckedChanged;
ApplicationGrid.ApplicationOpened += Application_Opened;
ApplicationGrid.DataContext = ViewModel;
ApplicationList.ApplicationOpened += Application_Opened;
ApplicationList.DataContext = ViewModel;
LoadHotKeys();
}
private void SetWindowSizePosition()
{
PixelPoint savedPoint = new(ConfigurationState.Instance.Ui.WindowStartup.WindowPositionX,
ConfigurationState.Instance.Ui.WindowStartup.WindowPositionY);
ViewModel.WindowHeight = ConfigurationState.Instance.Ui.WindowStartup.WindowSizeHeight * Program.WindowScaleFactor;
ViewModel.WindowWidth = ConfigurationState.Instance.Ui.WindowStartup.WindowSizeWidth * Program.WindowScaleFactor;
ViewModel.WindowState = ConfigurationState.Instance.Ui.WindowStartup.WindowMaximized.Value is true ? WindowState.Maximized : WindowState.Normal;
if (CheckScreenBounds(savedPoint))
{
Position = savedPoint;
}
else
{
WindowStartupLocation = WindowStartupLocation.CenterScreen;
}
}
private bool CheckScreenBounds(PixelPoint configPoint)
{
for (int i = 0; i < Screens.ScreenCount; i++)
{
if (Screens.All[i].Bounds.Contains(configPoint))
{
return true;
}
}
Logger.Warning?.Print(LogClass.Application, "Failed to find valid start-up coordinates. Defaulting to primary monitor center.");
return false;
}
private void SaveWindowSizePosition()
{
ConfigurationState.Instance.Ui.WindowStartup.WindowSizeHeight.Value = (int)Height;
ConfigurationState.Instance.Ui.WindowStartup.WindowSizeWidth.Value = (int)Width;
ConfigurationState.Instance.Ui.WindowStartup.WindowPositionX.Value = Position.X;
ConfigurationState.Instance.Ui.WindowStartup.WindowPositionY.Value = Position.Y;
ConfigurationState.Instance.Ui.WindowStartup.WindowMaximized.Value = WindowState == WindowState.Maximized;
MainWindowViewModel.SaveConfig();
}
protected override void OnOpened(EventArgs e)
{
base.OnOpened(e);
CheckLaunchState();
}
private void SetMainContent(Control content = null)
{
content ??= GameLibrary;
if (MainContent.Content != content)
{
MainContent.Content = content;
}
}
public static void UpdateGraphicsConfig()
{
#pragma warning disable IDE0055 // Disable formatting
GraphicsConfig.ResScale = ConfigurationState.Instance.Graphics.ResScale == -1 ? ConfigurationState.Instance.Graphics.ResScaleCustom : ConfigurationState.Instance.Graphics.ResScale;
GraphicsConfig.MaxAnisotropy = ConfigurationState.Instance.Graphics.MaxAnisotropy;
GraphicsConfig.ShadersDumpPath = ConfigurationState.Instance.Graphics.ShadersDumpPath;
GraphicsConfig.EnableShaderCache = ConfigurationState.Instance.Graphics.EnableShaderCache;
GraphicsConfig.EnableTextureRecompression = ConfigurationState.Instance.Graphics.EnableTextureRecompression;
GraphicsConfig.EnableMacroHLE = ConfigurationState.Instance.Graphics.EnableMacroHLE;
#pragma warning restore IDE0055
}
public void LoadHotKeys()
{
#pragma warning disable IDE0055 // Disable formatting
HotKeyManager.SetHotKey(FullscreenHotKey, new KeyGesture(Key.Enter, KeyModifiers.Alt));
HotKeyManager.SetHotKey(FullscreenHotKey2, new KeyGesture(Key.F11));
HotKeyManager.SetHotKey(FullscreenHotKeyMacOS, new KeyGesture(Key.F, KeyModifiers.Control | KeyModifiers.Meta));
HotKeyManager.SetHotKey(DockToggleHotKey, new KeyGesture(Key.F9));
HotKeyManager.SetHotKey(ExitHotKey, new KeyGesture(Key.Escape));
#pragma warning restore IDE0055
}
private void VolumeStatus_CheckedChanged(object sender, SplitButtonClickEventArgs e)
{
var volumeSplitButton = sender as ToggleSplitButton;
if (ViewModel.IsGameRunning)
{
if (!volumeSplitButton.IsChecked)
{
ViewModel.AppHost.Device.SetVolume(ConfigurationState.Instance.System.AudioVolume);
}
else
{
ViewModel.AppHost.Device.SetVolume(0);
}
ViewModel.Volume = ViewModel.AppHost.Device.GetVolume();
}
}
protected override void OnClosing(CancelEventArgs e)
{
if (!ViewModel.IsClosing && ViewModel.AppHost != null && ConfigurationState.Instance.ShowConfirmExit)
{
e.Cancel = true;
ConfirmExit();
return;
}
ViewModel.IsClosing = true;
if (ViewModel.AppHost != null)
{
ViewModel.AppHost.AppExit -= ViewModel.AppHost_AppExit;
ViewModel.AppHost.AppExit += (sender, e) =>
{
ViewModel.AppHost = null;
Dispatcher.UIThread.Post(() =>
{
MainContent = null;
Close();
});
};
ViewModel.AppHost?.Stop();
e.Cancel = true;
return;
}
SaveWindowSizePosition();
ApplicationLibrary.CancelLoading();
InputManager.Dispose();
Program.Exit();
base.OnClosing(e);
}
private void ConfirmExit()
{
Dispatcher.UIThread.InvokeAsync(async () =>
{
ViewModel.IsClosing = await ContentDialogHelper.CreateExitDialog();
if (ViewModel.IsClosing)
{
Close();
}
});
}
public async void LoadApplications()
{
await Dispatcher.UIThread.InvokeAsync(() =>
{
ViewModel.Applications.Clear();
StatusBarView.LoadProgressBar.IsVisible = true;
ViewModel.StatusBarProgressMaximum = 0;
ViewModel.StatusBarProgressValue = 0;
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarGamesLoaded, 0, 0);
});
ReloadGameList();
}
private void ReloadGameList()
{
if (_isLoading)
{
return;
}
_isLoading = true;
ApplicationLibrary.LoadApplications(ConfigurationState.Instance.Ui.GameDirs.Value, ConfigurationState.Instance.System.Language);
_isLoading = false;
}
}
}