ryujinx/Ryujinx/Ui/Windows/UserProfilesManagerWindow.cs
jhorv 5131b71437
Reducing memory allocations (#4537)
* add RecyclableMemoryStream dependency and MemoryStreamManager

* organize BinaryReader/BinaryWriter extensions

* add StreamExtensions to reduce need for BinaryWriter

* simple replacments of MemoryStream with RecyclableMemoryStream

* add write ReadOnlySequence<byte> support to IVirtualMemoryManager

* avoid 0-length array creation

* rework IpcMessage and related types to greatly reduce memory allocation by using RecylableMemoryStream, keeping streams around longer, avoiding their creation when possible, and avoiding creation of BinaryReader and BinaryWriter when possible

* reduce LINQ-induced memory allocations with custom methods to query KPriorityQueue

* use RecyclableMemoryStream in StreamUtils, and use StreamUtils in EmbeddedResources

* add constants for nanosecond/millisecond conversions

* code formatting

* XML doc adjustments

* fix: StreamExtension.WriteByte not writing non-zero values for lengths <= 16

* XML Doc improvements. Implement StreamExtensions.WriteByte() block writes for large-enough count values.

* add copyless path for StreamExtension.Write(ReadOnlySpan<int>)

* add default implementation of IVirtualMemoryManager.Write(ulong, ReadOnlySequence<byte>); remove previous explicit implementations

* code style fixes

* remove LINQ completely from KScheduler/KPriorityQueue by implementing a custom struct-based enumerator
2023-03-17 13:14:50 +01:00

331 lines
11 KiB
C#

using Gtk;
using Ryujinx.Common.Memory;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.Ui.Common.Configuration;
using Ryujinx.Ui.Widgets;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Processing;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Image = SixLabors.ImageSharp.Image;
using UserId = Ryujinx.HLE.HOS.Services.Account.Acc.UserId;
namespace Ryujinx.Ui.Windows
{
public partial class UserProfilesManagerWindow : Window
{
private const uint MaxProfileNameLength = 0x20;
private readonly AccountManager _accountManager;
private readonly ContentManager _contentManager;
private byte[] _bufferImageProfile;
private string _tempNewProfileName;
private Gdk.RGBA _selectedColor;
private ManualResetEvent _avatarsPreloadingEvent = new ManualResetEvent(false);
public UserProfilesManagerWindow(AccountManager accountManager, ContentManager contentManager, VirtualFileSystem virtualFileSystem) : base($"Ryujinx {Program.Version} - Manage User Profiles")
{
Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png");
InitializeComponent();
_selectedColor.Red = 0.212;
_selectedColor.Green = 0.843;
_selectedColor.Blue = 0.718;
_selectedColor.Alpha = 1;
_accountManager = accountManager;
_contentManager = contentManager;
CellRendererToggle userSelectedToggle = new CellRendererToggle();
userSelectedToggle.Toggled += UserSelectedToggle_Toggled;
// NOTE: Uncomment following line when multiple selection of user profiles is supported.
//_usersTreeView.AppendColumn("Selected", userSelectedToggle, "active", 0);
_usersTreeView.AppendColumn("User Icon", new CellRendererPixbuf(), "pixbuf", 1);
_usersTreeView.AppendColumn("User Info", new CellRendererText(), "text", 2, "background-rgba", 3);
_tableStore.SetSortColumnId(0, SortType.Descending);
RefreshList();
if (_contentManager.GetCurrentFirmwareVersion() != null)
{
Task.Run(() =>
{
AvatarWindow.PreloadAvatars(contentManager, virtualFileSystem);
_avatarsPreloadingEvent.Set();
});
}
}
public void RefreshList()
{
_tableStore.Clear();
foreach (UserProfile userProfile in _accountManager.GetAllUsers())
{
_tableStore.AppendValues(userProfile.AccountState == AccountState.Open, new Gdk.Pixbuf(userProfile.Image, 96, 96), $"{userProfile.Name}\n{userProfile.UserId}", Gdk.RGBA.Zero);
if (userProfile.AccountState == AccountState.Open)
{
_selectedUserImage.Pixbuf = new Gdk.Pixbuf(userProfile.Image, 96, 96);
_selectedUserIdLabel.Text = userProfile.UserId.ToString();
_selectedUserNameEntry.Text = userProfile.Name;
_deleteButton.Sensitive = userProfile.UserId != AccountManager.DefaultUserId;
_usersTreeView.Model.GetIterFirst(out TreeIter firstIter);
_tableStore.SetValue(firstIter, 3, _selectedColor);
}
}
}
//
// Events
//
private void UsersTreeView_Activated(object o, RowActivatedArgs args)
{
SelectUserTreeView();
}
private void UserSelectedToggle_Toggled(object o, ToggledArgs args)
{
SelectUserTreeView();
}
private void SelectUserTreeView()
{
// Get selected item informations.
_usersTreeView.Selection.GetSelected(out TreeIter selectedIter);
Gdk.Pixbuf userPicture = (Gdk.Pixbuf)_tableStore.GetValue(selectedIter, 1);
string userName = _tableStore.GetValue(selectedIter, 2).ToString().Split("\n")[0];
string userId = _tableStore.GetValue(selectedIter, 2).ToString().Split("\n")[1];
// Unselect the first user.
_usersTreeView.Model.GetIterFirst(out TreeIter firstIter);
_tableStore.SetValue(firstIter, 0, false);
_tableStore.SetValue(firstIter, 3, Gdk.RGBA.Zero);
// Set new informations.
_tableStore.SetValue(selectedIter, 0, true);
_selectedUserImage.Pixbuf = userPicture;
_selectedUserNameEntry.Text = userName;
_selectedUserIdLabel.Text = userId;
_saveProfileNameButton.Sensitive = false;
// Open the selected one.
_accountManager.OpenUser(new UserId(userId));
_deleteButton.Sensitive = userId != AccountManager.DefaultUserId.ToString();
_tableStore.SetValue(selectedIter, 3, _selectedColor);
}
private void SelectedUserNameEntry_KeyReleaseEvent(object o, KeyReleaseEventArgs args)
{
if (_saveProfileNameButton.Sensitive == false)
{
_saveProfileNameButton.Sensitive = true;
}
}
private void AddButton_Pressed(object sender, EventArgs e)
{
_tempNewProfileName = GtkDialog.CreateInputDialog(this, "Choose the Profile Name", "Please Enter a Profile Name", MaxProfileNameLength);
if (_tempNewProfileName != "")
{
SelectProfileImage(true);
if (_bufferImageProfile != null)
{
AddUser();
}
}
}
private void DeleteButton_Pressed(object sender, EventArgs e)
{
if (GtkDialog.CreateChoiceDialog("Delete User Profile", "Are you sure you want to delete the profile ?", "Deleting this profile will also delete all associated save data."))
{
_accountManager.DeleteUser(GetSelectedUserId());
RefreshList();
}
}
private void EditProfileNameButton_Pressed(object sender, EventArgs e)
{
_saveProfileNameButton.Sensitive = false;
_accountManager.SetUserName(GetSelectedUserId(), _selectedUserNameEntry.Text);
RefreshList();
}
private void ProcessProfileImage(byte[] buffer)
{
using (Image image = Image.Load(buffer))
{
image.Mutate(x => x.Resize(256, 256));
using (MemoryStream streamJpg = MemoryStreamManager.Shared.GetStream())
{
image.SaveAsJpeg(streamJpg);
_bufferImageProfile = streamJpg.ToArray();
}
}
}
private void ProfileImageFileChooser()
{
FileChooserNative fileChooser = new FileChooserNative("Import Custom Profile Image", this, FileChooserAction.Open, "Import", "Cancel")
{
SelectMultiple = false
};
FileFilter filter = new FileFilter()
{
Name = "Custom Profile Images"
};
filter.AddPattern("*.jpg");
filter.AddPattern("*.jpeg");
filter.AddPattern("*.png");
filter.AddPattern("*.bmp");
fileChooser.AddFilter(filter);
if (fileChooser.Run() == (int)ResponseType.Accept)
{
ProcessProfileImage(File.ReadAllBytes(fileChooser.Filename));
}
fileChooser.Dispose();
}
private void SelectProfileImage(bool newUser = false)
{
if (_contentManager.GetCurrentFirmwareVersion() == null)
{
ProfileImageFileChooser();
}
else
{
Dictionary<int, string> buttons = new Dictionary<int, string>()
{
{ 0, "Import Image File" },
{ 1, "Select Firmware Avatar" }
};
ResponseType responseDialog = GtkDialog.CreateCustomDialog("Profile Image Selection",
"Choose a Profile Image",
"You may import a custom profile image, or select an avatar from the system firmware.",
buttons, MessageType.Question);
if (responseDialog == 0)
{
ProfileImageFileChooser();
}
else if (responseDialog == (ResponseType)1)
{
AvatarWindow avatarWindow = new AvatarWindow()
{
NewUser = newUser
};
avatarWindow.DeleteEvent += AvatarWindow_DeleteEvent;
avatarWindow.SetSizeRequest((int)(avatarWindow.DefaultWidth * Program.WindowScaleFactor), (int)(avatarWindow.DefaultHeight * Program.WindowScaleFactor));
avatarWindow.Show();
}
}
}
private void ChangeProfileImageButton_Pressed(object sender, EventArgs e)
{
if (_contentManager.GetCurrentFirmwareVersion() != null)
{
_avatarsPreloadingEvent.WaitOne();
}
SelectProfileImage();
if (_bufferImageProfile != null)
{
SetUserImage();
}
}
private void AvatarWindow_DeleteEvent(object sender, DeleteEventArgs args)
{
_bufferImageProfile = ((AvatarWindow)sender).SelectedProfileImage;
if (_bufferImageProfile != null)
{
if (((AvatarWindow)sender).NewUser)
{
AddUser();
}
else
{
SetUserImage();
}
}
}
private void AddUser()
{
_accountManager.AddUser(_tempNewProfileName, _bufferImageProfile);
_bufferImageProfile = null;
_tempNewProfileName = "";
RefreshList();
}
private void SetUserImage()
{
_accountManager.SetUserImage(GetSelectedUserId(), _bufferImageProfile);
_bufferImageProfile = null;
RefreshList();
}
private UserId GetSelectedUserId()
{
if (_usersTreeView.Model.GetIterFirst(out TreeIter iter))
{
do
{
if ((bool)_tableStore.GetValue(iter, 0))
{
break;
}
}
while (_usersTreeView.Model.IterNext(ref iter));
}
return new UserId(_tableStore.GetValue(iter, 2).ToString().Split("\n")[1]);
}
private void CloseButton_Pressed(object sender, EventArgs e)
{
Close();
}
}
}