Merge branch 'fix/fullscreen-offset-253' into 'master'

Fix window placement being overwritten when closing while fullscreen.

See merge request [ryubing/ryujinx!267](https://git.ryujinx.app/ryubing/ryujinx/-/merge_requests/267)
This commit is contained in:
EscoDev 2026-03-11 12:40:35 +01:00
commit 0f3ca4a5cd

View file

@ -56,12 +56,15 @@ namespace Ryujinx.Ava.UI.Windows
private static bool _startFullscreen; private static bool _startFullscreen;
private IDisposable _appLibraryAppsSubscription; private IDisposable _appLibraryAppsSubscription;
private IDisposable _isActiveSubscription;
private IDisposable _windowStateSubscription;
private IDisposable _widthSubscription;
private IDisposable _heightSubscription;
public VirtualFileSystem VirtualFileSystem { get; private set; } public VirtualFileSystem VirtualFileSystem { get; private set; }
public ContentManager ContentManager { get; private set; } public ContentManager ContentManager { get; private set; }
public AccountManager AccountManager { get; private set; } public AccountManager AccountManager { get; private set; }
public LibHacHorizonManager LibHacHorizonManager { get; private set; } public LibHacHorizonManager LibHacHorizonManager { get; private set; }
public InputManager InputManager { get; private set; } public InputManager InputManager { get; private set; }
public SettingsWindow SettingsWindow { get; set; } public SettingsWindow SettingsWindow { get; set; }
@ -69,18 +72,21 @@ namespace Ryujinx.Ava.UI.Windows
public static bool ShowKeyErrorOnLoad { get; set; } public static bool ShowKeyErrorOnLoad { get; set; }
public ApplicationLibrary ApplicationLibrary { get; set; } public ApplicationLibrary ApplicationLibrary { get; set; }
// Correctly size window when 'TitleBar' is enabled (Nov. 14, 2024)
public readonly double TitleBarHeight; public readonly double TitleBarHeight;
public readonly double StatusBarHeight; public readonly double StatusBarHeight;
public readonly double MenuBarHeight; public readonly double MenuBarHeight;
public MainWindow() : base(useCustomTitleBar: true) // Persist last-known NORMAL bounds in DIPs so non-normal (fullscreen/maximized) doesn't poison saved placement.
private Point _lastNormalPositionDip;
private double _lastNormalWidth;
private double _lastNormalHeight;
private bool _hasLastNormalBounds;
private double _lastRenderScaling;
public MainWindow() : base(true)
{ {
DataContext = ViewModel = new MainWindowViewModel DataContext = ViewModel = new MainWindowViewModel { Window = this };
{
Window = this
};
InitializeComponent(); InitializeComponent();
Load(); Load();
@ -96,19 +102,23 @@ namespace Ryujinx.Ava.UI.Windows
TitleBar.Height = MenuBarHeight; TitleBar.Height = MenuBarHeight;
// Correctly size window when 'TitleBar' is enabled (Nov. 14, 2024) // Correctly size window when 'TitleBar' is enabled (Nov. 14, 2024)
TitleBarHeight = (ConfigurationState.Instance.ShowOldUI ? TitleBar.Height : 0); TitleBarHeight = ConfigurationState.Instance.ShowOldUI ? TitleBar.Height : 0;
ApplicationList.DataContext = DataContext; ApplicationList.DataContext = DataContext;
ApplicationGrid.DataContext = DataContext; ApplicationGrid.DataContext = DataContext;
SetWindowSizePosition(); SetWindowSizePosition();
InitializeWindowBoundsTracking();
_lastRenderScaling = RenderScaling;
ScalingChanged += OnScalingChanged;
if (Program.PreviewerDetached) if (Program.PreviewerDetached)
{ {
InputManager = new InputManager(new AvaloniaKeyboardDriver(this), new SDL3GamepadDriver()); InputManager = new InputManager(new AvaloniaKeyboardDriver(this), new SDL3GamepadDriver());
_ = this.GetObservable(IsActiveProperty).Subscribe(it => ViewModel.IsActive = it); _isActiveSubscription = this.GetObservable(IsActiveProperty)
this.ScalingChanged += OnScalingChanged; .Subscribe(it => ViewModel.IsActive = it);
} }
} }
@ -118,12 +128,31 @@ namespace Ryujinx.Ava.UI.Windows
private static void OnPlatformColorValuesChanged(object sender, PlatformColorValues e) private static void OnPlatformColorValuesChanged(object sender, PlatformColorValues e)
{ {
if (Application.Current is RyujinxApp app) if (Application.Current is RyujinxApp app)
{
app.ApplyConfiguredTheme(ConfigurationState.Instance.UI.BaseStyle); app.ApplyConfiguredTheme(ConfigurationState.Instance.UI.BaseStyle);
} }
}
protected override void OnClosed(EventArgs e) protected override void OnClosed(EventArgs e)
{ {
base.OnClosed(e); base.OnClosed(e);
_isActiveSubscription?.Dispose();
_isActiveSubscription = null;
_windowStateSubscription?.Dispose();
_windowStateSubscription = null;
_widthSubscription?.Dispose();
_widthSubscription = null;
_heightSubscription?.Dispose();
_heightSubscription = null;
PositionChanged -= OnPositionChangedForBoundsTracking;
ScalingChanged -= OnScalingChanged;
if (PlatformSettings != null) if (PlatformSettings != null)
{ {
PlatformSettings.ColorValuesChanged -= OnPlatformColorValuesChanged; PlatformSettings.ColorValuesChanged -= OnPlatformColorValuesChanged;
@ -148,19 +177,54 @@ namespace Ryujinx.Ava.UI.Windows
CommandLineState.FirmwareToInstallPathArg = default; CommandLineState.FirmwareToInstallPathArg = default;
} }
else else
Logger.Notice.Print(LogClass.UI, "Invalid firmware type provided. Path must be a directory, or a .zip or .xci file."); {
Logger.Notice.Print(LogClass.UI,
"Invalid firmware type provided. Path must be a directory, or a .zip or .xci file.");
}
} }
}); });
} }
private void OnScalingChanged(object sender, EventArgs e) private void OnScalingChanged(object sender, EventArgs e)
{ {
Program.DesktopScaleFactor = this.RenderScaling; double newScale = RenderScaling;
if (Math.Abs(newScale - _lastRenderScaling) < 0.0001)
{
return;
}
_lastRenderScaling = newScale;
Program.DesktopScaleFactor = newScale;
// Only clamp + refresh cached NORMAL bounds when we're in Normal.
if (WindowState != WindowState.Normal)
{
return;
}
Dispatcher.UIThread.Post(() =>
{
if (WindowState != WindowState.Normal)
{
return;
}
Screen screen = GetBestScreenForPoint(Position);
if (screen != null)
{
PixelSize windowSizePx = GetWindowPixelSize(Width, Height);
Position = ClampToWorkingArea(Position, screen.WorkingArea, windowSizePx);
}
UpdateLastNormalBoundsIfSafe();
}, DispatcherPriority.Background);
} }
private void ApplicationLibrary_ApplicationCountUpdated(object sender, ApplicationCountUpdatedEventArgs e) private void ApplicationLibrary_ApplicationCountUpdated(object sender, ApplicationCountUpdatedEventArgs e)
{ {
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarGamesLoaded, e.NumAppsLoaded, e.NumAppsFound); LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarGamesLoaded, e.NumAppsLoaded,
e.NumAppsFound);
Dispatcher.UIThread.Post(() => Dispatcher.UIThread.Post(() =>
{ {
@ -187,7 +251,8 @@ namespace Ryujinx.Ava.UI.Windows
ViewModel.UsableLdnData.Clear(); ViewModel.UsableLdnData.Clear();
foreach (ApplicationData application in ViewModel.Applications.Where(it => it.HasControlHolder)) foreach (ApplicationData application in ViewModel.Applications.Where(it => it.HasControlHolder))
{ {
ViewModel.UsableLdnData[application.IdString] = LdnGameModel.GetArrayForApp(e.LdnData, ref application.ControlHolder.Value); ViewModel.UsableLdnData[application.IdString] =
LdnGameModel.GetArrayForApp(e.LdnData, ref application.ControlHolder.Value);
UpdateApplicationWithLdnData(application); UpdateApplicationWithLdnData(application);
} }
@ -198,7 +263,8 @@ namespace Ryujinx.Ava.UI.Windows
private void UpdateApplicationWithLdnData(ApplicationData application) private void UpdateApplicationWithLdnData(ApplicationData application)
{ {
if (application.HasControlHolder && ViewModel.UsableLdnData.TryGetValue(application.IdString, out LdnGameModel.Array ldnGameDatas)) if (application.HasControlHolder &&
ViewModel.UsableLdnData.TryGetValue(application.IdString, out LdnGameModel.Array ldnGameDatas))
{ {
application.PlayerCount = ldnGameDatas.PlayerCount; application.PlayerCount = ldnGameDatas.PlayerCount;
application.GameCount = ldnGameDatas.GameCount; application.GameCount = ldnGameDatas.GameCount;
@ -222,7 +288,8 @@ namespace Ryujinx.Ava.UI.Windows
args.Handled = true; args.Handled = true;
} }
internal static void DeferLoadApplication(string launchPathArg, string launchApplicationId, bool startFullscreenArg) internal static void DeferLoadApplication(string launchPathArg, string launchApplicationId,
bool startFullscreenArg)
{ {
_deferLoad = true; _deferLoad = true;
_launchPath = launchPathArg; _launchPath = launchPathArg;
@ -266,9 +333,10 @@ namespace Ryujinx.Ava.UI.Windows
LibHacHorizonManager.InitializeBcatServer(); LibHacHorizonManager.InitializeBcatServer();
LibHacHorizonManager.InitializeSystemClients(); LibHacHorizonManager.InitializeSystemClients();
ApplicationLibrary = new ApplicationLibrary(VirtualFileSystem, ConfigurationState.Instance.System.IntegrityCheckLevel) ApplicationLibrary =
new ApplicationLibrary(VirtualFileSystem, ConfigurationState.Instance.System.IntegrityCheckLevel)
{ {
DesiredLanguage = ConfigurationState.Instance.System.Language, DesiredLanguage = ConfigurationState.Instance.System.Language
}; };
// Save data created before we supported extra data in directory save data will not work properly if // Save data created before we supported extra data in directory save data will not work properly if
@ -318,26 +386,32 @@ namespace Ryujinx.Ava.UI.Windows
switch (response) switch (response)
{ {
case UserResult.Ok: case UserResult.Ok:
rc = LinuxHelper.RunPkExec($"echo {LinuxHelper.RecommendedVmMaxMapCount} > {LinuxHelper.VmMaxMapCountPath}"); rc = LinuxHelper.RunPkExec(
$"echo {LinuxHelper.RecommendedVmMaxMapCount} > {LinuxHelper.VmMaxMapCountPath}");
if (rc == 0) if (rc == 0)
{ {
Logger.Info?.Print(LogClass.Application, $"vm.max_map_count set to {LinuxHelper.VmMaxMapCount} until the next restart."); Logger.Info?.Print(LogClass.Application,
$"vm.max_map_count set to {LinuxHelper.VmMaxMapCount} until the next restart.");
} }
else else
{ {
Logger.Error?.Print(LogClass.Application, $"Unable to change vm.max_map_count. Process exited with code: {rc}"); Logger.Error?.Print(LogClass.Application,
$"Unable to change vm.max_map_count. Process exited with code: {rc}");
} }
break; break;
case UserResult.No: case UserResult.No:
rc = LinuxHelper.RunPkExec($"echo \"vm.max_map_count = {LinuxHelper.RecommendedVmMaxMapCount}\" > {LinuxHelper.SysCtlConfigPath} && sysctl -p {LinuxHelper.SysCtlConfigPath}"); rc = LinuxHelper.RunPkExec(
$"echo \"vm.max_map_count = {LinuxHelper.RecommendedVmMaxMapCount}\" > {LinuxHelper.SysCtlConfigPath} && sysctl -p {LinuxHelper.SysCtlConfigPath}");
if (rc == 0) if (rc == 0)
{ {
Logger.Info?.Print(LogClass.Application, $"vm.max_map_count set to {LinuxHelper.VmMaxMapCount}. Written to config: {LinuxHelper.SysCtlConfigPath}"); Logger.Info?.Print(LogClass.Application,
$"vm.max_map_count set to {LinuxHelper.VmMaxMapCount}. Written to config: {LinuxHelper.SysCtlConfigPath}");
} }
else else
{ {
Logger.Error?.Print(LogClass.Application, $"Unable to write new value for vm.max_map_count to config. Process exited with code: {rc}"); Logger.Error?.Print(LogClass.Application,
$"Unable to write new value for vm.max_map_count to config. Process exited with code: {rc}");
} }
break; break;
@ -348,7 +422,8 @@ namespace Ryujinx.Ava.UI.Windows
{ {
if (OperatingSystem.IsLinux() && LinuxHelper.VmMaxMapCount < LinuxHelper.RecommendedVmMaxMapCount) 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})"); Logger.Warning?.Print(LogClass.Application,
$"The value of vm.max_map_count is lower than {LinuxHelper.RecommendedVmMaxMapCount}. ({LinuxHelper.VmMaxMapCount})");
if (LinuxHelper.PkExecPath is not null) if (LinuxHelper.PkExecPath is not null)
{ {
@ -366,13 +441,15 @@ namespace Ryujinx.Ava.UI.Windows
{ {
_deferLoad = false; _deferLoad = false;
if (ApplicationLibrary.TryGetApplicationsFromFile(_launchPath, out List<ApplicationData> applications)) if (ApplicationLibrary.TryGetApplicationsFromFile(_launchPath,
out List<ApplicationData> applications))
{ {
ApplicationData applicationData; ApplicationData applicationData;
if (_launchApplicationId != null) if (_launchApplicationId != null)
{ {
applicationData = applications.FirstOrDefault(application => application.IdString == _launchApplicationId); applicationData = applications.FirstOrDefault(application =>
application.IdString == _launchApplicationId);
if (applicationData != null) if (applicationData != null)
{ {
@ -381,8 +458,10 @@ namespace Ryujinx.Ava.UI.Windows
} }
else else
{ {
Logger.Error?.Print(LogClass.Application, $"Couldn't find requested application id '{_launchApplicationId}' in '{_launchPath}'."); Logger.Error?.Print(LogClass.Application,
await Dispatcher.UIThread.InvokeAsync(async () => await UserErrorDialog.ShowUserErrorDialog(UserError.ApplicationNotFound)); $"Couldn't find requested application id '{_launchApplicationId}' in '{_launchPath}'.");
await Dispatcher.UIThread.InvokeAsync(async () =>
await UserErrorDialog.ShowUserErrorDialog(UserError.ApplicationNotFound));
} }
} }
else else
@ -395,7 +474,8 @@ namespace Ryujinx.Ava.UI.Windows
else else
{ {
Logger.Error?.Print(LogClass.Application, $"Couldn't find any application in '{_launchPath}'."); Logger.Error?.Print(LogClass.Application, $"Couldn't find any application in '{_launchPath}'.");
await Dispatcher.UIThread.InvokeAsync(async () => await UserErrorDialog.ShowUserErrorDialog(UserError.ApplicationNotFound)); await Dispatcher.UIThread.InvokeAsync(async () =>
await UserErrorDialog.ShowUserErrorDialog(UserError.ApplicationNotFound));
} }
} }
} }
@ -403,11 +483,14 @@ namespace Ryujinx.Ava.UI.Windows
{ {
ShowKeyErrorOnLoad = false; ShowKeyErrorOnLoad = false;
await Dispatcher.UIThread.InvokeAsync(async () => await UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys)); await Dispatcher.UIThread.InvokeAsync(async () =>
await UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys));
} }
if (!Updater.CanUpdate() || CommandLineState.HideAvailableUpdates) if (!Updater.CanUpdate() || CommandLineState.HideAvailableUpdates)
{
return; return;
}
switch (ConfigurationState.Instance.UpdateCheckerType.Value) switch (ConfigurationState.Instance.UpdateCheckerType.Value)
{ {
@ -418,7 +501,8 @@ namespace Ryujinx.Ava.UI.Windows
case UpdaterType.CheckInBackground: case UpdaterType.CheckInBackground:
if ((await Updater.CheckVersionAsync()).TryGet(out (Version Current, Version Incoming) versions)) if ((await Updater.CheckVersionAsync()).TryGet(out (Version Current, Version Incoming) versions))
{ {
Dispatcher.UIThread.Post(() => RyujinxApp.MainWindow.ViewModel.UpdateAvailable = versions.Current < versions.Incoming); Dispatcher.UIThread.Post(() =>
RyujinxApp.MainWindow.ViewModel.UpdateAvailable = versions.Current < versions.Incoming);
} }
break; break;
@ -435,12 +519,113 @@ namespace Ryujinx.Ava.UI.Windows
ApplicationList.ApplicationOpened += Application_Opened; ApplicationList.ApplicationOpened += Application_Opened;
} }
private void InitializeWindowBoundsTracking()
{
Dispatcher.UIThread.Post(CaptureNormalBoundsFromCurrent, DispatcherPriority.Background);
_windowStateSubscription = this.GetObservable(WindowStateProperty)
.Subscribe(_ => UpdateLastNormalBoundsIfSafe());
PositionChanged += OnPositionChangedForBoundsTracking;
_widthSubscription = this.GetObservable(WidthProperty)
.Subscribe(_ => UpdateLastNormalBoundsIfSafe());
_heightSubscription = this.GetObservable(HeightProperty)
.Subscribe(_ => UpdateLastNormalBoundsIfSafe());
}
private void OnPositionChangedForBoundsTracking(object sender, PixelPointEventArgs e)
{
UpdateLastNormalBoundsIfSafe();
}
private void CaptureNormalBoundsFromCurrent()
{
if (WindowState != WindowState.Normal)
{
return;
}
double scale = Math.Max(0.0001, RenderScaling);
_lastNormalPositionDip = new Point(Position.X / scale, Position.Y / scale);
_lastNormalWidth = Width;
_lastNormalHeight = Height;
_hasLastNormalBounds = true;
}
private void UpdateLastNormalBoundsIfSafe()
{
if (WindowState != WindowState.Normal)
{
return;
}
CaptureNormalBoundsFromCurrent();
}
private Screen GetBestScreenForPoint(PixelPoint p)
{
Screen match = Screens.All.FirstOrDefault(s => s.WorkingArea.Contains(p));
return match ?? Screens.Primary ?? Screens.All.FirstOrDefault();
}
private PixelPoint ClampToWorkingArea(PixelPoint p, PixelRect workingArea, PixelSize windowSizePx)
{
int x = p.X;
int y = p.Y;
int workingWidth = Math.Max(1, workingArea.Width);
int workingHeight = Math.Max(1, workingArea.Height);
int windowWidth = Math.Max(1, windowSizePx.Width);
int windowHeight = Math.Max(1, windowSizePx.Height);
windowWidth = Math.Min(windowWidth, workingWidth);
windowHeight = Math.Min(windowHeight, workingHeight);
int maxX = workingArea.Right - windowWidth;
int maxY = workingArea.Bottom - windowHeight;
if (x < workingArea.X)
{
x = workingArea.X;
}
else if (x > maxX)
{
x = maxX;
}
if (y < workingArea.Y)
{
y = workingArea.Y;
}
else if (y > maxY)
{
y = maxY;
}
return new PixelPoint(x, y);
}
private PixelSize GetWindowPixelSize(double widthDip, double heightDip)
{
double scale = RenderScaling;
int w = (int)Math.Max(1, Math.Round(widthDip * scale));
int h = (int)Math.Max(1, Math.Round(heightDip * scale));
return new PixelSize(w, h);
}
private void SetWindowSizePosition() private void SetWindowSizePosition()
{ {
if (!ConfigurationState.Instance.RememberWindowState) if (!ConfigurationState.Instance.RememberWindowState)
{ {
// Correctly size window when 'TitleBar' is enabled (Nov. 14, 2024) // Correctly size window when 'TitleBar' is enabled (Nov. 14, 2024)
ViewModel.WindowHeight = (720 + StatusBarHeight + MenuBarHeight + TitleBarHeight) * Program.WindowScaleFactor; ViewModel.WindowHeight =
(720 + StatusBarHeight + MenuBarHeight + TitleBarHeight) * Program.WindowScaleFactor;
ViewModel.WindowWidth = 1280 * Program.WindowScaleFactor; ViewModel.WindowWidth = 1280 * Program.WindowScaleFactor;
WindowState = WindowState.Normal; WindowState = WindowState.Normal;
@ -452,38 +637,85 @@ namespace Ryujinx.Ava.UI.Windows
PixelPoint savedPoint = new(ConfigurationState.Instance.UI.WindowStartup.WindowPositionX, PixelPoint savedPoint = new(ConfigurationState.Instance.UI.WindowStartup.WindowPositionX,
ConfigurationState.Instance.UI.WindowStartup.WindowPositionY); ConfigurationState.Instance.UI.WindowStartup.WindowPositionY);
ViewModel.WindowHeight = ConfigurationState.Instance.UI.WindowStartup.WindowSizeHeight * Program.WindowScaleFactor; ViewModel.WindowHeight = ConfigurationState.Instance.UI.WindowStartup.WindowSizeHeight *
ViewModel.WindowWidth = ConfigurationState.Instance.UI.WindowStartup.WindowSizeWidth * Program.WindowScaleFactor; Program.WindowScaleFactor;
ViewModel.WindowWidth =
ConfigurationState.Instance.UI.WindowStartup.WindowSizeWidth * Program.WindowScaleFactor;
ViewModel.WindowState = ConfigurationState.Instance.UI.WindowStartup.WindowMaximized.Value ? WindowState.Maximized : WindowState.Normal; ViewModel.WindowState = ConfigurationState.Instance.UI.WindowStartup.WindowMaximized.Value
? WindowState.Maximized
: WindowState.Normal;
if (Screens.All.Any(screen => screen.Bounds.Contains(savedPoint))) Screen screen = GetBestScreenForPoint(savedPoint);
if (screen != null)
{ {
Position = savedPoint; PixelSize windowSizePx = GetWindowPixelSize(ViewModel.WindowWidth, ViewModel.WindowHeight);
Position = ClampToWorkingArea(savedPoint, screen.WorkingArea, windowSizePx);
return;
} }
else
{ Logger.Warning?.Print(LogClass.Application,
Logger.Warning?.Print(LogClass.Application, "Failed to find valid start-up coordinates. Defaulting to primary monitor center."); "Failed to find screen for saved start-up coordinates. Defaulting to primary monitor center.");
WindowStartupLocation = WindowStartupLocation.CenterScreen; WindowStartupLocation = WindowStartupLocation.CenterScreen;
} }
}
private void SaveWindowSizePosition() private void SaveWindowSizePosition()
{ {
ConfigurationState.Instance.UI.WindowStartup.WindowMaximized.Value = WindowState == WindowState.Maximized; ConfigurationState.Instance.UI.WindowStartup.WindowMaximized.Value = WindowState == WindowState.Maximized;
// Only save rectangle properties if the window is not in a maximized state. if (WindowState == WindowState.Maximized)
if (WindowState != WindowState.Maximized)
{ {
// Since scaling is being applied to the loaded settings from disk (see SetWindowSizePosition() above), scaling should be removed from width/height before saving out to disk MainWindowViewModel.SaveConfig();
// as well - otherwise anyone not using a 1.0 scale factor their window will increase in size with every subsequent launch of the program when scaling is applied (Nov. 14, 2024) return;
ConfigurationState.Instance.UI.WindowStartup.WindowSizeHeight.Value = (int)(Height / Program.WindowScaleFactor);
ConfigurationState.Instance.UI.WindowStartup.WindowSizeWidth.Value = (int)(Width / Program.WindowScaleFactor);
ConfigurationState.Instance.UI.WindowStartup.WindowPositionX.Value = Position.X;
ConfigurationState.Instance.UI.WindowStartup.WindowPositionY.Value = Position.Y;
} }
PixelPoint posPx;
double widthDip;
double heightDip;
if (WindowState == WindowState.Normal)
{
posPx = Position;
widthDip = Width;
heightDip = Height;
CaptureNormalBoundsFromCurrent();
}
else
{
if (!_hasLastNormalBounds)
{
_lastNormalPositionDip = new Point(0, 0);
_lastNormalWidth = Width;
_lastNormalHeight = Height;
_hasLastNormalBounds = true;
}
double scale = Math.Max(0.0001, RenderScaling);
posPx = new PixelPoint(
(int)Math.Round(_lastNormalPositionDip.X * scale),
(int)Math.Round(_lastNormalPositionDip.Y * scale));
widthDip = _lastNormalWidth;
heightDip = _lastNormalHeight;
}
Screen screen = GetBestScreenForPoint(posPx);
if (screen != null)
{
PixelSize windowSizePx = GetWindowPixelSize(widthDip, heightDip);
posPx = ClampToWorkingArea(posPx, screen.WorkingArea, windowSizePx);
}
ConfigurationState.Instance.UI.WindowStartup.WindowSizeHeight.Value =
(int)(heightDip / Program.WindowScaleFactor);
ConfigurationState.Instance.UI.WindowStartup.WindowSizeWidth.Value =
(int)(widthDip / Program.WindowScaleFactor);
ConfigurationState.Instance.UI.WindowStartup.WindowPositionX.Value = posPx.X;
ConfigurationState.Instance.UI.WindowStartup.WindowPositionY.Value = posPx.Y;
MainWindowViewModel.SaveConfig(); MainWindowViewModel.SaveConfig();
} }
@ -709,7 +941,8 @@ namespace Ryujinx.Ava.UI.Windows
ApplicationLibrary.LoadApplications(ConfigurationState.Instance.UI.GameDirs); ApplicationLibrary.LoadApplications(ConfigurationState.Instance.UI.GameDirs);
List<string> autoloadDirs = ConfigurationState.Instance.UI.AutoloadDirs.Value; List<string> autoloadDirs = ConfigurationState.Instance.UI.AutoloadDirs.Value;
autoloadDirs.ForEach(dir => Logger.Info?.Print(LogClass.Application, $"Auto loading DLC & updates from: {dir}")); autoloadDirs.ForEach(dir =>
Logger.Info?.Print(LogClass.Application, $"Auto loading DLC & updates from: {dir}"));
if (autoloadDirs.Count > 0) if (autoloadDirs.Count > 0)
{ {
int updatesLoaded = ApplicationLibrary.AutoLoadTitleUpdates(autoloadDirs, out int updatesRemoved); int updatesLoaded = ApplicationLibrary.AutoLoadTitleUpdates(autoloadDirs, out int updatesRemoved);
@ -721,28 +954,35 @@ namespace Ryujinx.Ava.UI.Windows
Executor.ExecuteBackgroundAsync(ApplicationLibrary.RefreshTotalTimePlayedAsync); Executor.ExecuteBackgroundAsync(ApplicationLibrary.RefreshTotalTimePlayedAsync);
_isLoading = false; _isLoading = false;
}) }) { Name = "GUI.ApplicationLibraryThread", IsBackground = true };
{
Name = "GUI.ApplicationLibraryThread",
IsBackground = true,
};
applicationLibraryThread.Start(); applicationLibraryThread.Start();
} }
private static void ShowNewContentAddedDialog(int numDlcAdded, int numDlcRemoved, int numUpdatesAdded, int numUpdatesRemoved) private static void ShowNewContentAddedDialog(int numDlcAdded, int numDlcRemoved, int numUpdatesAdded,
int numUpdatesRemoved)
{ {
string[] messages = string[] messages =
[ [
numDlcRemoved > 0 ? string.Format(LocaleManager.Instance[LocaleKeys.AutoloadDlcRemovedMessage], numDlcRemoved): null, numDlcRemoved > 0
numDlcAdded > 0 ? string.Format(LocaleManager.Instance[LocaleKeys.AutoloadDlcAddedMessage], numDlcAdded): null, ? string.Format(LocaleManager.Instance[LocaleKeys.AutoloadDlcRemovedMessage], numDlcRemoved)
numUpdatesRemoved > 0 ? string.Format(LocaleManager.Instance[LocaleKeys.AutoloadUpdateRemovedMessage], numUpdatesRemoved): null, : null,
numUpdatesAdded > 0 ? string.Format(LocaleManager.Instance[LocaleKeys.AutoloadUpdateAddedMessage], numUpdatesAdded) : null numDlcAdded > 0
? string.Format(LocaleManager.Instance[LocaleKeys.AutoloadDlcAddedMessage], numDlcAdded)
: null,
numUpdatesRemoved > 0
? string.Format(LocaleManager.Instance[LocaleKeys.AutoloadUpdateRemovedMessage], numUpdatesRemoved)
: null,
numUpdatesAdded > 0
? string.Format(LocaleManager.Instance[LocaleKeys.AutoloadUpdateAddedMessage], numUpdatesAdded)
: null
]; ];
string msg = String.Join("\r\n", messages); string msg = string.Join("\r\n", messages);
if (String.IsNullOrWhiteSpace(msg)) if (string.IsNullOrWhiteSpace(msg))
{
return; return;
}
Dispatcher.UIThread.InvokeAsync(async () => Dispatcher.UIThread.InvokeAsync(async () =>
{ {
@ -762,7 +1002,9 @@ namespace Ryujinx.Ava.UI.Windows
public static async Task ShowIntelMacWarningAsync() public static async Task ShowIntelMacWarningAsync()
{ {
if (_intelMacWarningShown) if (_intelMacWarningShown)
{
return; return;
}
await Dispatcher.UIThread.InvokeAsync(async () => await ContentDialogHelper.CreateWarningDialog( await Dispatcher.UIThread.InvokeAsync(async () => await ContentDialogHelper.CreateWarningDialog(
"Intel Mac Warning", "Intel Mac Warning",
@ -774,10 +1016,14 @@ namespace Ryujinx.Ava.UI.Windows
private void AppWindow_OnGotFocus(object sender, GotFocusEventArgs e) private void AppWindow_OnGotFocus(object sender, GotFocusEventArgs e)
{ {
if (ViewModel.AppHost is null) if (ViewModel.AppHost is null)
{
return; return;
}
if (!_focusLoss.Active) if (!_focusLoss.Active)
{
return; return;
}
switch (_focusLoss.Type) switch (_focusLoss.Type)
{ {
@ -809,7 +1055,9 @@ namespace Ryujinx.Ava.UI.Windows
case FocusLostType.BlockInputAndMuteAudio: case FocusLostType.BlockInputAndMuteAudio:
{ {
if (!ViewModel.AppHost.Device.IsAudioMuted()) if (!ViewModel.AppHost.Device.IsAudioMuted())
{
goto case FocusLostType.BlockInput; goto case FocusLostType.BlockInput;
}
ViewModel.AppHost.Device.SetVolume(ViewModel.VolumeBeforeMute); ViewModel.AppHost.Device.SetVolume(ViewModel.VolumeBeforeMute);
ViewModel.AppHost.NpadManager.UnblockInputUpdates(); ViewModel.AppHost.NpadManager.UnblockInputUpdates();
@ -838,17 +1086,23 @@ namespace Ryujinx.Ava.UI.Windows
private void AppWindow_OnLostFocus(object sender, RoutedEventArgs e) private void AppWindow_OnLostFocus(object sender, RoutedEventArgs e)
{ {
if (ConfigurationState.Instance.FocusLostActionType.Value is FocusLostType.DoNothing) if (ConfigurationState.Instance.FocusLostActionType.Value is FocusLostType.DoNothing)
{
return; return;
}
if (ViewModel.AppHost is null) if (ViewModel.AppHost is null)
{
return; return;
}
switch (ConfigurationState.Instance.FocusLostActionType.Value) switch (ConfigurationState.Instance.FocusLostActionType.Value)
{ {
case FocusLostType.BlockInput: case FocusLostType.BlockInput:
{ {
if (ViewModel.AppHost.NpadManager.InputUpdatesBlocked) if (ViewModel.AppHost.NpadManager.InputUpdatesBlocked)
{
return; return;
}
ViewModel.AppHost.NpadManager.BlockInputUpdates(); ViewModel.AppHost.NpadManager.BlockInputUpdates();
_focusLoss = (FocusLostType.BlockInput, ViewModel.AppHost.NpadManager.InputUpdatesBlocked); _focusLoss = (FocusLostType.BlockInput, ViewModel.AppHost.NpadManager.InputUpdatesBlocked);
@ -857,7 +1111,9 @@ namespace Ryujinx.Ava.UI.Windows
case FocusLostType.MuteAudio: case FocusLostType.MuteAudio:
{ {
if (ViewModel.AppHost.Device.GetVolume() is 0) if (ViewModel.AppHost.Device.GetVolume() is 0)
{
return; return;
}
ViewModel.VolumeBeforeMute = ViewModel.AppHost.Device.GetVolume(); ViewModel.VolumeBeforeMute = ViewModel.AppHost.Device.GetVolume();
ViewModel.AppHost.Device.SetVolume(0); ViewModel.AppHost.Device.SetVolume(0);
@ -867,18 +1123,24 @@ namespace Ryujinx.Ava.UI.Windows
case FocusLostType.BlockInputAndMuteAudio: case FocusLostType.BlockInputAndMuteAudio:
{ {
if (ViewModel.AppHost.Device.GetVolume() is 0) if (ViewModel.AppHost.Device.GetVolume() is 0)
{
goto case FocusLostType.BlockInput; goto case FocusLostType.BlockInput;
}
ViewModel.VolumeBeforeMute = ViewModel.AppHost.Device.GetVolume(); ViewModel.VolumeBeforeMute = ViewModel.AppHost.Device.GetVolume();
ViewModel.AppHost.Device.SetVolume(0); ViewModel.AppHost.Device.SetVolume(0);
ViewModel.AppHost.NpadManager.BlockInputUpdates(); ViewModel.AppHost.NpadManager.BlockInputUpdates();
_focusLoss = (FocusLostType.BlockInputAndMuteAudio, ViewModel.AppHost.Device.GetVolume() is 0f && ViewModel.AppHost.NpadManager.InputUpdatesBlocked); _focusLoss = (FocusLostType.BlockInputAndMuteAudio,
ViewModel.AppHost.Device.GetVolume() is 0f &&
ViewModel.AppHost.NpadManager.InputUpdatesBlocked);
break; break;
} }
case FocusLostType.PauseEmulation: case FocusLostType.PauseEmulation:
{ {
if (ViewModel.AppHost.Device.System.IsPaused) if (ViewModel.AppHost.Device.System.IsPaused)
{
return; return;
}
ViewModel.AppHost.Pause(); ViewModel.AppHost.Pause();
_focusLoss = (FocusLostType.PauseEmulation, ViewModel.AppHost.Device.System.IsPaused); _focusLoss = (FocusLostType.PauseEmulation, ViewModel.AppHost.Device.System.IsPaused);