Browse Source

Save window position/size on resize & exit, and restore on startup

pull/14/head
JT 1 year ago
parent
commit
4663d07f8c
  1. 3
      StabilityMatrix/App.xaml.cs
  2. 1
      StabilityMatrix/Helper/ISettingsManager.cs
  3. 181
      StabilityMatrix/Helper/ScreenExtensions.cs
  4. 17
      StabilityMatrix/Helper/SettingsManager.cs
  5. 51
      StabilityMatrix/Helper/UpdateHelper.cs
  6. 8
      StabilityMatrix/MainWindow.xaml
  7. 61
      StabilityMatrix/MainWindow.xaml.cs
  8. 2
      StabilityMatrix/Models/Settings/GlobalSettings.cs
  9. 2
      StabilityMatrix/Models/Settings/Settings.cs
  10. 6
      StabilityMatrix/Models/Settings/WindowSettings.cs
  11. 1
      StabilityMatrix/StabilityMatrix.csproj
  12. 7
      StabilityMatrix/ViewModels/MainWindowViewModel.cs

3
StabilityMatrix/App.xaml.cs

@ -339,9 +339,6 @@ namespace StabilityMatrix
var window = serviceProvider.GetRequiredService<MainWindow>();
window.Show();
var updateHelper = serviceProvider.GetRequiredService<IUpdateHelper>();
updateHelper.StartCheckingForUpdates();
}
private void App_OnExit(object sender, ExitEventArgs e)

1
StabilityMatrix/Helper/ISettingsManager.cs

@ -67,4 +67,5 @@ public interface ISettingsManager
/// </summary>
void SetPortableMode();
void SaveSettings();
void SetPlacement(string placementStr);
}

181
StabilityMatrix/Helper/ScreenExtensions.cs

@ -0,0 +1,181 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Windows;
namespace StabilityMatrix.Helper;
public static class ScreenExtensions
{
public const string User32 = "user32.dll";
public const string shcore = "Shcore.dll";
public static void GetDpi(this System.Windows.Forms.Screen screen, DpiType dpiType,
out uint dpiX, out uint dpiY)
{
var pnt = new System.Drawing.Point(screen.Bounds.Left + 1, screen.Bounds.Top + 1);
var mon = MonitorFromPoint(pnt, 2 /*MONITOR_DEFAULTTONEAREST*/);
GetDpiForMonitor(mon, dpiType, out dpiX, out dpiY);
}
public static double GetScalingForPoint(System.Drawing.Point aPoint)
{
var mon = MonitorFromPoint(aPoint, 2 /*MONITOR_DEFAULTTONEAREST*/);
uint dpiX, dpiY;
GetDpiForMonitor(mon, DpiType.Effective, out dpiX, out dpiY);
return (double) dpiX / 96.0;
}
[DllImport(User32)]
private static extern IntPtr MonitorFromPoint([In] System.Drawing.Point pt, [In] uint dwFlags);
[DllImport(shcore)]
private static extern IntPtr GetDpiForMonitor([In] IntPtr hmonitor, [In] DpiType dpiType,
[Out] out uint dpiX, [Out] out uint dpiY);
[DllImport(User32, CharSet = CharSet.Auto)]
[ResourceExposure(ResourceScope.None)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetWindowPlacement(IntPtr hWnd, ref WINDOWPLACEMENT lpwndpl);
[DllImport(User32, CharSet = CharSet.Auto, SetLastError = true)]
[ResourceExposure(ResourceScope.None)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool SetWindowPlacement(IntPtr hWnd, [In] ref WINDOWPLACEMENT lpwndpl);
public enum DpiType
{
Effective = 0,
Angular = 1,
Raw = 2,
}
public static WINDOWPLACEMENT GetPlacement(IntPtr hWnd)
{
WINDOWPLACEMENT placement = new WINDOWPLACEMENT();
placement.length = Marshal.SizeOf(placement);
GetWindowPlacement(hWnd, ref placement);
return placement;
}
public static bool SetPlacement(IntPtr hWnd, WINDOWPLACEMENT aPlacement)
{
bool erg = SetWindowPlacement(hWnd, ref aPlacement);
return erg;
}
[StructLayout(LayoutKind.Sequential)]
public struct POINTSTRUCT
{
public int x;
public int y;
public POINTSTRUCT(int x, int y)
{
this.x = x;
this.y = y;
}
}
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int left;
public int top;
public int right;
public int bottom;
public RECT(int left, int top, int right, int bottom)
{
this.left = left;
this.top = top;
this.right = right;
this.bottom = bottom;
}
public RECT(Rect r)
{
this.left = (int) r.Left;
this.top = (int) r.Top;
this.right = (int) r.Right;
this.bottom = (int) r.Bottom;
}
public static RECT FromXYWH(int x, int y, int width, int height)
{
return new RECT(x, y, x + width, y + height);
}
public System.Windows.Size Size
{
get { return new System.Windows.Size(this.right - this.left, this.bottom - this.top); }
}
}
[StructLayout(LayoutKind.Sequential)]
public struct WINDOWPLACEMENT
{
public int length;
public uint flags;
public uint showCmd;
public POINTSTRUCT ptMinPosition;
public POINTSTRUCT ptMaxPosition;
public RECT rcNormalPosition;
public override string ToString()
{
byte[] StructBytes = RawSerialize(this);
return System.Convert.ToBase64String(StructBytes);
}
public void ReadFromBase64String(string aB64)
{
byte[] b64 = System.Convert.FromBase64String(aB64);
var NewWP = ReadStruct<WINDOWPLACEMENT>(b64, 0);
length = NewWP.length;
flags = NewWP.flags;
showCmd = NewWP.showCmd;
ptMinPosition.x = NewWP.ptMinPosition.x;
ptMinPosition.y = NewWP.ptMinPosition.y;
ptMaxPosition.x = NewWP.ptMaxPosition.x;
ptMaxPosition.y = NewWP.ptMaxPosition.y;
rcNormalPosition.left = NewWP.rcNormalPosition.left;
rcNormalPosition.top = NewWP.rcNormalPosition.top;
rcNormalPosition.right = NewWP.rcNormalPosition.right;
rcNormalPosition.bottom = NewWP.rcNormalPosition.bottom;
}
static public T ReadStruct<T>(byte[] aSrcBuffer, int aOffset)
{
byte[] buffer = new byte[Marshal.SizeOf(typeof(T))];
Buffer.BlockCopy(aSrcBuffer, aOffset, buffer, 0, Marshal.SizeOf(typeof(T)));
GCHandle handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
T temp = (T) Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
handle.Free();
return temp;
}
static public T ReadStruct<T>(Stream fs)
{
byte[] buffer = new byte[Marshal.SizeOf(typeof(T))];
fs.Read(buffer, 0, Marshal.SizeOf(typeof(T)));
GCHandle handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
T temp = (T) Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
handle.Free();
return temp;
}
public static byte[] RawSerialize(object anything)
{
int rawsize = Marshal.SizeOf(anything);
byte[] rawdata = new byte[rawsize];
GCHandle handle = GCHandle.Alloc(rawdata, GCHandleType.Pinned);
Marshal.StructureToPtr(anything, handle.AddrOfPinnedObject(), false);
handle.Free();
return rawdata;
}
}
}

17
StabilityMatrix/Helper/SettingsManager.cs

@ -6,6 +6,7 @@ using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using NLog;
using Refit;
using StabilityMatrix.Models;
using StabilityMatrix.Models.Settings;
using StabilityMatrix.Python;
@ -353,6 +354,12 @@ public class SettingsManager : ISettingsManager
var json = JsonSerializer.Serialize(globalSettings);
File.WriteAllText(GlobalSettingsPath, json);
}
public void SetPlacement(string placementStr)
{
Settings.Placement = placementStr;
SaveSettings();
}
/// <summary>
/// Loads settings from the settings file
@ -374,10 +381,12 @@ public class SettingsManager : ISettingsManager
}
var settingsContent = File.ReadAllText(SettingsPath);
Settings = JsonSerializer.Deserialize<Settings>(settingsContent, new JsonSerializerOptions
{
Converters = { new JsonStringEnumConverter() }
})!;
var modifiedDefaultSerializerOptions =
SystemTextJsonContentSerializer.GetDefaultJsonSerializerOptions();
modifiedDefaultSerializerOptions.Converters.Add(new JsonStringEnumConverter());
Settings =
JsonSerializer.Deserialize<Settings>(settingsContent,
modifiedDefaultSerializerOptions)!;
}
finally
{

51
StabilityMatrix/Helper/UpdateHelper.cs

@ -57,33 +57,40 @@ public class UpdateHelper : IUpdateHelper
private async Task CheckForUpdate()
{
var httpClient = httpClientFactory.CreateClient("UpdateClient");
var response = await httpClient.GetAsync("https://cdn.lykos.ai/update.json");
if (!response.IsSuccessStatusCode)
try
{
logger.LogError("Error while checking for update");
return;
}
var httpClient = httpClientFactory.CreateClient("UpdateClient");
var response = await httpClient.GetAsync("https://cdn.lykos.ai/update.json");
if (!response.IsSuccessStatusCode)
{
logger.LogError("Error while checking for update");
return;
}
var updateInfo =
await JsonSerializer.DeserializeAsync<UpdateInfo>(
await response.Content.ReadAsStreamAsync());
var updateInfo =
await JsonSerializer.DeserializeAsync<UpdateInfo>(
await response.Content.ReadAsStreamAsync());
if (updateInfo == null)
{
logger.LogError("UpdateInfo is null");
return;
}
if (updateInfo == null)
{
logger.LogError("UpdateInfo is null");
return;
}
var currentVersion = Assembly.GetExecutingAssembly().GetName().Version;
var currentVersion = Assembly.GetExecutingAssembly().GetName().Version;
if (updateInfo.Version <= currentVersion)
if (updateInfo.Version <= currentVersion)
{
logger.LogInformation("No update available");
return;
}
logger.LogInformation("Update available");
EventManager.Instance.OnUpdateAvailable(updateInfo);
}
catch (Exception e)
{
logger.LogInformation("No update available");
return;
logger.LogError(e, "Couldn't check for update");
}
logger.LogInformation("Update available");
EventManager.Instance.OnUpdateAvailable(updateInfo);
}
}

8
StabilityMatrix/MainWindow.xaml

@ -1,20 +1,24 @@
<ui:FluentWindow
Closing="MainWindow_OnClosing"
Closed="MainWindow_OnClosed"
ExtendsContentIntoTitleBar="True"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
Height="700"
Icon="pack://application:,,,/Assets/Icon.ico"
Loaded="MainWindow_OnLoaded"
Title="Stability Matrix"
UseLayoutRounding="True"
Width="1100"
WindowBackdropType="Mica"
WindowStartupLocation="CenterScreen"
d:DataContext="{d:DesignInstance Type=viewModels:MainWindowViewModel,
IsDesignTimeCreatable=True}"
d:DesignHeight="750"
d:DesignWidth="1100"
MinHeight="400"
MinWidth="600"
Height="700"
Width="1100"
mc:Ignorable="d"
ResizeMode="CanResize"
ui:Design.Background="{DynamicResource ApplicationBackgroundBrush}"
ui:Design.Foreground="{DynamicResource TextFillColorPrimaryBrush}"
x:Class="StabilityMatrix.MainWindow"

61
StabilityMatrix/MainWindow.xaml.cs

@ -1,12 +1,19 @@
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Reactive.Linq;
using System.Threading;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Interop;
using CommunityToolkit.Mvvm.ComponentModel;
using StabilityMatrix.Helper;
using StabilityMatrix.Services;
using StabilityMatrix.ViewModels;
using Wpf.Ui.Contracts;
using Wpf.Ui.Controls.Navigation;
using Wpf.Ui.Controls.Window;
using Application = System.Windows.Application;
using EventManager = StabilityMatrix.Helper.EventManager;
using ISnackbarService = Wpf.Ui.Contracts.ISnackbarService;
@ -21,7 +28,8 @@ namespace StabilityMatrix
private readonly ISettingsManager settingsManager;
public MainWindow(IPageService pageService, IContentDialogService contentDialogService,
MainWindowViewModel mainWindowViewModel, ISettingsManager settingsManager, ISnackbarService snackbarService, INotificationBarService notificationBarService)
MainWindowViewModel mainWindowViewModel, ISettingsManager settingsManager,
ISnackbarService snackbarService, INotificationBarService notificationBarService)
{
InitializeComponent();
@ -36,7 +44,7 @@ namespace StabilityMatrix
snackbarService.SetSnackbarControl(RootSnackbar);
notificationBarService.SetSnackbarControl(NotificationSnackbar);
contentDialogService.SetContentPresenter(RootContentDialog);
EventManager.Instance.PageChangeRequested += InstanceOnPageChangeRequested;
}
@ -50,6 +58,8 @@ namespace StabilityMatrix
RootNavigation.Navigate(typeof(LaunchPage));
RootNavigation.IsPaneOpen = settingsManager.Settings.IsNavExpanded;
await mainWindowViewModel.OnLoaded();
ResizeWindow();
ObserveSizeChanged();
}
private void RootNavigation_OnPaneOpened(NavigationView sender, RoutedEventArgs args)
@ -72,5 +82,52 @@ namespace StabilityMatrix
{
Application.Current.Shutdown();
}
private void ObserveSizeChanged()
{
var observableSizeChanges = Observable
.FromEventPattern<SizeChangedEventHandler, SizeChangedEventArgs>(
h => SizeChanged += h,
h => SizeChanged -= h)
.Select(x => x.EventArgs)
.Throttle(TimeSpan.FromMilliseconds(150));
observableSizeChanges
.ObserveOn(SynchronizationContext.Current)
.Subscribe(args =>
{
if (args is {HeightChanged: false, WidthChanged: false}) return;
var interopHelper = new WindowInteropHelper(this);
var placement = ScreenExtensions.GetPlacement(interopHelper.Handle);
settingsManager.SetPlacement(placement.ToString());
});
}
private void ResizeWindow()
{
var interopHelper = new WindowInteropHelper(this);
if (string.IsNullOrWhiteSpace(settingsManager.Settings.Placement))
return;
var placement = new ScreenExtensions.WINDOWPLACEMENT();
placement.ReadFromBase64String(settingsManager.Settings.Placement);
var primaryMonitorScaling = ScreenExtensions.GetScalingForPoint(new System.Drawing.Point(1, 1));
var currentMonitorScaling = ScreenExtensions.GetScalingForPoint(new System.Drawing.Point(placement.rcNormalPosition.left, placement.rcNormalPosition.top));
var rescaleFactor = currentMonitorScaling / primaryMonitorScaling;
double width = placement.rcNormalPosition.right - placement.rcNormalPosition.left;
double height = placement.rcNormalPosition.bottom - placement.rcNormalPosition.top;
placement.rcNormalPosition.right = placement.rcNormalPosition.left + (int)(width / rescaleFactor + 0.5);
placement.rcNormalPosition.bottom = placement.rcNormalPosition.top + (int)(height / rescaleFactor + 0.5);
ScreenExtensions.SetPlacement(interopHelper.Handle, placement);
}
private void MainWindow_OnClosing(object? sender, CancelEventArgs e)
{
var interopHelper = new WindowInteropHelper(this);
var placement = ScreenExtensions.GetPlacement(interopHelper.Handle);
settingsManager.SetPlacement(placement.ToString());
}
}
}

2
StabilityMatrix/Models/Settings/GlobalSettings.cs

@ -1,6 +1,6 @@
namespace StabilityMatrix.Models.Settings;
public class GlobalSettings
public record GlobalSettings
{
public bool EulaAccepted { get; set; }
}

2
StabilityMatrix/Models/Settings/Settings.cs

@ -27,7 +27,7 @@ public class Settings
SharedFolderType.Lora |
SharedFolderType.LyCORIS;
public WindowSettings? WindowSettings { get; set; }
public string? Placement { get; set; }
public InstalledPackage? GetActiveInstalledPackage()
{

6
StabilityMatrix/Models/Settings/WindowSettings.cs

@ -1,7 +1,3 @@
namespace StabilityMatrix.Models.Settings;
public class WindowSettings
{
public double Width { get; set; }
public double Height { get; set; }
}
public record WindowSettings(double Width, double Height);

1
StabilityMatrix/StabilityMatrix.csproj

@ -43,6 +43,7 @@
<PackageReference Include="Sentry" Version="3.33.1" />
<PackageReference Include="Sentry.NLog" Version="3.33.1" />
<PackageReference Include="SharpCompress" Version="0.33.0" />
<PackageReference Include="System.Reactive" Version="6.0.0" />
<PackageReference Include="WebView2.Runtime.AutoInstaller" Version="1.0.0" />
<PackageReference Include="WPF-UI" Version="3.0.0-preview.2" />
<PackageReference Include="pythonnet" Version="3.0.1" />

7
StabilityMatrix/ViewModels/MainWindowViewModel.cs

@ -1,10 +1,12 @@
using System;
using System.Dynamic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
using System.Windows.Shell;
using AsyncAwaitBestPractices;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Microsoft.Extensions.Options;
@ -25,6 +27,7 @@ public partial class MainWindowViewModel : ObservableObject
private readonly IDialogFactory dialogFactory;
private readonly INotificationBarService notificationBarService;
private readonly UpdateWindowViewModel updateWindowViewModel;
private readonly IUpdateHelper updateHelper;
private readonly DebugOptions debugOptions;
private UpdateInfo? updateInfo;
@ -34,12 +37,14 @@ public partial class MainWindowViewModel : ObservableObject
IDialogFactory dialogFactory,
INotificationBarService notificationBarService,
UpdateWindowViewModel updateWindowViewModel,
IUpdateHelper updateHelper,
IOptions<DebugOptions> debugOptions)
{
this.settingsManager = settingsManager;
this.dialogFactory = dialogFactory;
this.notificationBarService = notificationBarService;
this.updateWindowViewModel = updateWindowViewModel;
this.updateHelper = updateHelper;
this.debugOptions = debugOptions.Value;
// Listen to dev mode event
@ -70,6 +75,8 @@ public partial class MainWindowViewModel : ObservableObject
IsUpdateAvailable = true;
updateInfo = args;
};
updateHelper.StartCheckingForUpdates().SafeFireAndForget();
// show path selection window if no paths are set
await DoSettingsCheck();

Loading…
Cancel
Save