diff --git a/src/Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectApplet.cs b/src/Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectApplet.cs index 05bddc76f..2b426787b 100644 --- a/src/Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectApplet.cs +++ b/src/Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectApplet.cs @@ -1,3 +1,4 @@ +using Ryujinx.Common.Logging; using Ryujinx.Common.Memory; using Ryujinx.HLE.HOS.Services.Account.Acc; using Ryujinx.HLE.HOS.Services.Am.AppletAE; @@ -26,10 +27,9 @@ 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(); + _normalSession.Push(BuildResponse(selected)); AppletStateChanged?.Invoke(this, null); _system.ReturnFocus(); @@ -37,16 +37,14 @@ 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(); } 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 000000000..8310994de Binary files /dev/null and b/src/Ryujinx.HLE/HOS/Services/Account/Acc/GuestUserImage.jpg differ diff --git a/src/Ryujinx.HLE/Ryujinx.HLE.csproj b/src/Ryujinx.HLE/Ryujinx.HLE.csproj index d42ecf8b8..f551f1a18 100644 --- a/src/Ryujinx.HLE/Ryujinx.HLE.csproj +++ b/src/Ryujinx.HLE/Ryujinx.HLE.csproj @@ -48,6 +48,7 @@ + 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/WindowBase.cs b/src/Ryujinx/Headless/WindowBase.cs index d89638cc1..6ca11f51c 100644 --- a/src/Ryujinx/Headless/WindowBase.cs +++ b/src/Ryujinx/Headless/WindowBase.cs @@ -9,6 +9,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.UI; using Ryujinx.Input; @@ -572,5 +573,10 @@ namespace Ryujinx.Headless SDL2Driver.Instance.Dispose(); } } + + public UserProfile ShowPlayerSelectDialog() + { + throw new NotImplementedException(); + } } } diff --git a/src/Ryujinx/Ryujinx.csproj b/src/Ryujinx/Ryujinx.csproj index ab9a3696d..0a284e58b 100644 --- a/src/Ryujinx/Ryujinx.csproj +++ b/src/Ryujinx/Ryujinx.csproj @@ -174,4 +174,10 @@ + + + UserSelectorDialog.axaml + Code + + diff --git a/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs b/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs index 65f4c7795..f5d5111d6 100644 --- a/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs +++ b/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs @@ -1,17 +1,25 @@ +using Avalonia; using Avalonia.Controls; +using Avalonia.Media.Imaging; 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.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 +261,60 @@ 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 () => + { + var Profiles = new ObservableCollection(); + var nav = new NavigationDialogHost(); + + _parent.AccountManager.GetAllUsers() + .OrderBy(x => x.Name) + .ForEach(profile => Profiles.Add(new Models.UserProfile(profile, nav))); + + Profiles.Add(new Models.UserProfile(guest, nav)); + + var content = new UserSelectorDialog(Profiles); + (UserId id, _) = await UserSelectorDialog.ShowInputDialog(content); + + if (id != UserId.Null) + { + selected = id; + } + else + { + selected = _parent.AccountManager.LastOpenedUser.UserId; + } + + dialogCloseEvent.Set(); + }); + + dialogCloseEvent.WaitOne(); + + UserProfile profile = _parent.AccountManager.LastOpenedUser; + if (selected == guest.UserId) + { + profile = guest; + } + 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..dcdd8c853 --- /dev/null +++ b/src/Ryujinx/UI/Applet/UserSelectorDialog.axaml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx/UI/Applet/UserSelectorDialog.axaml.cs b/src/Ryujinx/UI/Applet/UserSelectorDialog.axaml.cs new file mode 100644 index 000000000..e654c6b99 --- /dev/null +++ b/src/Ryujinx/UI/Applet/UserSelectorDialog.axaml.cs @@ -0,0 +1,126 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Interactivity; +using FluentAvalonia.UI.Controls; +using FluentAvalonia.UI.Navigation; +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.Applets; +using Ryujinx.HLE.HOS.Services.Account.Acc; +using shaderc; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using Button = Avalonia.Controls.Button; +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 event PropertyChangedEventHandler PropertyChanged; + + private UserId _selectedUserId; + + public ObservableCollection Profiles { get; set; } + + public UserSelectorDialog(ObservableCollection Profiles) + { + InitializeComponent(); + this.Profiles = Profiles; + DataContext = this; + } + + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = "") + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + private void Grid_PointerEntered(object sender, PointerEventArgs e) + { + if (sender is Grid grid && grid.DataContext is UserProfile profile) + { + profile.IsPointerOver = true; + } + } + + private void Grid_OnPointerExited(object sender, PointerEventArgs e) + { + if (sender is Grid grid && grid.DataContext is 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 < Profiles.Count) + { + if (Profiles[selectedIndex] is UserProfile userProfile) + { + _selectedUserId = userProfile.UserId; + Logger.Info?.Print(LogClass.UI, $"Selected user: {userProfile.UserId}"); + var NewProfiles = new ObservableCollection(); + + foreach (var item in Profiles) + { + UserProfile originalItem = (UserProfile)item; + UserProfileSft profile = new (originalItem.UserId, originalItem.Name, + originalItem.Image); + if (profile.UserId == _selectedUserId) + { + profile.AccountState = AccountState.Open; + } + NewProfiles.Add(new UserProfile(profile, new NavigationDialogHost())); + } + + Profiles = NewProfiles; + OnPropertyChanged(nameof(Profiles)); + } + } + } + } + + 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 = string.Empty, + Content = content, + Padding = new Thickness(0) + }; + + UserId result = UserId.Null; + bool input = false; + + void Handler(ContentDialog sender, ContentDialogClosedEventArgs eventArgs) + { + if (eventArgs.Result == ContentDialogResult.Primary) + { + UserSelectorDialog view = (UserSelectorDialog)contentDialog.Content; + result = view?._selectedUserId ?? UserId.Null; + input = true; + } + } + + contentDialog.Closed += Handler; + + await ContentDialogHelper.ShowAsync(contentDialog); + + return (result, input); + } + } +}