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);
+ }
+ }
+}