From 52269964b60c0e9cee85b4ca3d658cefe451cabb Mon Sep 17 00:00:00 2001 From: Jacob <38381609+Jacobwasbeast@users.noreply.github.com> Date: Sat, 18 Jan 2025 20:40:33 -0600 Subject: [PATCH] Add the player select applet. (#537) This introduces the somewhat completed version of the Player Select Applet, allowing users to select either a user or a guest from the UI. Note: Selecting the guest more then once currently does not work. closes https://github.com/Ryubing/Ryujinx/issues/532 --- .../PlayerSelect/PlayerSelectApplet.cs | 44 +++++-- .../Services/Account/Acc/GuestUserImage.jpg | Bin 0 -> 8021 bytes src/Ryujinx.HLE/Ryujinx.HLE.csproj | 1 + src/Ryujinx.HLE/UI/IHostUIHandler.cs | 7 + src/Ryujinx/Headless/Windows/WindowBase.cs | 7 + src/Ryujinx/Ryujinx.csproj | 6 + src/Ryujinx/UI/Applet/AvaHostUIHandler.cs | 61 +++++++++ .../UI/Applet/UserSelectorDialog.axaml | 121 +++++++++++++++++ .../UI/Applet/UserSelectorDialog.axaml.cs | 123 ++++++++++++++++++ .../ViewModels/UserSelectorDialogViewModel.cs | 14 ++ 10 files changed, 376 insertions(+), 8 deletions(-) create mode 100644 src/Ryujinx.HLE/HOS/Services/Account/Acc/GuestUserImage.jpg create mode 100644 src/Ryujinx/UI/Applet/UserSelectorDialog.axaml create mode 100644 src/Ryujinx/UI/Applet/UserSelectorDialog.axaml.cs create mode 100644 src/Ryujinx/UI/ViewModels/UserSelectorDialogViewModel.cs diff --git a/src/Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectApplet.cs b/src/Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectApplet.cs index 05bddc76f..cf99b0e7a 100644 --- a/src/Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectApplet.cs +++ b/src/Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectApplet.cs @@ -26,10 +26,20 @@ namespace Ryujinx.HLE.HOS.Applets { _normalSession = normalSession; _interactiveSession = interactiveSession; - - // TODO(jduncanator): Parse PlayerSelectConfig from input data - _normalSession.Push(BuildResponse()); - + + UserProfile selected = _system.Device.UIHandler.ShowPlayerSelectDialog(); + if (selected == null) + { + _normalSession.Push(BuildResponse()); + } + else if (selected.UserId == new UserId("00000000000000000000000000000080")) + { + _normalSession.Push(BuildGuestResponse()); + } + else + { + _normalSession.Push(BuildResponse(selected)); + } AppletStateChanged?.Invoke(this, null); _system.ReturnFocus(); @@ -37,16 +47,34 @@ namespace Ryujinx.HLE.HOS.Applets return ResultCode.Success; } - private byte[] BuildResponse() + private byte[] BuildResponse(UserProfile selectedUser) { - UserProfile currentUser = _system.AccountManager.LastOpenedUser; - using MemoryStream stream = MemoryStreamManager.Shared.GetStream(); using BinaryWriter writer = new(stream); writer.Write((ulong)PlayerSelectResult.Success); - currentUser.UserId.Write(writer); + selectedUser.UserId.Write(writer); + + return stream.ToArray(); + } + + private byte[] BuildGuestResponse() + { + using MemoryStream stream = MemoryStreamManager.Shared.GetStream(); + using BinaryWriter writer = new(stream); + + writer.Write(new byte()); + + return stream.ToArray(); + } + + private byte[] BuildResponse() + { + using MemoryStream stream = MemoryStreamManager.Shared.GetStream(); + using BinaryWriter writer = new(stream); + + writer.Write((ulong)PlayerSelectResult.Failure); return stream.ToArray(); } diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/GuestUserImage.jpg b/src/Ryujinx.HLE/HOS/Services/Account/Acc/GuestUserImage.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8310994de5167a3b6696849a127226e0a14b5159 GIT binary patch literal 8021 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 + diff --git a/src/Ryujinx.HLE/UI/IHostUIHandler.cs b/src/Ryujinx.HLE/UI/IHostUIHandler.cs index 88af83735..8ccb5cf89 100644 --- a/src/Ryujinx.HLE/UI/IHostUIHandler.cs +++ b/src/Ryujinx.HLE/UI/IHostUIHandler.cs @@ -1,4 +1,5 @@ using Ryujinx.HLE.HOS.Applets; +using Ryujinx.HLE.HOS.Services.Account.Acc; using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types; namespace Ryujinx.HLE.UI @@ -59,5 +60,11 @@ namespace Ryujinx.HLE.UI /// Gets fonts and colors used by the host. /// IHostUITheme HostUITheme { get; } + + + /// + /// Displays the player select dialog and returns the selected profile. + /// + UserProfile ShowPlayerSelectDialog(); } } diff --git a/src/Ryujinx/Headless/Windows/WindowBase.cs b/src/Ryujinx/Headless/Windows/WindowBase.cs index 7017d1f59..068c32062 100644 --- a/src/Ryujinx/Headless/Windows/WindowBase.cs +++ b/src/Ryujinx/Headless/Windows/WindowBase.cs @@ -9,6 +9,7 @@ using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL.Multithreading; using Ryujinx.Graphics.OpenGL; using Ryujinx.HLE.HOS.Applets; +using Ryujinx.HLE.HOS.Services.Account.Acc; using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types; using Ryujinx.HLE.UI; using Ryujinx.Input; @@ -26,6 +27,7 @@ using static SDL2.SDL; using AntiAliasing = Ryujinx.Common.Configuration.AntiAliasing; using ScalingFilter = Ryujinx.Common.Configuration.ScalingFilter; using Switch = Ryujinx.HLE.Switch; +using UserProfile = Ryujinx.HLE.HOS.Services.Account.Acc.UserProfile; namespace Ryujinx.Headless { @@ -555,5 +557,10 @@ namespace Ryujinx.Headless SDL2Driver.Instance.Dispose(); } } + + public UserProfile ShowPlayerSelectDialog() + { + return AccountSaveDataManager.GetLastUsedUser(); + } } } diff --git a/src/Ryujinx/Ryujinx.csproj b/src/Ryujinx/Ryujinx.csproj index 8929ba3eb..8c72d5a3c 100644 --- a/src/Ryujinx/Ryujinx.csproj +++ b/src/Ryujinx/Ryujinx.csproj @@ -173,4 +173,10 @@ + + + UserSelectorDialog.axaml + Code + + diff --git a/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs b/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs index 65f4c7795..2c63f6af0 100644 --- a/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs +++ b/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs @@ -1,17 +1,24 @@ using Avalonia.Controls; using Avalonia.Threading; using FluentAvalonia.UI.Controls; +using Gommon; using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.UI.Controls; using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.ViewModels; +using Ryujinx.Ava.UI.ViewModels.Input; using Ryujinx.Ava.UI.Windows; using Ryujinx.Ava.Utilities.Configuration; +using Ryujinx.Common; using Ryujinx.HLE; using Ryujinx.HLE.HOS.Applets; using Ryujinx.HLE.HOS.Applets.SoftwareKeyboard; +using Ryujinx.HLE.HOS.Services.Account.Acc; using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types; using Ryujinx.HLE.UI; using System; +using System.Collections.ObjectModel; +using System.Linq; using System.Threading; namespace Ryujinx.Ava.UI.Applet @@ -253,5 +260,59 @@ namespace Ryujinx.Ava.UI.Applet } public IDynamicTextInputHandler CreateDynamicTextInputHandler() => new AvaloniaDynamicTextInputHandler(_parent); + + public UserProfile ShowPlayerSelectDialog() + { + UserId selected = UserId.Null; + byte[] defaultGuestImage = EmbeddedResources.Read("Ryujinx.HLE/HOS/Services/Account/Acc/GuestUserImage.jpg"); + UserProfile guest = new UserProfile(new UserId("00000000000000000000000000000080"), "Guest", defaultGuestImage); + + ManualResetEvent dialogCloseEvent = new(false); + + Dispatcher.UIThread.InvokeAsync(async () => + { + ObservableCollection profiles = []; + NavigationDialogHost nav = new(); + + _parent.AccountManager.GetAllUsers() + .OrderBy(x => x.Name) + .ForEach(profile => profiles.Add(new Models.UserProfile(profile, nav))); + + profiles.Add(new Models.UserProfile(guest, nav)); + UserSelectorDialogViewModel viewModel = new(); + viewModel.Profiles = profiles; + viewModel.SelectedUserId = _parent.AccountManager.LastOpenedUser.UserId; + UserSelectorDialog content = new(viewModel); + (UserId id, _) = await UserSelectorDialog.ShowInputDialog(content); + + selected = id; + + dialogCloseEvent.Set(); + }); + + dialogCloseEvent.WaitOne(); + + UserProfile profile = _parent.AccountManager.LastOpenedUser; + if (selected == guest.UserId) + { + profile = guest; + } + else if (selected == UserId.Null) + { + profile = null; + } + else + { + foreach (UserProfile p in _parent.AccountManager.GetAllUsers()) + { + if (p.UserId == selected) + { + profile = p; + break; + } + } + } + return profile; + } } } diff --git a/src/Ryujinx/UI/Applet/UserSelectorDialog.axaml b/src/Ryujinx/UI/Applet/UserSelectorDialog.axaml new file mode 100644 index 000000000..ed22fb088 --- /dev/null +++ b/src/Ryujinx/UI/Applet/UserSelectorDialog.axaml @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx/UI/Applet/UserSelectorDialog.axaml.cs b/src/Ryujinx/UI/Applet/UserSelectorDialog.axaml.cs new file mode 100644 index 000000000..6e25588ec --- /dev/null +++ b/src/Ryujinx/UI/Applet/UserSelectorDialog.axaml.cs @@ -0,0 +1,123 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Input; +using FluentAvalonia.UI.Controls; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Controls; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.ViewModels; +using Ryujinx.Ava.UI.ViewModels.Input; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Services.Account.Acc; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using UserProfile = Ryujinx.Ava.UI.Models.UserProfile; +using UserProfileSft = Ryujinx.HLE.HOS.Services.Account.Acc.UserProfile; + +namespace Ryujinx.Ava.UI.Applet +{ + public partial class UserSelectorDialog : UserControl, INotifyPropertyChanged + { + public UserSelectorDialogViewModel ViewModel { get; set; } + + public UserSelectorDialog(UserSelectorDialogViewModel viewModel) + { + InitializeComponent(); + ViewModel = viewModel; + DataContext = ViewModel; + } + + private void Grid_PointerEntered(object sender, PointerEventArgs e) + { + if (sender is Grid { DataContext: UserProfile profile }) + { + profile.IsPointerOver = true; + } + } + + private void Grid_OnPointerExited(object sender, PointerEventArgs e) + { + if (sender is Grid { DataContext: UserProfile profile }) + { + profile.IsPointerOver = false; + } + } + + private void ProfilesList_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + if (sender is ListBox listBox) + { + int selectedIndex = listBox.SelectedIndex; + + if (selectedIndex >= 0 && selectedIndex < ViewModel.Profiles.Count) + { + if (ViewModel.Profiles[selectedIndex] is UserProfile userProfile) + { + ViewModel.SelectedUserId = userProfile.UserId; + Logger.Info?.Print(LogClass.UI, $"Selected user: {userProfile.UserId}"); + + ObservableCollection newProfiles = []; + + foreach (var item in ViewModel.Profiles) + { + if (item is UserProfile originalItem) + { + var profile = new UserProfileSft(originalItem.UserId, originalItem.Name, originalItem.Image); + + if (profile.UserId == ViewModel.SelectedUserId) + { + profile.AccountState = AccountState.Open; + } + + newProfiles.Add(new UserProfile(profile, new NavigationDialogHost())); + } + } + + ViewModel.Profiles = newProfiles; + } + } + } + } + + public static async Task<(UserId Id, bool Result)> ShowInputDialog(UserSelectorDialog content) + { + ContentDialog contentDialog = new() + { + Title = LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle], + PrimaryButtonText = LocaleManager.Instance[LocaleKeys.Continue], + SecondaryButtonText = string.Empty, + CloseButtonText = LocaleManager.Instance[LocaleKeys.Cancel], + Content = content, + Padding = new Thickness(0) + }; + + UserId result = UserId.Null; + bool input = false; + + void Handler(ContentDialog sender, ContentDialogClosedEventArgs eventArgs) + { + if (eventArgs.Result == ContentDialogResult.Primary) + { + if (contentDialog.Content is UserSelectorDialog view) + { + result = view.ViewModel.SelectedUserId; + input = true; + } + } + else + { + result = UserId.Null; + input = false; + } + } + + contentDialog.Closed += Handler; + + await ContentDialogHelper.ShowAsync(contentDialog); + + return (result, input); + } + } +} diff --git a/src/Ryujinx/UI/ViewModels/UserSelectorDialogViewModel.cs b/src/Ryujinx/UI/ViewModels/UserSelectorDialogViewModel.cs new file mode 100644 index 000000000..094aed5cf --- /dev/null +++ b/src/Ryujinx/UI/ViewModels/UserSelectorDialogViewModel.cs @@ -0,0 +1,14 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using Ryujinx.HLE.HOS.Services.Account.Acc; +using System.Collections.ObjectModel; + +namespace Ryujinx.Ava.UI.ViewModels +{ + public partial class UserSelectorDialogViewModel : BaseModel + { + + [ObservableProperty] private UserId _selectedUserId; + + [ObservableProperty] private ObservableCollection _profiles = []; + } +}