This commit is contained in:
Jean-Philippe HAUTIN 2024-07-16 09:48:50 +02:00 committed by GitHub
commit 650ec8b99f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
34 changed files with 794 additions and 4 deletions

View file

@ -320,6 +320,11 @@ namespace Ryujinx
}
}
if (ConfigurationState.Instance.UI.ShowProfilesAtStartup)
{
mainWindow.ShowProfilesSelector();
}
if (CommandLineState.LaunchPathArg != null)
{
mainWindow.RunApplication(CommandLineState.LaunchPathArg, CommandLineState.StartFullscreenArg);

View file

@ -887,6 +887,12 @@ namespace Ryujinx.UI
return false;
}
public void ShowProfilesSelector()
{
var window = new UserProfilesSelectorWindow(_accountManager, _contentManager, _virtualFileSystem);
window.Show();
}
public void RunApplication(string path, bool startFullscreen = false)
{
if (_gameLoaded)

View file

@ -52,6 +52,7 @@ namespace Ryujinx.UI.Windows
[GUI] CheckButton _dockedModeToggle;
[GUI] CheckButton _discordToggle;
[GUI] CheckButton _checkUpdatesToggle;
[GUI] CheckButton _showProfileToggle;
[GUI] CheckButton _showConfirmExitToggle;
[GUI] RadioButton _hideCursorNever;
[GUI] RadioButton _hideCursorOnIdle;
@ -225,6 +226,11 @@ namespace Ryujinx.UI.Windows
_checkUpdatesToggle.Click();
}
if (ConfigurationState.Instance.UI.ShowProfilesAtStartup)
{
_showProfileToggle.Click();
}
if (ConfigurationState.Instance.ShowConfirmExit)
{
_showConfirmExitToggle.Click();
@ -626,6 +632,7 @@ namespace Ryujinx.UI.Windows
ConfigurationState.Instance.System.EnableDockedMode.Value = _dockedModeToggle.Active;
ConfigurationState.Instance.EnableDiscordIntegration.Value = _discordToggle.Active;
ConfigurationState.Instance.CheckUpdatesOnStart.Value = _checkUpdatesToggle.Active;
ConfigurationState.Instance.UI.ShowProfilesAtStartup.Value = _showProfileToggle.Active;
ConfigurationState.Instance.ShowConfirmExit.Value = _showConfirmExitToggle.Active;
ConfigurationState.Instance.HideCursor.Value = hideCursor;
ConfigurationState.Instance.Graphics.EnableVsync.Value = _vSyncToggle.Active;

View file

@ -143,6 +143,22 @@
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="_showProfileToggle">
<property name="label" translatable="yes">Show Profiles on Launch</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="halign">start</property>
<property name="draw-indicator">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">5</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="_showConfirmExitToggle">
<property name="label" translatable="yes">Show "Confirm Exit" Dialog</property>
@ -156,7 +172,7 @@
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">5</property>
<property name="position">2</property>
<property name="position">3</property>
</packing>
</child>
<child>

View file

@ -0,0 +1,160 @@
using Gtk;
using Pango;
using System;
namespace Ryujinx.UI.Windows
{
public partial class UserProfilesSelectorWindow : Window
{
private Box _mainBox;
private Label _selectedLabel;
private Box _selectedUserBox;
private Image _selectedUserImage;
private Box _selectedUserInfoBox;
private Entry _selectedUserNameEntry;
private Label _selectedUserIdLabel;
private Box _selectedUserButtonsBox;
private Button _saveProfileNameButton;
private Button _changeProfileImageButton;
private Box _usersTreeViewBox;
private Label _availableUsersLabel;
private ScrolledWindow _usersTreeViewWindow;
private ListStore _tableStore;
private TreeView _usersTreeView;
private Box _bottomBox;
private Button _addButton;
private Button _deleteButton;
private Button _closeButton;
private void InitializeComponent()
{
//
// UserProfilesManagerWindow
//
CanFocus = false;
Resizable = false;
Modal = true;
WindowPosition = WindowPosition.Center;
DefaultWidth = 620;
DefaultHeight = 548;
TypeHint = Gdk.WindowTypeHint.Dialog;
//
// _mainBox
//
_mainBox = new Box(Orientation.Vertical, 0);
//
// _viewBox
//
_usersTreeViewBox = new Box(Orientation.Vertical, 0);
//
// _saveProfileNameButton
//
_saveProfileNameButton = new Button()
{
Label = "Save Profile Name",
CanFocus = true,
ReceivesDefault = true,
Sensitive = false,
};
_saveProfileNameButton.Clicked += EditProfileNameButton_Pressed;
//
// _changeProfileImageButton
//
_changeProfileImageButton = new Button()
{
Label = "Change Profile Image",
CanFocus = true,
ReceivesDefault = true,
MarginTop = 10,
};
_changeProfileImageButton.Clicked += ChangeProfileImageButton_Pressed;
//
// _availableUsersLabel
//
_availableUsersLabel = new Label("Available User Profiles:")
{
Margin = 15,
Attributes = new AttrList(),
Halign = Align.Start,
};
_availableUsersLabel.Attributes.Insert(new Pango.AttrWeight(Weight.Bold));
//
// _usersTreeViewWindow
//
_usersTreeViewWindow = new ScrolledWindow()
{
ShadowType = ShadowType.In,
CanFocus = true,
Expand = true,
MarginStart = 30,
MarginEnd = 30,
MarginBottom = 15,
};
//
// _tableStore
//
_tableStore = new ListStore(typeof(bool), typeof(Gdk.Pixbuf), typeof(string), typeof(Gdk.RGBA));
//
// _usersTreeView
//
_usersTreeView = new TreeView(_tableStore)
{
HoverSelection = true,
HeadersVisible = false,
};
_usersTreeView.RowActivated += UsersTreeView_Activated;
//
// _bottomBox
//
_bottomBox = new Box(Orientation.Horizontal, 0)
{
MarginStart = 30,
MarginEnd = 30,
MarginBottom = 15,
};
//
// _closeButton
//
_closeButton = new Button()
{
Label = "Close",
CanFocus = true,
ReceivesDefault = true,
HeightRequest = 35,
WidthRequest = 80,
};
_closeButton.Clicked += CloseButton_Pressed;
ShowComponent();
}
private void ShowComponent()
{
_usersTreeViewWindow.Add(_usersTreeView);
_usersTreeViewBox.Add(_usersTreeViewWindow);
_bottomBox.PackStart(_addButton, false, false, 0);
_bottomBox.PackEnd(_closeButton, false, false, 0);
_mainBox.PackStart(_availableUsersLabel, false, false, 0);
_mainBox.Add(_usersTreeViewBox);
_mainBox.Add(_bottomBox);
Add(_mainBox);
ShowAll();
}
}
}

View file

@ -0,0 +1,271 @@
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;
namespace Ryujinx.UI.Windows
{
public partial class UserProfilesSelectorWindow : Window
{
private readonly AccountManager _accountManager;
private readonly ContentManager _contentManager;
private byte[] _bufferImageProfile;
private string _tempNewProfileName;
private Gdk.RGBA _selectedColor;
private readonly ManualResetEvent _avatarsPreloadingEvent = new(false);
public UserProfilesSelectorWindow(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();
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)
{
_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);
string userId = _tableStore.GetValue(selectedIter, 2).ToString().Split("\n")[1];
// Open the selected one.
_accountManager.OpenUser(new UserId(userId));
Close();
}
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("Import Custom Profile Image", this, FileChooserAction.Open, "Import", "Cancel")
{
SelectMultiple = false,
};
FileFilter filter = new()
{
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()
{
{ 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()
{
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();
}
}
}

View file

@ -15,7 +15,7 @@ namespace Ryujinx.UI.Common.Configuration
/// <summary>
/// The current version of the file format
/// </summary>
public const int CurrentVersion = 51;
public const int CurrentVersion = 52;
/// <summary>
/// Version of the configuration file format
@ -162,6 +162,11 @@ namespace Ryujinx.UI.Common.Configuration
/// </summary>
public bool ShowConfirmExit { get; set; }
/// <summary>
/// Show Profiles Dialog at Startup
/// </summary>
public bool ShowProfilesAtStartup { get; set; }
/// <summary>
/// Enables or disables save window size, position and state on close.
/// </summary>

View file

@ -187,6 +187,12 @@ namespace Ryujinx.UI.Common.Configuration
/// </summary>
public ReactiveObject<bool> IsAscendingOrder { get; private set; }
/// <summary>
/// Sets if Grid is ordered in Ascending Order
/// </summary>
public ReactiveObject<bool> ShowProfilesAtStartup { get; private set; }
public UISection()
{
GuiColumns = new Columns();
@ -205,6 +211,7 @@ namespace Ryujinx.UI.Common.Configuration
IsAscendingOrder = new ReactiveObject<bool>();
LanguageCode = new ReactiveObject<string>();
ShowConsole = new ReactiveObject<bool>();
ShowProfilesAtStartup = new ReactiveObject<bool>();
ShowConsole.Event += static (s, e) => { ConsoleHelper.SetConsoleWindowState(e.NewValue); };
}
}
@ -766,6 +773,7 @@ namespace Ryujinx.UI.Common.Configuration
PreferredGpu = Graphics.PreferredGpu,
MultiplayerLanInterfaceId = Multiplayer.LanInterfaceId,
MultiplayerMode = Multiplayer.Mode,
ShowProfilesAtStartup = UI.ShowProfilesAtStartup,
};
return configurationFile;
@ -853,6 +861,7 @@ namespace Ryujinx.UI.Common.Configuration
UI.IsAscendingOrder.Value = true;
UI.StartFullscreen.Value = false;
UI.ShowConsole.Value = true;
UI.ShowProfilesAtStartup.Value = false;
UI.WindowStartup.WindowSizeWidth.Value = 1280;
UI.WindowStartup.WindowSizeHeight.Value = 760;
UI.WindowStartup.WindowPositionX.Value = 0;
@ -1477,6 +1486,15 @@ namespace Ryujinx.UI.Common.Configuration
configurationFileUpdated = true;
}
if (configurationFileFormat.Version < 52)
{
Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 52.");
configurationFileFormat.ShowProfilesAtStartup = false;
configurationFileUpdated = true;
}
Logger.EnableFileLog.Value = configurationFileFormat.EnableFileLog;
Graphics.ResScale.Value = configurationFileFormat.ResScale;
Graphics.ResScaleCustom.Value = configurationFileFormat.ResScaleCustom;
@ -1555,6 +1573,7 @@ namespace Ryujinx.UI.Common.Configuration
UI.ApplicationSort.Value = configurationFileFormat.ApplicationSort;
UI.StartFullscreen.Value = configurationFileFormat.StartFullscreen;
UI.ShowConsole.Value = configurationFileFormat.ShowConsole;
UI.ShowProfilesAtStartup.Value = configurationFileFormat.ShowProfilesAtStartup;
UI.WindowStartup.WindowSizeWidth.Value = configurationFileFormat.WindowStartup.WindowSizeWidth;
UI.WindowStartup.WindowSizeHeight.Value = configurationFileFormat.WindowStartup.WindowSizeHeight;
UI.WindowStartup.WindowPositionX.Value = configurationFileFormat.WindowStartup.WindowPositionX;

View file

@ -14,4 +14,4 @@
<sty:FluentAvaloniaTheme PreferSystemTheme="False" />
<StyleInclude Source="/Assets/Styles/Styles.xaml"/>
</Application.Styles>
</Application>
</Application>

View file

@ -96,6 +96,7 @@
"SettingsTabGeneralEnableDiscordRichPresence": "تمكين وجود ديسكورد الغني",
"SettingsTabGeneralCheckUpdatesOnLaunch": "التحقق من وجود تحديثات عند التشغيل",
"SettingsTabGeneralShowConfirmExitDialog": "إظهار مربع حوار \"تأكيد الخروج\"",
"SettingsTabGeneralShoProfilesAtStartup": "Show Profiles at Startup",
"SettingsTabGeneralRememberWindowState": "تذكر حجم/موضع النافذة",
"SettingsTabGeneralHideCursor": "إخفاء المؤشر:",
"SettingsTabGeneralHideCursorNever": "مطلقا",

View file

@ -96,6 +96,7 @@
"SettingsTabGeneralEnableDiscordRichPresence": "Aktiviere die Statusanzeige für Discord",
"SettingsTabGeneralCheckUpdatesOnLaunch": "Beim Start nach Updates suchen",
"SettingsTabGeneralShowConfirmExitDialog": "Zeige den \"Beenden bestätigen\"-Dialog",
"SettingsTabGeneralShoProfilesAtStartup": "Show Profiles at Startup",
"SettingsTabGeneralRememberWindowState": "Fenstergröße/-position merken",
"SettingsTabGeneralHideCursor": "Mauszeiger ausblenden",
"SettingsTabGeneralHideCursorNever": "Niemals",

View file

@ -96,6 +96,7 @@
"SettingsTabGeneralEnableDiscordRichPresence": "Ενεργοποίηση Εμπλουτισμένης Παρουσίας Discord",
"SettingsTabGeneralCheckUpdatesOnLaunch": "Έλεγχος για Ενημερώσεις στην Εκκίνηση",
"SettingsTabGeneralShowConfirmExitDialog": "Εμφάνιση διαλόγου \"Επιβεβαίωση Εξόδου\".",
"SettingsTabGeneralShoProfilesAtStartup": "Show Profiles at Startup",
"SettingsTabGeneralRememberWindowState": "Remember Window Size/Position",
"SettingsTabGeneralHideCursor": "Απόκρυψη Κέρσορα:",
"SettingsTabGeneralHideCursorNever": "Ποτέ",

View file

@ -96,6 +96,7 @@
"SettingsTabGeneralEnableDiscordRichPresence": "Enable Discord Rich Presence",
"SettingsTabGeneralCheckUpdatesOnLaunch": "Check for Updates on Launch",
"SettingsTabGeneralShowConfirmExitDialog": "Show \"Confirm Exit\" Dialog",
"SettingsTabGeneralShoProfilesAtStartup": "Show Profiles at Startup",
"SettingsTabGeneralRememberWindowState": "Remember Window Size/Position",
"SettingsTabGeneralHideCursor": "Hide Cursor:",
"SettingsTabGeneralHideCursorNever": "Never",

View file

@ -96,6 +96,7 @@
"SettingsTabGeneralEnableDiscordRichPresence": "Habilitar estado en Discord",
"SettingsTabGeneralCheckUpdatesOnLaunch": "Buscar actualizaciones al iniciar",
"SettingsTabGeneralShowConfirmExitDialog": "Mostrar diálogo de confirmación al cerrar",
"SettingsTabGeneralShoProfilesAtStartup": "Show Profiles at Startup",
"SettingsTabGeneralRememberWindowState": "Remember Window Size/Position",
"SettingsTabGeneralHideCursor": "Esconder el cursor:",
"SettingsTabGeneralHideCursorNever": "Nunca",

View file

@ -95,6 +95,7 @@
"SettingsTabGeneralGeneral": "Général",
"SettingsTabGeneralEnableDiscordRichPresence": "Activer Discord Rich Presence",
"SettingsTabGeneralCheckUpdatesOnLaunch": "Vérifier les mises à jour au démarrage",
"SettingsTabGeneralShoProfilesAtStartup": "Afficher les profiles au démarrage",
"SettingsTabGeneralShowConfirmExitDialog": "Afficher le message de \"Confirmation de sortie\"",
"SettingsTabGeneralRememberWindowState": "Remember Window Size/Position",
"SettingsTabGeneralHideCursor": "Masquer le Curseur :",

View file

@ -96,6 +96,7 @@
"SettingsTabGeneralEnableDiscordRichPresence": "הפעלת תצוגה עשירה בדיסקורד",
"SettingsTabGeneralCheckUpdatesOnLaunch": "בדוק אם קיימים עדכונים בהפעלה",
"SettingsTabGeneralShowConfirmExitDialog": "הראה דיאלוג \"אשר יציאה\"",
"SettingsTabGeneralShoProfilesAtStartup": "Show Profiles at Startup",
"SettingsTabGeneralRememberWindowState": "Remember Window Size/Position",
"SettingsTabGeneralHideCursor": "הסתר את הסמן",
"SettingsTabGeneralHideCursorNever": "אף פעם",

View file

@ -96,6 +96,7 @@
"SettingsTabGeneralEnableDiscordRichPresence": "Attiva Discord Rich Presence",
"SettingsTabGeneralCheckUpdatesOnLaunch": "Controlla aggiornamenti all'avvio",
"SettingsTabGeneralShowConfirmExitDialog": "Mostra dialogo \"Conferma Uscita\"",
"SettingsTabGeneralShoProfilesAtStartup": "Show Profiles at Startup",
"SettingsTabGeneralRememberWindowState": "Remember Window Size/Position",
"SettingsTabGeneralHideCursor": "Nascondi il cursore:",
"SettingsTabGeneralHideCursorNever": "Mai",

View file

@ -96,6 +96,7 @@
"SettingsTabGeneralEnableDiscordRichPresence": "Discord リッチプレゼンスを有効にする",
"SettingsTabGeneralCheckUpdatesOnLaunch": "起動時にアップデートを確認する",
"SettingsTabGeneralShowConfirmExitDialog": "\"終了を確認\" ダイアログを表示する",
"SettingsTabGeneralShoProfilesAtStartup": "Show Profiles at Startup",
"SettingsTabGeneralRememberWindowState": "Remember Window Size/Position",
"SettingsTabGeneralHideCursor": "マウスカーソルを非表示",
"SettingsTabGeneralHideCursorNever": "決して",

View file

@ -96,6 +96,7 @@
"SettingsTabGeneralEnableDiscordRichPresence": "디스코드 활동 상태 활성화",
"SettingsTabGeneralCheckUpdatesOnLaunch": "시작 시, 업데이트 확인",
"SettingsTabGeneralShowConfirmExitDialog": "\"종료 확인\" 대화 상자 표시",
"SettingsTabGeneralShoProfilesAtStartup": "Show Profiles at Startup",
"SettingsTabGeneralRememberWindowState": "창 크기/위치 기억",
"SettingsTabGeneralHideCursor": "마우스 커서 숨기기",
"SettingsTabGeneralHideCursorNever": "절대 안 함",

View file

@ -96,6 +96,7 @@
"SettingsTabGeneralEnableDiscordRichPresence": "Włącz Bogatą Obecność Discord",
"SettingsTabGeneralCheckUpdatesOnLaunch": "Sprawdzaj aktualizacje przy uruchomieniu",
"SettingsTabGeneralShowConfirmExitDialog": "Pokazuj okno dialogowe \"Potwierdź wyjście\"",
"SettingsTabGeneralShoProfilesAtStartup": "Show Profiles at Startup",
"SettingsTabGeneralRememberWindowState": "Remember Window Size/Position",
"SettingsTabGeneralHideCursor": "Ukryj kursor:",
"SettingsTabGeneralHideCursorNever": "Nigdy",

View file

@ -96,6 +96,7 @@
"SettingsTabGeneralEnableDiscordRichPresence": "Habilitar Rich Presence do Discord",
"SettingsTabGeneralCheckUpdatesOnLaunch": "Verificar se há atualizações ao iniciar",
"SettingsTabGeneralShowConfirmExitDialog": "Exibir diálogo de confirmação ao sair",
"SettingsTabGeneralShoProfilesAtStartup": "Show Profiles at Startup",
"SettingsTabGeneralRememberWindowState": "Lembrar tamanho/posição da Janela",
"SettingsTabGeneralHideCursor": "Esconder o cursor do mouse:",
"SettingsTabGeneralHideCursorNever": "Nunca",

View file

@ -96,6 +96,7 @@
"SettingsTabGeneralEnableDiscordRichPresence": "Статус активности в Discord",
"SettingsTabGeneralCheckUpdatesOnLaunch": "Проверять наличие обновлений при запуске",
"SettingsTabGeneralShowConfirmExitDialog": "Подтверждать выход из приложения",
"SettingsTabGeneralShoProfilesAtStartup": "Show Profiles at Startup",
"SettingsTabGeneralRememberWindowState": "Запомнить размер/положение окна",
"SettingsTabGeneralHideCursor": "Скрывать курсор",
"SettingsTabGeneralHideCursorNever": "Никогда",

View file

@ -96,6 +96,7 @@
"SettingsTabGeneralEnableDiscordRichPresence": "เปิดใช้งาน Discord Rich Presence",
"SettingsTabGeneralCheckUpdatesOnLaunch": "ตรวจหาการอัปเดตเมื่อเปิดโปรแกรม",
"SettingsTabGeneralShowConfirmExitDialog": "แสดง \"ยืนยันการออก\" กล่องข้อความโต้ตอบ",
"SettingsTabGeneralShoProfilesAtStartup": "Show Profiles at Startup",
"SettingsTabGeneralRememberWindowState": "Remember Window Size/Position",
"SettingsTabGeneralHideCursor": "ซ่อน เคอร์เซอร์:",
"SettingsTabGeneralHideCursorNever": "ไม่มี",

View file

@ -96,6 +96,7 @@
"SettingsTabGeneralEnableDiscordRichPresence": "Discord Zengin İçerik'i Etkinleştir",
"SettingsTabGeneralCheckUpdatesOnLaunch": "Her Açılışta Güncellemeleri Denetle",
"SettingsTabGeneralShowConfirmExitDialog": "\"Çıkışı Onayla\" Diyaloğunu Göster",
"SettingsTabGeneralShoProfilesAtStartup": "Show Profiles at Startup",
"SettingsTabGeneralRememberWindowState": "Remember Window Size/Position",
"SettingsTabGeneralHideCursor": "İşaretçiyi Gizle:",
"SettingsTabGeneralHideCursorNever": "Hiçbir Zaman",

View file

@ -96,6 +96,7 @@
"SettingsTabGeneralEnableDiscordRichPresence": "Увімкнути розширену присутність Discord",
"SettingsTabGeneralCheckUpdatesOnLaunch": "Перевіряти наявність оновлень під час запуску",
"SettingsTabGeneralShowConfirmExitDialog": "Показати діалогове вікно «Підтвердити вихід».",
"SettingsTabGeneralShoProfilesAtStartup": "Show Profiles at Startup",
"SettingsTabGeneralRememberWindowState": "Remember Window Size/Position",
"SettingsTabGeneralHideCursor": "Сховати вказівник:",
"SettingsTabGeneralHideCursorNever": "Ніколи",

View file

@ -96,6 +96,7 @@
"SettingsTabGeneralEnableDiscordRichPresence": "启用 Discord 在线状态展示",
"SettingsTabGeneralCheckUpdatesOnLaunch": "启动时检查更新",
"SettingsTabGeneralShowConfirmExitDialog": "退出游戏时需要确认",
"SettingsTabGeneralShoProfilesAtStartup": "Show Profiles at Startup",
"SettingsTabGeneralRememberWindowState": "记住窗口大小和位置",
"SettingsTabGeneralHideCursor": "隐藏鼠标指针:",
"SettingsTabGeneralHideCursorNever": "从不隐藏",

View file

@ -96,6 +96,7 @@
"SettingsTabGeneralEnableDiscordRichPresence": "啟用 Discord 動態狀態展示",
"SettingsTabGeneralCheckUpdatesOnLaunch": "啟動時檢查更新",
"SettingsTabGeneralShowConfirmExitDialog": "顯示「確認結束」對話方塊",
"SettingsTabGeneralShoProfilesAtStartup": "Show Profiles at Startup",
"SettingsTabGeneralRememberWindowState": "記住視窗大小/位置",
"SettingsTabGeneralHideCursor": "隱藏滑鼠游標:",
"SettingsTabGeneralHideCursorNever": "從不",

View file

@ -164,4 +164,10 @@
<ItemGroup>
<AdditionalFiles Include="Assets\Locales\en_US.json" />
</ItemGroup>
<ItemGroup>
<Compile Update="UI\Views\User\UserSimpleSelectorView.axaml.cs">
<DependentUpon>UserSimpleSelectorView.axaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
</ItemGroup>
</Project>

View file

@ -0,0 +1,50 @@
using Avalonia.Collections;
using System;
using System.Collections.Generic;
using UserProfile = Ryujinx.Ava.UI.Models.UserProfile;
namespace Ryujinx.Ava.UI.ViewModels
{
public class ProfilesViewModel : BaseModel
{
private UserProfile _selectedProfile;
public event Action CloseWindow;
public event Action ApplyProfile;
public ProfilesViewModel()
{
Profiles = new AvaloniaList<UserProfile>();
Profiles.Clear();
}
public ProfilesViewModel(IEnumerable<UserProfile> profiles)
{
Profiles = new AvaloniaList<UserProfile>();
Profiles.Clear();
Profiles.AddRange(profiles);
}
public AvaloniaList<UserProfile> Profiles
{
get; set;
}
public UserProfile SelectedProfile
{
get => _selectedProfile;
set
{
_selectedProfile = value;
OnPropertyChanged();
}
}
public void Close()
{
CloseWindow?.Invoke();
}
}
}

View file

@ -130,6 +130,8 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool EnableDiscordIntegration { get; set; }
public bool CheckUpdatesOnStart { get; set; }
public bool ShowProfilesAtStartup { get; set; }
public bool ShowConfirmExit { get; set; }
public bool RememberWindowState { get; set; }
public int HideCursor { get; set; }
@ -391,6 +393,7 @@ namespace Ryujinx.Ava.UI.ViewModels
EnableDiscordIntegration = config.EnableDiscordIntegration;
CheckUpdatesOnStart = config.CheckUpdatesOnStart;
ShowConfirmExit = config.ShowConfirmExit;
ShowProfilesAtStartup = config.UI.ShowProfilesAtStartup;
RememberWindowState = config.RememberWindowState;
HideCursor = (int)config.HideCursor.Value;
@ -483,6 +486,7 @@ namespace Ryujinx.Ava.UI.ViewModels
config.EnableDiscordIntegration.Value = EnableDiscordIntegration;
config.CheckUpdatesOnStart.Value = CheckUpdatesOnStart;
config.ShowConfirmExit.Value = ShowConfirmExit;
config.UI.ShowProfilesAtStartup.Value = ShowProfilesAtStartup;
config.RememberWindowState.Value = RememberWindowState;
config.HideCursor.Value = (HideCursorMode)HideCursor;

View file

@ -1,4 +1,4 @@
<UserControl
<UserControl
x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsUiView"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
@ -33,6 +33,9 @@
<CheckBox IsChecked="{Binding CheckUpdatesOnStart}">
<TextBlock Text="{locale:Locale SettingsTabGeneralCheckUpdatesOnLaunch}" />
</CheckBox>
<CheckBox IsChecked="{Binding ShowProfilesAtStartup}">
<TextBlock Text="{locale:Locale SettingsTabGeneralShoProfilesAtStartup}" />
</CheckBox>
<CheckBox IsChecked="{Binding ShowConfirmExit}">
<TextBlock Text="{locale:Locale SettingsTabGeneralShowConfirmExitDialog}" />
</CheckBox>

View file

@ -0,0 +1,104 @@
<UserControl
x:Class="Ryujinx.Ava.UI.Views.User.UserSimpleSelectorView"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
d:DesignHeight="450"
MinWidth="500"
d:DesignWidth="800"
mc:Ignorable="d"
Focusable="True"
x:DataType="viewModels:ProfilesViewModel">
<UserControl.Resources>
<helpers:BitmapArrayValueConverter x:Key="ByteImage" />
</UserControl.Resources>
<Design.DataContext>
<viewModels:ProfilesViewModel />
</Design.DataContext>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Border
CornerRadius="5"
BorderBrush="{DynamicResource AppListHoverBackgroundColor}"
BorderThickness="1">
<ListBox
MaxHeight="300"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
SelectionChanged="ProfilesList_SelectionChanged"
Background="Transparent"
ItemsSource="{Binding Profiles}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel
HorizontalAlignment="Left"
VerticalAlignment="Center"
Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.Styles>
<Style Selector="ListBoxItem">
<Setter Property="Margin" Value="5 5 0 5" />
<Setter Property="CornerRadius" Value="5" />
</Style>
<Style Selector="Rectangle#SelectionIndicator">
<Setter Property="Opacity" Value="0" />
</Style>
</ListBox.Styles>
<ListBox.DataTemplates>
<DataTemplate
DataType="models:UserProfile">
<Grid
PointerEntered="Grid_PointerEntered"
PointerExited="Grid_OnPointerExited">
<Border
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
ClipToBounds="True"
CornerRadius="5"
Background="{Binding BackgroundColor}">
<StackPanel
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Image
Width="96"
Height="96"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
Source="{Binding Image, Converter={StaticResource ByteImage}}" />
<TextBlock
HorizontalAlignment="Stretch"
MaxWidth="90"
Text="{Binding Name}"
TextAlignment="Center"
TextWrapping="Wrap"
TextTrimming="CharacterEllipsis"
MaxLines="2"
Margin="5" />
</StackPanel>
</Border>
</Grid>
</DataTemplate>
</ListBox.DataTemplates>
</ListBox>
</Border>
<StackPanel
Grid.Row="1"
Margin="0 24 0 0"
HorizontalAlignment="Right"
Orientation="Horizontal">
<Button
Command="{Binding Close}"
Content="{locale:Locale UserProfilesClose}" />
</StackPanel>
</Grid>
</UserControl>

View file

@ -0,0 +1,84 @@
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.ViewModels;
using Ryujinx.HLE.HOS.Services.Account.Acc;
using System.Linq;
using Button = Avalonia.Controls.Button;
using UserProfile = Ryujinx.Ava.UI.Models.UserProfile;
namespace Ryujinx.Ava.UI.Views.User
{
public partial class UserSimpleSelectorView : UserControl
{
public ProfilesViewModel ViewModel { get; set; }
private readonly AccountManager _accountManager;
public UserSimpleSelectorView(AccountManager accountManager)
{
_accountManager = accountManager;
ViewModel = new ProfilesViewModel();
var profiles = _accountManager
.GetAllUsers()
.Select(p => new Models.UserProfile(p, null))
.OrderBy(p => p.Name);
ViewModel.Profiles.AddRange(profiles);
InitializeComponent();
}
private void Grid_PointerEntered(object sender, PointerEventArgs e)
{
if (sender is Grid grid)
{
if (grid.DataContext is UserProfile profile)
{
profile.IsPointerOver = true;
}
}
}
private void Grid_OnPointerExited(object sender, PointerEventArgs e)
{
if (sender is Grid grid)
{
if (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 < ViewModel.Profiles.Count)
{
if (ViewModel.Profiles[selectedIndex] is UserProfile userProfile)
{
_accountManager?.OpenUser(userProfile.UserId);
foreach (BaseModel profile in ViewModel.Profiles)
{
if (profile is UserProfile uProfile)
{
uProfile.UpdateState();
}
}
}
}
}
}
}
}

View file

@ -3,6 +3,7 @@ using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Interactivity;
using Avalonia.Platform;
using Avalonia.Styling;
using Avalonia.Threading;
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common;
@ -11,6 +12,7 @@ using Ryujinx.Ava.Input;
using Ryujinx.Ava.UI.Applet;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.UI.Views.User;
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.Gpu;
using Ryujinx.HLE.FileSystem;
@ -292,8 +294,38 @@ namespace Ryujinx.Ava.UI.Windows
}
}
private async Task ShowProfilesDialog()
{
await Dispatcher.UIThread.InvokeAsync(async () =>
{
//await ContentDialogHelper.ShowWindowAsync(new ProfilesWindows(AccountManager)));
var content = new UserSimpleSelectorView(AccountManager);
ContentDialog contentDialog = new()
{
Title = LocaleManager.Instance[LocaleKeys.UserProfileWindowTitle],
PrimaryButtonText = "",
SecondaryButtonText = "",
CloseButtonText = "",
DataContext = content.ViewModel,
Content = content,
Padding = new Thickness(0),
};
content.ViewModel.CloseWindow += contentDialog.Hide;
Style footer = new(x => x.Name("DialogSpace").Child().OfType<Border>());
footer.Setters.Add(new Setter(IsVisibleProperty, false));
contentDialog.Styles.Add(footer);
await contentDialog.ShowAsync();
});
}
private async Task CheckLaunchState()
{
if (ConfigurationState.Instance.UI.ShowProfilesAtStartup)
{
await ShowProfilesDialog();
}
if (OperatingSystem.IsLinux() && LinuxHelper.VmMaxMapCount < LinuxHelper.RecommendedVmMaxMapCount)
{
Logger.Warning?.Print(LogClass.Application, $"The value of vm.max_map_count is lower than {LinuxHelper.RecommendedVmMaxMapCount}. ({LinuxHelper.VmMaxMapCount})");