From 87605ebce204a3751885d36da4978c58af100489 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 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 - misc: chore: optimize UserSelectorDialog closed handler - misc: chore: Rename UserSelectorDialog to ProfileSelectorDialog Co-authored-by: GreemDev --- .../PlayerSelect/PlayerSelectApplet.cs | 42 +++++- .../Services/Account/Acc/GuestUserImage.jpg | Bin 0 -> 8021 bytes src/Ryujinx.HLE/Ryujinx.HLE.csproj | 1 + src/Ryujinx.HLE/UI/IHostUIHandler.cs | 6 + src/Ryujinx/Headless/WindowBase.cs | 7 + src/Ryujinx/Ryujinx.csproj | 6 + src/Ryujinx/UI/Applet/AvaHostUIHandler.cs | 64 ++++++++- .../UI/Applet/ProfileSelectorDialog.axaml | 121 ++++++++++++++++++ .../UI/Applet/ProfileSelectorDialog.axaml.cs | 118 +++++++++++++++++ .../ProfileSelectorDialogViewModel.cs | 14 ++ 10 files changed, 368 insertions(+), 11 deletions(-) create mode 100644 src/Ryujinx.HLE/HOS/Services/Account/Acc/GuestUserImage.jpg create mode 100644 src/Ryujinx/UI/Applet/ProfileSelectorDialog.axaml create mode 100644 src/Ryujinx/UI/Applet/ProfileSelectorDialog.axaml.cs create mode 100644 src/Ryujinx/UI/ViewModels/ProfileSelectorDialogViewModel.cs diff --git a/src/Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectApplet.cs b/src/Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectApplet.cs index 05bddc76f..9d5b53177 100644 --- a/src/Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectApplet.cs +++ b/src/Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectApplet.cs @@ -27,9 +27,19 @@ 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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 literal 0 HcmV?d00001 diff --git a/src/Ryujinx.HLE/Ryujinx.HLE.csproj b/src/Ryujinx.HLE/Ryujinx.HLE.csproj index 9c5d0c4c6..5ec79a57c 100644 --- a/src/Ryujinx.HLE/Ryujinx.HLE.csproj +++ b/src/Ryujinx.HLE/Ryujinx.HLE.csproj @@ -59,6 +59,7 @@ + diff --git a/src/Ryujinx.HLE/UI/IHostUIHandler.cs b/src/Ryujinx.HLE/UI/IHostUIHandler.cs index f33c8d7aa..35ec69135 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 @@ -65,5 +66,10 @@ namespace Ryujinx.HLE.UI /// Takes a screenshot from the current renderer and saves it in the screenshots folder. /// void TakeScreenshot(); + + /// + /// Displays the player select dialog and returns the selected profile. + /// + UserProfile ShowPlayerSelectDialog(); } } diff --git a/src/Ryujinx/Headless/WindowBase.cs b/src/Ryujinx/Headless/WindowBase.cs index 77b7e5a81..49e465569 100644 --- a/src/Ryujinx/Headless/WindowBase.cs +++ b/src/Ryujinx/Headless/WindowBase.cs @@ -10,6 +10,7 @@ using Ryujinx.Graphics.GAL.Multithreading; using Ryujinx.Graphics.Gpu; 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.Loaders.Processes; using Ryujinx.HLE.UI; @@ -28,6 +29,7 @@ using static SDL.SDL3; 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 { @@ -590,5 +592,10 @@ namespace Ryujinx.Headless { throw new NotImplementedException(); } + + public UserProfile ShowPlayerSelectDialog() + { + return AccountSaveDataManager.GetLastUsedUser(); + } } } diff --git a/src/Ryujinx/Ryujinx.csproj b/src/Ryujinx/Ryujinx.csproj index 1c04aa02e..4685c84a7 100644 --- a/src/Ryujinx/Ryujinx.csproj +++ b/src/Ryujinx/Ryujinx.csproj @@ -169,4 +169,10 @@ + + + UserSelectorDialog.axaml + Code + + diff --git a/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs b/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs index 21bac3935..db984595c 100644 --- a/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs +++ b/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs @@ -1,17 +1,23 @@ 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.Windows; +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 Ryujinx.UI.Common.Configuration; using System; +using System.Collections.ObjectModel; +using System.Linq; using System.Threading; namespace Ryujinx.Ava.UI.Applet @@ -256,11 +262,61 @@ namespace Ryujinx.Ava.UI.Applet return showDetails; } - public IDynamicTextInputHandler CreateDynamicTextInputHandler() - { - return new AvaloniaDynamicTextInputHandler(_parent); - } + 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)); + ProfileSelectorDialogViewModel viewModel = new() + { + Profiles = profiles, + SelectedUserId = _parent.AccountManager.LastOpenedUser.UserId + }; + (selected, _) = await ProfileSelectorDialog.ShowInputDialog(viewModel); + + 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; + } + public void TakeScreenshot() { _parent.ViewModel.AppHost.ScreenshotRequested = true; diff --git a/src/Ryujinx/UI/Applet/ProfileSelectorDialog.axaml b/src/Ryujinx/UI/Applet/ProfileSelectorDialog.axaml new file mode 100644 index 000000000..369790053 --- /dev/null +++ b/src/Ryujinx/UI/Applet/ProfileSelectorDialog.axaml @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx/UI/Applet/ProfileSelectorDialog.axaml.cs b/src/Ryujinx/UI/Applet/ProfileSelectorDialog.axaml.cs new file mode 100644 index 000000000..8d2c54088 --- /dev/null +++ b/src/Ryujinx/UI/Applet/ProfileSelectorDialog.axaml.cs @@ -0,0 +1,118 @@ +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.Common.Logging; +using Ryujinx.HLE.HOS.Services.Account.Acc; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Threading.Tasks; +using UserProfile = Ryujinx.Ava.UI.Models.UserProfile; +using UserProfileSft = Ryujinx.HLE.HOS.Services.Account.Acc.UserProfile; + +namespace Ryujinx.Ava.UI.Applet +{ + public partial class ProfileSelectorDialog : UserControl + { + public ProfileSelectorDialogViewModel ViewModel { get; set; } + + public ProfileSelectorDialog(ProfileSelectorDialogViewModel viewModel) + { + DataContext = ViewModel = viewModel; + + InitializeComponent(); + } + + 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: {userProfile.UserId}", "ProfileSelector"); + + ObservableCollection newProfiles = []; + + foreach (BaseModel item in ViewModel.Profiles) + { + if (item is UserProfile originalItem) + { + UserProfileSft profile = new(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(ProfileSelectorDialogViewModel viewModel) + { + FAContentDialog contentDialog = new() + { + Title = LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle], + PrimaryButtonText = LocaleManager.Instance[LocaleKeys.Continue], + SecondaryButtonText = string.Empty, + CloseButtonText = LocaleManager.Instance[LocaleKeys.Cancel], + Content = new ProfileSelectorDialog(viewModel), + Padding = new Thickness(0) + }; + + UserId result = UserId.Null; + bool input = false; + + contentDialog.Closed += Handler; + + await ContentDialogHelper.ShowAsync(contentDialog); + + return (result, input); + + void Handler(FAContentDialog sender, FAContentDialogClosedEventArgs eventArgs) + { + if (eventArgs.Result == FAContentDialogResult.Primary) + { + result = viewModel.SelectedUserId; + input = true; + } + else + { + result = UserId.Null; + input = false; + } + } + } + } +} diff --git a/src/Ryujinx/UI/ViewModels/ProfileSelectorDialogViewModel.cs b/src/Ryujinx/UI/ViewModels/ProfileSelectorDialogViewModel.cs new file mode 100644 index 000000000..979e1616a --- /dev/null +++ b/src/Ryujinx/UI/ViewModels/ProfileSelectorDialogViewModel.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 ProfileSelectorDialogViewModel : BaseModel + { + + [ObservableProperty] private UserId _selectedUserId; + + [ObservableProperty] private ObservableCollection _profiles = []; + } +}