Compare commits

...

12 Commits

Author SHA1 Message Date
asfasagag fcb7425770 Merge 6b44f32448 into e676fd8b17 2025-01-19 17:07:57 -06:00
Evan Husted e676fd8b17 UI: misc: simplify Intel Mac warning logic 2025-01-19 14:42:15 -06:00
Evan Husted dd16e3cee1 misc: chore: very small cleanup in AvaHostUIHandler 2025-01-19 13:18:40 -06:00
Evan Husted 31e5f74e05 UI: misc: Replace spaces in Title with newlines when using custom title bar (since the Title is in an Avalonia tooltip) 2025-01-19 13:05:20 -06:00
Evan Husted f2f099bddb remove Async suffixes; they're factory methods not actual async methods. 2025-01-19 12:46:32 -06:00
Evan Husted 2616dc57fb misc: chore: RelayCommand helper 2025-01-19 12:44:07 -06:00
Evan Husted 0cdf7cfe21 UI: Open cheat manager in catch-all try 2025-01-18 22:48:06 -06:00
Evan Husted 2ecf999569 misc: chore: change ThemeManager ThemeChanged to a basic Action since both arguments are unused 2025-01-18 22:48:06 -06:00
Your Name 6b44f32448 Update ConfigurationState.Migration.cs 2025-01-01 12:41:19 -08:00
Your Name e849f94a2e misc: Update ConfigurationState & Version
Rebased to resolve PR conflicts
2025-01-01 12:34:03 -08:00
Your Name 62f3f5414f Update locales.json
Fix missing placeholder string for recently added Swedish locale
2025-01-01 12:15:22 -08:00
Your Name cd7fbf60f8 UI: Option to automatically Hide UI when game launches
Quality of life feature. Removes the need to manually hide UI every time the game launches
2025-01-01 12:15:22 -08:00
16 changed files with 167 additions and 31 deletions
+4 -4
View File
@@ -489,7 +489,7 @@ namespace Ryujinx.Ava
Dispatcher.UIThread.InvokeAsync(() => Dispatcher.UIThread.InvokeAsync(() =>
{ {
_viewModel.Title = TitleHelper.ActiveApplicationTitle(Device.Processes.ActiveApplication, Program.Version); _viewModel.Title = TitleHelper.ActiveApplicationTitle(Device.Processes.ActiveApplication, Program.Version, !ConfigurationState.Instance.ShowTitleBar);
}); });
_viewModel.SetUiProgressHandlers(Device); _viewModel.SetUiProgressHandlers(Device);
@@ -872,7 +872,7 @@ namespace Ryujinx.Ava
Device?.System.TogglePauseEmulation(false); Device?.System.TogglePauseEmulation(false);
_viewModel.IsPaused = false; _viewModel.IsPaused = false;
_viewModel.Title = TitleHelper.ActiveApplicationTitle(Device?.Processes.ActiveApplication, Program.Version); _viewModel.Title = TitleHelper.ActiveApplicationTitle(Device?.Processes.ActiveApplication, Program.Version, !ConfigurationState.Instance.ShowTitleBar);
Logger.Info?.Print(LogClass.Emulation, "Emulation was resumed"); Logger.Info?.Print(LogClass.Emulation, "Emulation was resumed");
} }
@@ -881,7 +881,7 @@ namespace Ryujinx.Ava
Device?.System.TogglePauseEmulation(true); Device?.System.TogglePauseEmulation(true);
_viewModel.IsPaused = true; _viewModel.IsPaused = true;
_viewModel.Title = TitleHelper.ActiveApplicationTitle(Device?.Processes.ActiveApplication, Program.Version, LocaleManager.Instance[LocaleKeys.Paused]); _viewModel.Title = TitleHelper.ActiveApplicationTitle(Device?.Processes.ActiveApplication, Program.Version, !ConfigurationState.Instance.ShowTitleBar, LocaleManager.Instance[LocaleKeys.Paused]);
Logger.Info?.Print(LogClass.Emulation, "Emulation was paused"); Logger.Info?.Print(LogClass.Emulation, "Emulation was paused");
} }
@@ -1040,7 +1040,7 @@ namespace Ryujinx.Ava
_viewModel.WindowState = WindowState.FullScreen; _viewModel.WindowState = WindowState.FullScreen;
} }
if (_viewModel.WindowState is WindowState.FullScreen) if (_viewModel.WindowState is WindowState.FullScreen || _viewModel.StartGamesWithoutUI)
{ {
_viewModel.ShowMenuAndStatusBar = false; _viewModel.ShowMenuAndStatusBar = false;
} }
+26 -1
View File
@@ -572,6 +572,31 @@
"zh_TW": "使用全螢幕模式啟動遊戲" "zh_TW": "使用全螢幕模式啟動遊戲"
} }
}, },
{
"ID": "MenuBarOptionsStartGamesWithoutUI",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Start Games with UI Hidden",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{ {
"ID": "MenuBarOptionsStopEmulation", "ID": "MenuBarOptionsStopEmulation",
"Translations": { "Translations": {
@@ -22898,4 +22923,4 @@
} }
} }
] ]
} }
+2 -2
View File
@@ -4,11 +4,11 @@ namespace Ryujinx.Ava.Common
{ {
public static class ThemeManager public static class ThemeManager
{ {
public static event EventHandler ThemeChanged; public static event Action ThemeChanged;
public static void OnThemeChanged() public static void OnThemeChanged()
{ {
ThemeChanged?.Invoke(null, EventArgs.Empty); ThemeChanged?.Invoke();
} }
} }
} }
+2 -1
View File
@@ -2,6 +2,7 @@ using DiscordRPC;
using Gommon; using Gommon;
using Humanizer; using Humanizer;
using Humanizer.Localisation; using Humanizer.Localisation;
using Ryujinx.Ava.Utilities;
using Ryujinx.Ava.Utilities.AppLibrary; using Ryujinx.Ava.Utilities.AppLibrary;
using Ryujinx.Ava.Utilities.Configuration; using Ryujinx.Ava.Utilities.Configuration;
using Ryujinx.Common; using Ryujinx.Common;
@@ -97,7 +98,7 @@ namespace Ryujinx.Ava
}, },
Details = TruncateToByteLength($"Playing {appMeta.Title}"), Details = TruncateToByteLength($"Playing {appMeta.Title}"),
State = appMeta.LastPlayed.HasValue && appMeta.TimePlayed.TotalSeconds > 5 State = appMeta.LastPlayed.HasValue && appMeta.TimePlayed.TotalSeconds > 5
? $"Total play time: {appMeta.TimePlayed.Humanize(2, false, maxUnit: TimeUnit.Hour)}" ? $"Total play time: {ValueFormatUtils.FormatTimeSpan(appMeta.TimePlayed)}"
: "Never played", : "Never played",
Timestamps = Timestamps.Now Timestamps = Timestamps.Now
}); });
+6 -6
View File
@@ -279,13 +279,13 @@ namespace Ryujinx.Ava.UI.Applet
.ForEach(profile => profiles.Add(new Models.UserProfile(profile, nav))); .ForEach(profile => profiles.Add(new Models.UserProfile(profile, nav)));
profiles.Add(new Models.UserProfile(guest, nav)); profiles.Add(new Models.UserProfile(guest, nav));
UserSelectorDialogViewModel viewModel = new(); UserSelectorDialogViewModel viewModel = new()
viewModel.Profiles = profiles; {
viewModel.SelectedUserId = _parent.AccountManager.LastOpenedUser.UserId; Profiles = profiles,
SelectedUserId = _parent.AccountManager.LastOpenedUser.UserId
};
UserSelectorDialog content = new(viewModel); UserSelectorDialog content = new(viewModel);
(UserId id, _) = await UserSelectorDialog.ShowInputDialog(content); (selected, _) = await UserSelectorDialog.ShowInputDialog(content);
selected = id;
dialogCloseEvent.Set(); dialogCloseEvent.Set();
}); });
+48
View File
@@ -0,0 +1,48 @@
using CommunityToolkit.Mvvm.Input;
using System;
using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.Helpers
{
#nullable enable
public static class Commands
{
public static RelayCommand Create(Action action)
=> new(action);
public static RelayCommand CreateConditional(Action action, Func<bool> canExecute)
=> new(action, canExecute);
public static RelayCommand<T> CreateWithArg<T>(Action<T?> action)
=> new(action);
public static RelayCommand<T> CreateConditionalWithArg<T>(Action<T?> action, Predicate<T?> canExecute)
=> new(action, canExecute);
public static AsyncRelayCommand Create(Func<Task> action)
=> new(action, AsyncRelayCommandOptions.None);
public static AsyncRelayCommand CreateConcurrent(Func<Task> action)
=> new(action, AsyncRelayCommandOptions.AllowConcurrentExecutions);
public static AsyncRelayCommand CreateSilentFail(Func<Task> action)
=> new(action, AsyncRelayCommandOptions.FlowExceptionsToTaskScheduler);
public static AsyncRelayCommand<T> CreateWithArg<T>(Func<T?, Task> action)
=> new(action, AsyncRelayCommandOptions.None);
public static AsyncRelayCommand<T> CreateConcurrentWithArg<T>(Func<T?, Task> action)
=> new(action, AsyncRelayCommandOptions.AllowConcurrentExecutions);
public static AsyncRelayCommand<T> CreateSilentFailWithArg<T>(Func<T?, Task> action)
=> new(action, AsyncRelayCommandOptions.FlowExceptionsToTaskScheduler);
public static AsyncRelayCommand CreateConditional(Func<Task> action, Func<bool> canExecute)
=> new(action, canExecute, AsyncRelayCommandOptions.None);
public static AsyncRelayCommand CreateConcurrentConditional(Func<Task> action, Func<bool> canExecute)
=> new(action, canExecute, AsyncRelayCommandOptions.AllowConcurrentExecutions);
public static AsyncRelayCommand CreateSilentFailConditional(Func<Task> action, Func<bool> canExecute)
=> new(action, canExecute, AsyncRelayCommandOptions.FlowExceptionsToTaskScheduler);
public static AsyncRelayCommand<T> CreateConditionalWithArg<T>(Func<T?, Task> action, Predicate<T?> canExecute)
=> new(action, canExecute, AsyncRelayCommandOptions.None);
public static AsyncRelayCommand<T> CreateConcurrentConditionalWithArg<T>(Func<T?, Task> action, Predicate<T?> canExecute)
=> new(action, canExecute, AsyncRelayCommandOptions.AllowConcurrentExecutions);
public static AsyncRelayCommand<T> CreateSilentFailConditionalWithArg<T>(Func<T?, Task> action, Predicate<T?> canExecute)
=> new(action, canExecute, AsyncRelayCommandOptions.FlowExceptionsToTaskScheduler);
}
}
@@ -27,7 +27,7 @@ namespace Ryujinx.Ava.UI.ViewModels
ThemeManager.ThemeChanged += ThemeManager_ThemeChanged; ThemeManager.ThemeChanged += ThemeManager_ThemeChanged;
} }
private void ThemeManager_ThemeChanged(object sender, EventArgs e) private void ThemeManager_ThemeChanged()
{ {
Dispatcher.UIThread.Post(() => UpdateLogoTheme(ConfigurationState.Instance.UI.BaseStyle.Value)); Dispatcher.UIThread.Post(() => UpdateLogoTheme(ConfigurationState.Instance.UI.BaseStyle.Value));
} }
@@ -489,6 +489,19 @@ namespace Ryujinx.Ava.UI.ViewModels
} }
} }
public bool StartGamesWithoutUI
{
get => ConfigurationState.Instance.UI.StartNoUI;
set
{
ConfigurationState.Instance.UI.StartNoUI.Value = value;
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
OnPropertyChanged();
}
}
public bool ShowConsole public bool ShowConsole
{ {
get => ConfigurationState.Instance.UI.ShowConsole; get => ConfigurationState.Instance.UI.ShowConsole;
@@ -1196,6 +1209,11 @@ namespace Ryujinx.Ava.UI.ViewModels
StartGamesInFullscreen = !StartGamesInFullscreen; StartGamesInFullscreen = !StartGamesInFullscreen;
} }
public void ToggleStartGamesWithoutUI()
{
StartGamesWithoutUI = !StartGamesWithoutUI;
}
public void ToggleShowConsole() public void ToggleShowConsole()
{ {
ShowConsole = !ShowConsole; ShowConsole = !ShowConsole;
@@ -119,6 +119,29 @@
</Style> </Style>
</MenuItem.Styles> </MenuItem.Styles>
</MenuItem> </MenuItem>
<MenuItem
Padding="0"
Command="{Binding ToggleStartGamesWithoutUI}"
Header="{ext:Locale MenuBarOptionsStartGamesWithoutUI}">
<MenuItem.Icon>
<CheckBox
MinWidth="{DynamicResource CheckBoxSize}"
MinHeight="{DynamicResource CheckBoxSize}"
IsChecked="{Binding StartGamesWithoutUI, Mode=TwoWay}"
Padding="0" />
</MenuItem.Icon>
<MenuItem.Styles>
<Style Selector="Viewbox#PART_IconPresenter">
<Setter Property="MaxHeight" Value="36" />
<Setter Property="MinHeight" Value="36" />
<Setter Property="MaxWidth" Value="36" />
<Setter Property="MinWidth" Value="36" />
</Style>
<Style Selector="ContentPresenter#PART_HeaderPresenter">
<Setter Property="Padding" Value="-10,0,0,0" />
</Style>
</MenuItem.Styles>
</MenuItem>
<MenuItem <MenuItem
Padding="0" Padding="0"
IsVisible="{Binding ShowConsoleVisible}" IsVisible="{Binding ShowConsoleVisible}"
@@ -43,7 +43,13 @@ namespace Ryujinx.Ava.UI.Views.Main
PauseEmulationMenuItem.Command = new RelayCommand(() => ViewModel.AppHost?.Pause()); PauseEmulationMenuItem.Command = new RelayCommand(() => ViewModel.AppHost?.Pause());
ResumeEmulationMenuItem.Command = new RelayCommand(() => ViewModel.AppHost?.Resume()); ResumeEmulationMenuItem.Command = new RelayCommand(() => ViewModel.AppHost?.Resume());
StopEmulationMenuItem.Command = new AsyncRelayCommand(() => ViewModel.AppHost?.ShowExitPrompt().OrCompleted()); StopEmulationMenuItem.Command = new AsyncRelayCommand(() => ViewModel.AppHost?.ShowExitPrompt().OrCompleted());
CheatManagerMenuItem.Command = new AsyncRelayCommand(OpenCheatManagerForCurrentApp); CheatManagerMenuItem.Command = new AsyncRelayCommand(async () =>
{
try
{
await OpenCheatManagerForCurrentApp();
} catch {}
});
InstallFileTypesMenuItem.Command = new AsyncRelayCommand(InstallFileTypes); InstallFileTypesMenuItem.Command = new AsyncRelayCommand(InstallFileTypes);
UninstallFileTypesMenuItem.Command = new AsyncRelayCommand(UninstallFileTypes); UninstallFileTypesMenuItem.Command = new AsyncRelayCommand(UninstallFileTypes);
XciTrimmerMenuItem.Command = new AsyncRelayCommand(() => XCITrimmerWindow.Show(ViewModel)); XciTrimmerMenuItem.Command = new AsyncRelayCommand(() => XCITrimmerWindow.Show(ViewModel));
+9 -11
View File
@@ -735,21 +735,19 @@ namespace Ryujinx.Ava.UI.Windows
}); });
} }
private static bool _intelMacWarningShown; private static bool _intelMacWarningShown = !(OperatingSystem.IsMacOS() &&
(RuntimeInformation.OSArchitecture == Architecture.X64 ||
RuntimeInformation.OSArchitecture == Architecture.X86));
public static async Task ShowIntelMacWarningAsync() public static async Task ShowIntelMacWarningAsync()
{ {
if (!_intelMacWarningShown && if (_intelMacWarningShown) return;
(OperatingSystem.IsMacOS() &&
(RuntimeInformation.OSArchitecture == Architecture.X64 || await Dispatcher.UIThread.InvokeAsync(async () => await ContentDialogHelper.CreateWarningDialog(
RuntimeInformation.OSArchitecture == Architecture.X86))) "Intel Mac Warning",
{ "Intel Macs are not supported and will not work properly.\nIf you continue, do not come to our Discord asking for support;\nand do not report bugs on the GitHub. They will be closed."));
_intelMacWarningShown = true;
await Dispatcher.UIThread.InvokeAsync(async () => await ContentDialogHelper.CreateWarningDialog( _intelMacWarningShown = true;
"Intel Mac Warning",
"Intel Macs are not supported and will not work properly.\nIf you continue, do not come to our Discord asking for support."));
}
} }
} }
} }
@@ -17,7 +17,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
/// <summary> /// <summary>
/// The current version of the file format /// The current version of the file format
/// </summary> /// </summary>
public const int CurrentVersion = 59; public const int CurrentVersion = 60;
/// <summary> /// <summary>
/// Version of the configuration file format /// Version of the configuration file format
@@ -351,6 +351,11 @@ namespace Ryujinx.Ava.Utilities.Configuration
/// </summary> /// </summary>
public bool StartFullscreen { get; set; } public bool StartFullscreen { get; set; }
/// <summary>
/// Start games with UI hidden
/// </summary>
public bool StartNoUI { get; set; }
/// <summary> /// <summary>
/// Show console window /// Show console window
/// </summary> /// </summary>
@@ -127,6 +127,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
UI.GridSize.Value = cff.GridSize; UI.GridSize.Value = cff.GridSize;
UI.ApplicationSort.Value = cff.ApplicationSort; UI.ApplicationSort.Value = cff.ApplicationSort;
UI.StartFullscreen.Value = cff.StartFullscreen; UI.StartFullscreen.Value = cff.StartFullscreen;
UI.StartNoUI.Value = cff.StartNoUI;
UI.ShowConsole.Value = cff.ShowConsole; UI.ShowConsole.Value = cff.ShowConsole;
UI.WindowStartup.WindowSizeWidth.Value = cff.WindowStartup.WindowSizeWidth; UI.WindowStartup.WindowSizeWidth.Value = cff.WindowStartup.WindowSizeWidth;
UI.WindowStartup.WindowSizeHeight.Value = cff.WindowStartup.WindowSizeHeight; UI.WindowStartup.WindowSizeHeight.Value = cff.WindowStartup.WindowSizeHeight;
@@ -414,7 +415,8 @@ namespace Ryujinx.Ava.Utilities.Configuration
// This was accidentally enabled by default when it was PRed. That is not what we want, // This was accidentally enabled by default when it was PRed. That is not what we want,
// so as a compromise users who want to use it will simply need to re-enable it once after updating. // so as a compromise users who want to use it will simply need to re-enable it once after updating.
cff.IgnoreApplet = false; cff.IgnoreApplet = false;
}) }),
(60, static cff => cff.StartNoUI = false)
); );
} }
} }
@@ -152,6 +152,11 @@ namespace Ryujinx.Ava.Utilities.Configuration
/// </summary> /// </summary>
public ReactiveObject<bool> StartFullscreen { get; private set; } public ReactiveObject<bool> StartFullscreen { get; private set; }
/// <summary>
/// Start games with UI hidden
/// </summary>
public ReactiveObject<bool> StartNoUI { get; private set; }
/// <summary> /// <summary>
/// Hide / Show Console Window /// Hide / Show Console Window
/// </summary> /// </summary>
@@ -192,6 +197,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
WindowStartup = new WindowStartupSettings(); WindowStartup = new WindowStartupSettings();
BaseStyle = new ReactiveObject<string>(); BaseStyle = new ReactiveObject<string>();
StartFullscreen = new ReactiveObject<bool>(); StartFullscreen = new ReactiveObject<bool>();
StartNoUI = new ReactiveObject<bool>();
GameListViewMode = new ReactiveObject<int>(); GameListViewMode = new ReactiveObject<int>();
ShowNames = new ReactiveObject<bool>(); ShowNames = new ReactiveObject<bool>();
GridSize = new ReactiveObject<int>(); GridSize = new ReactiveObject<int>();
@@ -125,6 +125,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
ApplicationSort = UI.ApplicationSort, ApplicationSort = UI.ApplicationSort,
IsAscendingOrder = UI.IsAscendingOrder, IsAscendingOrder = UI.IsAscendingOrder,
StartFullscreen = UI.StartFullscreen, StartFullscreen = UI.StartFullscreen,
StartNoUI = UI.StartNoUI,
ShowConsole = UI.ShowConsole, ShowConsole = UI.ShowConsole,
EnableKeyboard = Hid.EnableKeyboard, EnableKeyboard = Hid.EnableKeyboard,
EnableMouse = Hid.EnableMouse, EnableMouse = Hid.EnableMouse,
@@ -233,6 +234,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
UI.ApplicationSort.Value = 0; UI.ApplicationSort.Value = 0;
UI.IsAscendingOrder.Value = true; UI.IsAscendingOrder.Value = true;
UI.StartFullscreen.Value = false; UI.StartFullscreen.Value = false;
UI.StartNoUI.Value = false;
UI.ShowConsole.Value = true; UI.ShowConsole.Value = true;
UI.WindowStartup.WindowSizeWidth.Value = 1280; UI.WindowStartup.WindowSizeWidth.Value = 1280;
UI.WindowStartup.WindowSizeHeight.Value = 760; UI.WindowStartup.WindowSizeHeight.Value = 760;
+4 -2
View File
@@ -4,7 +4,7 @@ namespace Ryujinx.Ava.Utilities
{ {
public static class TitleHelper public static class TitleHelper
{ {
public static string ActiveApplicationTitle(ProcessResult activeProcess, string applicationVersion, string pauseString = "") public static string ActiveApplicationTitle(ProcessResult activeProcess, string applicationVersion, bool customTitlebar, string pauseString = "")
{ {
if (activeProcess == null) if (activeProcess == null)
return string.Empty; return string.Empty;
@@ -14,7 +14,9 @@ namespace Ryujinx.Ava.Utilities
string titleIdSection = $" ({activeProcess.ProgramIdText.ToUpper()})"; string titleIdSection = $" ({activeProcess.ProgramIdText.ToUpper()})";
string titleArchSection = activeProcess.Is64Bit ? " (64-bit)" : " (32-bit)"; string titleArchSection = activeProcess.Is64Bit ? " (64-bit)" : " (32-bit)";
string appTitle = $"Ryujinx {applicationVersion} -{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}"; string appTitle = customTitlebar
? $"Ryujinx {applicationVersion}\n{titleNameSection.Trim()}\n{titleVersionSection.Trim()}\n{titleIdSection.Trim()}{titleArchSection}"
: $"Ryujinx {applicationVersion} -{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}";
return !string.IsNullOrEmpty(pauseString) return !string.IsNullOrEmpty(pauseString)
? appTitle + $" ({pauseString})" ? appTitle + $" ({pauseString})"