From 52859f40da07cd3bca9693190781d9bddc9408fb Mon Sep 17 00:00:00 2001 From: Daniel Lerch <36048059+daniel-lerch@users.noreply.github.com> Date: Tue, 26 Nov 2024 16:49:09 +0100 Subject: [PATCH] Load settings in Avalonia desktop project --- src/Vocup.Desktop/App.cs | 15 +++++ src/Vocup.Desktop/DesktopSettingsLoader.cs | 18 +++++ src/Vocup.WinForms/MainForm.cs | 18 +++-- .../Settings/WindowsSettingsLoader.cs | 12 ++-- src/Vocup.WinForms/Util/Extensions.cs | 8 +-- src/Vocup.WinForms/Util/RecentFilesService.cs | 4 +- src/Vocup/App.axaml.cs | 19 +++++- .../Settings/Core/JsonSerializerFactory.cs | 2 +- ...onverter.cs => PixelPointJsonConverter.cs} | 33 ++-------- src/Vocup/Settings/Core/SettingsLoaderBase.cs | 11 +++- src/Vocup/Settings/Model/RecentFile.cs | 4 +- src/Vocup/Settings/VocupSettings.cs | 37 +++++++---- src/Vocup/ViewModels/HostWindowViewModel.cs | 51 ++++++++++++++ src/Vocup/ViewModels/MainViewModel.cs | 16 ++++- src/Vocup/ViewModels/RecentFilesViewModel.cs | 19 ++++++ src/Vocup/Views/HostWindow.axaml | 21 ++++++ src/Vocup/Views/HostWindow.axaml.cs | 66 +++++++++++++++++++ src/Vocup/Views/MainWindow.axaml | 12 ---- src/Vocup/Views/MainWindow.axaml.cs | 11 ---- src/Vocup/Vocup.csproj | 1 + tests/Vocup.UnitTests/SettingsTests.cs | 13 +++- 21 files changed, 304 insertions(+), 87 deletions(-) create mode 100644 src/Vocup.Desktop/App.cs create mode 100644 src/Vocup.Desktop/DesktopSettingsLoader.cs rename src/Vocup/Settings/Core/{RectJsonConverter.cs => PixelPointJsonConverter.cs} (53%) create mode 100644 src/Vocup/ViewModels/HostWindowViewModel.cs create mode 100644 src/Vocup/ViewModels/RecentFilesViewModel.cs create mode 100644 src/Vocup/Views/HostWindow.axaml create mode 100644 src/Vocup/Views/HostWindow.axaml.cs delete mode 100644 src/Vocup/Views/MainWindow.axaml delete mode 100644 src/Vocup/Views/MainWindow.axaml.cs diff --git a/src/Vocup.Desktop/App.cs b/src/Vocup.Desktop/App.cs new file mode 100644 index 0000000..d613458 --- /dev/null +++ b/src/Vocup.Desktop/App.cs @@ -0,0 +1,15 @@ +using Microsoft.Extensions.DependencyInjection; +using Vocup.Settings; +using Vocup.Settings.Core; + +namespace Vocup.Desktop; + +public class App : Vocup.App +{ + protected override void ConfigureSettings(IServiceCollection services) + { + base.ConfigureSettings(services); + + services.AddSingleton, DesktopSettingsLoader>(); + } +} diff --git a/src/Vocup.Desktop/DesktopSettingsLoader.cs b/src/Vocup.Desktop/DesktopSettingsLoader.cs new file mode 100644 index 0000000..0ddd225 --- /dev/null +++ b/src/Vocup.Desktop/DesktopSettingsLoader.cs @@ -0,0 +1,18 @@ +using System; +using System.IO; +using Vocup.Settings; +using Vocup.Settings.Core; + +namespace Vocup.Desktop; + +public class DesktopSettingsLoader : SettingsLoaderBase +{ + public DesktopSettingsLoader() + : base( + new DirectoryInfo( + Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), + "Vocup")), + "settings.2.json") + { } +} diff --git a/src/Vocup.WinForms/MainForm.cs b/src/Vocup.WinForms/MainForm.cs index 7f4b8fd..19bcb46 100644 --- a/src/Vocup.WinForms/MainForm.cs +++ b/src/Vocup.WinForms/MainForm.cs @@ -19,6 +19,7 @@ namespace Vocup; public partial class MainForm : Form, IMainForm { private int lastSearchResult; + private SizeF scaleFactor = new(1, 1); public MainForm() { @@ -143,11 +144,12 @@ private void StoreSettings() _ => throw new ArgumentException($"Unknown FormWindowState {WindowState}") }; - Rectangle logicalBounds = new(bounds.Location, bounds.Size.Multiply(96f / DeviceDpi).Round()); + Program.Settings.WindowWidth = bounds.Width / scaleFactor.Width; + Program.Settings.WindowHeight = bounds.Height / scaleFactor.Height; - Program.Settings.MainFormBounds = logicalBounds.ToAvaloniaRect(); + Program.Settings.WindowPosition = bounds.Location.ToAvaloniaPixelPoint(); - Program.Settings.MainFormWindowState = WindowState.ToAvaloniaWindowState(); + Program.Settings.WindowState = WindowState.ToAvaloniaWindowState(); Program.Settings.MainFormSplitterDistance = SplitContainer.SplitterDistance; } @@ -155,6 +157,8 @@ private void StoreSettings() protected override void ScaleControl(SizeF factor, BoundsSpecified specified) { base.ScaleControl(factor, specified); + + scaleFactor = scaleFactor.Multiply(factor); } /// @@ -162,7 +166,9 @@ protected override void ScaleControl(SizeF factor, BoundsSpecified specified) /// private void RestoreSettings() { - Rectangle mainFormBounds = Program.Settings.MainFormBounds.ToSystemDrawingRect(); + Point position = Program.Settings.WindowPosition.ToSystemDrawingPoint(); + Size size = new((int)(Program.Settings.WindowWidth * scaleFactor.Width), (int)(Program.Settings.WindowHeight * scaleFactor.Height)); + Rectangle mainFormBounds = new(position, size); // check if stored form bound is visible on any screen bool isVisible = false; @@ -180,12 +186,12 @@ private void RestoreSettings() SplitContainer.SplitterDistance = Program.Settings.MainFormSplitterDistance; } - if (isVisible && Program.Settings.MainFormBounds != default) + if (isVisible && mainFormBounds != default) { // visible => restore the bounds of the main form Bounds = mainFormBounds; - FormWindowState windowState = Program.Settings.MainFormWindowState.ToFormWindowState(); + FormWindowState windowState = Program.Settings.WindowState.ToFormWindowState(); // Do not restore the window state when the form was minimzed if (windowState != FormWindowState.Minimized) diff --git a/src/Vocup.WinForms/Settings/WindowsSettingsLoader.cs b/src/Vocup.WinForms/Settings/WindowsSettingsLoader.cs index d51fb8b..12ee010 100644 --- a/src/Vocup.WinForms/Settings/WindowsSettingsLoader.cs +++ b/src/Vocup.WinForms/Settings/WindowsSettingsLoader.cs @@ -71,8 +71,10 @@ private async ValueTask MigrateJsonSettings1(SettingsContext settings settings.Value.EvaluateToleratePunctuationMark = OldSettings.Default.EvaluateToleratePunctuationMark; settings.Value.EvaluateTolerateSpecialChar = OldSettings.Default.EvaluateTolerateSpecialChar; settings.Value.EvaluateTolerateArticle = OldSettings.Default.EvaluateTolerateArticle; - settings.Value.MainFormBounds = OldSettings.Default.MainFormBounds.ToAvaloniaRect(); - settings.Value.MainFormWindowState = OldSettings.Default.MainFormWindowState.ToAvaloniaWindowState(); + settings.Value.WindowWidth = OldSettings.Default.MainFormBounds.Width; + settings.Value.WindowHeight = OldSettings.Default.MainFormBounds.Height; + settings.Value.WindowPosition = OldSettings.Default.MainFormBounds.Location.ToAvaloniaPixelPoint(); + settings.Value.WindowState = OldSettings.Default.MainFormWindowState.ToAvaloniaWindowState(); settings.Value.MainFormSplitterDistance = OldSettings.Default.MainFormSplitterDistance; settings.Value.SpecialCharTab = OldSettings.Default.SpecialCharTab; if (Version.TryParse(OldSettings.Default.Version, out Version? version)) settings.Value.Version = version; diff --git a/src/Vocup.WinForms/Util/Extensions.cs b/src/Vocup.WinForms/Util/Extensions.cs index 6ee92e8..a7b0815 100644 --- a/src/Vocup.WinForms/Util/Extensions.cs +++ b/src/Vocup.WinForms/Util/Extensions.cs @@ -95,14 +95,14 @@ public static System.Drawing.Size ToSystemDrawingSize(this Avalonia.Size size) return new((int)size.Width, (int)size.Height); } - public static Avalonia.Rect ToAvaloniaRect(this System.Drawing.Rectangle rect) + public static Avalonia.PixelPoint ToAvaloniaPixelPoint(this System.Drawing.Point point) { - return new(rect.X, rect.Y, rect.Width, rect.Height); + return new(point.X, point.Y); } - public static System.Drawing.Rectangle ToSystemDrawingRect(this Avalonia.Rect rect) + public static System.Drawing.Point ToSystemDrawingPoint(this Avalonia.PixelPoint point) { - return new((int)rect.X, (int)rect.Y, (int)rect.Width, (int)rect.Height); + return new(point.X, point.Y); } public static Avalonia.Controls.WindowState ToAvaloniaWindowState(this FormWindowState state) diff --git a/src/Vocup.WinForms/Util/RecentFilesService.cs b/src/Vocup.WinForms/Util/RecentFilesService.cs index 2e59ecc..65113d2 100644 --- a/src/Vocup.WinForms/Util/RecentFilesService.cs +++ b/src/Vocup.WinForms/Util/RecentFilesService.cs @@ -26,7 +26,7 @@ public void InteractedWith(string fileName) else { recentFile.LastAccess = DateTime.Now; - recentFile.LastAvalailable = DateTime.Now; + recentFile.LastAvailable = DateTime.Now; } } @@ -41,7 +41,7 @@ private static bool Exists(RecentFile recentFile) bool exists = File.Exists(recentFile.FileName); if (exists) { - recentFile.LastAvalailable = DateTime.Now; + recentFile.LastAvailable = DateTime.Now; } return exists; } diff --git a/src/Vocup/App.axaml.cs b/src/Vocup/App.axaml.cs index 2d6f5e2..972fd3b 100644 --- a/src/Vocup/App.axaml.cs +++ b/src/Vocup/App.axaml.cs @@ -1,7 +1,8 @@ using Avalonia; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; - +using Microsoft.Extensions.DependencyInjection; +using System; using Vocup.ViewModels; using Vocup.Views; @@ -16,11 +17,15 @@ public override void Initialize() public override void OnFrameworkInitializationCompleted() { + ServiceCollection services = new(); + ConfigureSettings(services); + IServiceProvider serviceProvider = services.BuildServiceProvider(); + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { - desktop.MainWindow = new MainWindow + desktop.MainWindow = new HostWindow { - DataContext = new MainViewModel() + DataContext = new HostWindowViewModel(serviceProvider, ConfigureServices) }; } else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform) @@ -33,4 +38,12 @@ public override void OnFrameworkInitializationCompleted() base.OnFrameworkInitializationCompleted(); } + + protected virtual void ConfigureSettings(IServiceCollection services) + { + } + + protected virtual void ConfigureServices(IServiceProvider serviceProvider, IServiceCollection services) + { + } } diff --git a/src/Vocup/Settings/Core/JsonSerializerFactory.cs b/src/Vocup/Settings/Core/JsonSerializerFactory.cs index 06ee71c..0a67f3c 100644 --- a/src/Vocup/Settings/Core/JsonSerializerFactory.cs +++ b/src/Vocup/Settings/Core/JsonSerializerFactory.cs @@ -15,7 +15,7 @@ public class JsonSerializerFactory : ISerializerFactory, IDeserializerFactory public JsonSerializerFactory() { options = new JsonSerializerOptions { WriteIndented = true }; - options.Converters.Add(new RectJsonConverter()); + options.Converters.Add(new PixelPointJsonConverter()); options.Converters.Add(new SizeJsonConverter()); } diff --git a/src/Vocup/Settings/Core/RectJsonConverter.cs b/src/Vocup/Settings/Core/PixelPointJsonConverter.cs similarity index 53% rename from src/Vocup/Settings/Core/RectJsonConverter.cs rename to src/Vocup/Settings/Core/PixelPointJsonConverter.cs index 9acc11d..f90a1bc 100644 --- a/src/Vocup/Settings/Core/RectJsonConverter.cs +++ b/src/Vocup/Settings/Core/PixelPointJsonConverter.cs @@ -5,14 +5,14 @@ namespace Vocup.Settings.Core; -public class RectJsonConverter : JsonConverter +public class PixelPointJsonConverter : JsonConverter { - public override Rect Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override PixelPoint Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { if (reader.TokenType != JsonTokenType.StartObject) throw new JsonException(); - double x = 0, y = 0, width = 0, height = 0; + int x = 0, y = 0; while (reader.Read()) { @@ -23,22 +23,12 @@ public override Rect Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSer if (propertyName == "X") { reader.Read(); - x = reader.GetDouble(); + x = reader.GetInt32(); } else if (propertyName == "Y") { reader.Read(); - y = reader.GetDouble(); - } - else if (propertyName == "Width") - { - reader.Read(); - width = reader.GetDouble(); - } - else if (propertyName == "Height") - { - reader.Read(); - height = reader.GetDouble(); + y = reader.GetInt32(); } else { @@ -47,29 +37,20 @@ public override Rect Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSer } else if (reader.TokenType == JsonTokenType.EndObject) { - return new Rect(x, y, width, height); + return new PixelPoint(x, y); } } throw new JsonException(); } - public override void Write(Utf8JsonWriter writer, Rect value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, PixelPoint value, JsonSerializerOptions options) { writer.WriteStartObject(); - writer.WritePropertyName("X"); writer.WriteNumberValue(value.X); - writer.WritePropertyName("Y"); writer.WriteNumberValue(value.Y); - - writer.WritePropertyName("Width"); - writer.WriteNumberValue(value.Width); - - writer.WritePropertyName("Height"); - writer.WriteNumberValue(value.Height); - writer.WriteEndObject(); } } diff --git a/src/Vocup/Settings/Core/SettingsLoaderBase.cs b/src/Vocup/Settings/Core/SettingsLoaderBase.cs index fe5a54e..4d5c392 100644 --- a/src/Vocup/Settings/Core/SettingsLoaderBase.cs +++ b/src/Vocup/Settings/Core/SettingsLoaderBase.cs @@ -2,9 +2,11 @@ using LostTech.App.DataBinding; using System; using System.ComponentModel; +#if DEBUG +using System.Diagnostics; +#endif using System.IO; using System.Threading.Tasks; -using Vocup.Util; using LSettings = LostTech.App.Settings; namespace Vocup.Settings.Core; @@ -22,7 +24,6 @@ public SettingsLoaderBase(DirectoryInfo directory, string filename) public async ValueTask> LoadAsync() { - await default(HopToThreadPoolAwaitable); // Force blocking IO to run on a background thread directory.Create(); LSettings settings = new(directory, ClonableFreezerFactory.Instance, JsonSerializerFactory.Instance, JsonSerializerFactory.Instance); @@ -33,7 +34,11 @@ public async ValueTask> LoadAsync() { settingsSet = await settings.Load(filename).ConfigureAwait(false); } +#if DEBUG + catch (Exception) when (!Debugger.IsAttached) +#else catch (Exception) +#endif { try { @@ -49,7 +54,7 @@ public async ValueTask> LoadAsync() } settingsSet.Autosave = true; SettingsContext result = new(settings, settingsSet); - if (created) + if (created) await OnSettingsCreated(result).ConfigureAwait(false); return result; } diff --git a/src/Vocup/Settings/Model/RecentFile.cs b/src/Vocup/Settings/Model/RecentFile.cs index f7dcf8b..9221b51 100644 --- a/src/Vocup/Settings/Model/RecentFile.cs +++ b/src/Vocup/Settings/Model/RecentFile.cs @@ -9,7 +9,7 @@ public RecentFile(string fileName, DateTime lastAccess, DateTime lastAvailable) { FileName = fileName; LastAccess = lastAccess; - LastAvalailable = lastAvailable; + LastAvailable = lastAvailable; } public string FileName { get; } @@ -25,7 +25,7 @@ public DateTime LastAccess /// /// Gets or sets the last time the file was available on the file system. /// - public DateTime LastAvalailable + public DateTime LastAvailable { get => _lastAvailable; set => RaiseAndSetIfChanged(ref _lastAvailable, value); diff --git a/src/Vocup/Settings/VocupSettings.cs b/src/Vocup/Settings/VocupSettings.cs index dd54d35..721bc3c 100644 --- a/src/Vocup/Settings/VocupSettings.cs +++ b/src/Vocup/Settings/VocupSettings.cs @@ -15,7 +15,7 @@ public VocupSettings Copy() { return new() { - RecentFiles = new(RecentFiles.Select(x => new RecentFile(x.FileName, x.LastAccess, x.LastAvalailable))), + RecentFiles = new(RecentFiles.Select(x => new RecentFile(x.FileName, x.LastAccess, x.LastAvailable))), _startScreen = _startScreen, _autoSave = _autoSave, _disableInternetServices = _disableInternetServices, @@ -39,8 +39,10 @@ public VocupSettings Copy() _evaluateToleratePunctuationMark = _evaluateToleratePunctuationMark, _evaluateTolerateSpecialChar = _evaluateTolerateSpecialChar, _evaluateTolerateArticle = _evaluateTolerateArticle, - _mainFormBounds = _mainFormBounds, - _mainFormWindowState = _mainFormWindowState, + _windowWidth = _windowWidth, + _windowHeight = _windowHeight, + _windowPosition = _windowPosition, + _windowState = _windowState, _mainFormSplitterDistance = _mainFormSplitterDistance, _specialCharTab = _specialCharTab, _practiceDialogSize = _practiceDialogSize, @@ -236,19 +238,32 @@ public bool EvaluateTolerateArticle #endregion #region UI state - private Rect _mainFormBounds; - public Rect MainFormBounds + private double _windowWidth; + public double WindowWidth { - get => _mainFormBounds; - set => RaiseAndSetIfChanged(ref _mainFormBounds, value); + get => _windowWidth; + set => RaiseAndSetIfChanged(ref _windowWidth, value); } + private double _windowHeight; + public double WindowHeight + { + get => _windowHeight; + set => RaiseAndSetIfChanged(ref _windowHeight, value); + } + + private PixelPoint _windowPosition; + public PixelPoint WindowPosition + { + get => _windowPosition; + set => RaiseAndSetIfChanged(ref _windowPosition, value); + } - private WindowState _mainFormWindowState; - public WindowState MainFormWindowState + private WindowState _windowState; + public WindowState WindowState { - get => _mainFormWindowState; - set => RaiseAndSetIfChanged(ref _mainFormWindowState, value); + get => _windowState; + set => RaiseAndSetIfChanged(ref _windowState, value); } diff --git a/src/Vocup/ViewModels/HostWindowViewModel.cs b/src/Vocup/ViewModels/HostWindowViewModel.cs new file mode 100644 index 0000000..9e126dc --- /dev/null +++ b/src/Vocup/ViewModels/HostWindowViewModel.cs @@ -0,0 +1,51 @@ +using Microsoft.Extensions.DependencyInjection; +using ReactiveUI; +using System; +using Vocup.Settings; +using Vocup.Settings.Core; +using Vocup.Util; + +namespace Vocup.ViewModels; + +public class HostWindowViewModel : ViewModelBase +{ + private readonly IServiceProvider serviceProvider; + private readonly Action configureServices; + + public HostWindowViewModel(IServiceProvider serviceProvider, Action configureServices) + { + this.serviceProvider = serviceProvider; + this.configureServices = configureServices; + Initialize(); + } + + private ViewModelBase? _mainView; + public ViewModelBase? MainView + { + get => _mainView; + private set => this.RaiseAndSetIfChanged(ref _mainView, value); + } + + private VocupSettings? _settings; + public VocupSettings? Settings + { + get => _settings; + private set => this.RaiseAndSetIfChanged(ref _settings, value); + } + + public async void Initialize() + { + await default(HopToThreadPoolAwaitable); // Force blocking IO to run on a background thread + + var settingsLoader = serviceProvider.GetRequiredService>(); + var settingsContext = await settingsLoader.LoadAsync(); + + IServiceCollection services = new ServiceCollection(); + services.AddSingleton(settingsContext); + services.AddSingleton(settingsContext.Value); + configureServices(serviceProvider, services); + + MainView = new MainViewModel(); + Settings = settingsContext.Value; + } +} diff --git a/src/Vocup/ViewModels/MainViewModel.cs b/src/Vocup/ViewModels/MainViewModel.cs index af019ff..41d7218 100644 --- a/src/Vocup/ViewModels/MainViewModel.cs +++ b/src/Vocup/ViewModels/MainViewModel.cs @@ -1,6 +1,20 @@ -namespace Vocup.ViewModels; +using Vocup.Settings; + +namespace Vocup.ViewModels; public class MainViewModel : ViewModelBase { + private readonly VocupSettings? settings; + + public MainViewModel() + { + } + + public MainViewModel(VocupSettings settings) + { + this.settings = settings; + } + public AboutViewModel About { get; } = new(); + public RecentFilesViewModel RecentFiles => new(settings?.RecentFiles ?? []); } diff --git a/src/Vocup/ViewModels/RecentFilesViewModel.cs b/src/Vocup/ViewModels/RecentFilesViewModel.cs new file mode 100644 index 0000000..bd4709e --- /dev/null +++ b/src/Vocup/ViewModels/RecentFilesViewModel.cs @@ -0,0 +1,19 @@ +using System.Collections.ObjectModel; +using Vocup.Settings.Model; + +namespace Vocup.ViewModels; + +public class RecentFilesViewModel +{ + public RecentFilesViewModel() + { + Files = []; + } + + public RecentFilesViewModel(ObservableCollection files) + { + Files = files; + } + + public ObservableCollection Files { get; } +} diff --git a/src/Vocup/Views/HostWindow.axaml b/src/Vocup/Views/HostWindow.axaml new file mode 100644 index 0000000..2c4a15c --- /dev/null +++ b/src/Vocup/Views/HostWindow.axaml @@ -0,0 +1,21 @@ + + + + + + + + + + diff --git a/src/Vocup/Views/HostWindow.axaml.cs b/src/Vocup/Views/HostWindow.axaml.cs new file mode 100644 index 0000000..1da6891 --- /dev/null +++ b/src/Vocup/Views/HostWindow.axaml.cs @@ -0,0 +1,66 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.ReactiveUI; +using Avalonia.Threading; +using ReactiveUI; +using System; +using System.Reactive.Linq; +using Vocup.ViewModels; + +namespace Vocup.Views; + +public partial class HostWindow : ReactiveWindow +{ + public HostWindow() + { + InitializeComponent(); + + this.WhenAnyValue( + x => x.ViewModel, + x => x.ViewModel!.Settings, + (vm, settings) => vm?.Settings + ) + .Subscribe(settings => + { + if (settings != null) + { + Dispatcher.UIThread.Post(() => + { + Position = settings.WindowPosition; + Width = settings.WindowWidth; + Height = settings.WindowHeight; + }); + + // Windows does not remember the original size when the window is maximized in the same operation: + // https://github.com/AvaloniaUI/Avalonia/issues/14517#issuecomment-1930908206 + Dispatcher.UIThread.Post(() => + { + WindowState = settings.WindowState; + + // Subscribe to changes after restoring saved values + PropertyChanged += HostWindow_PropertyChanged; + PositionChanged += HostWindow_PositionChanged; + }); + } + }); + } + + private void HostWindow_PropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) + { + if (ViewModel != null && ViewModel.Settings != null) + { + if (e.Property == HeightProperty) + ViewModel.Settings.WindowHeight = Height; + else if (e.Property == WidthProperty) + ViewModel.Settings.WindowWidth = Width; + else if (e.Property == WindowStateProperty) + ViewModel.Settings.WindowState = WindowState; + } + } + + private void HostWindow_PositionChanged(object? sender, PixelPointEventArgs e) + { + if (ViewModel != null && ViewModel.Settings != null) + ViewModel.Settings.WindowPosition = e.Point; + } +} diff --git a/src/Vocup/Views/MainWindow.axaml b/src/Vocup/Views/MainWindow.axaml deleted file mode 100644 index d7c2c9f..0000000 --- a/src/Vocup/Views/MainWindow.axaml +++ /dev/null @@ -1,12 +0,0 @@ - - - diff --git a/src/Vocup/Views/MainWindow.axaml.cs b/src/Vocup/Views/MainWindow.axaml.cs deleted file mode 100644 index 0519c54..0000000 --- a/src/Vocup/Views/MainWindow.axaml.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Avalonia.Controls; - -namespace Vocup.Views; - -public partial class MainWindow : Window -{ - public MainWindow() - { - InitializeComponent(); - } -} diff --git a/src/Vocup/Vocup.csproj b/src/Vocup/Vocup.csproj index 50d851d..9be4935 100644 --- a/src/Vocup/Vocup.csproj +++ b/src/Vocup/Vocup.csproj @@ -18,6 +18,7 @@ + diff --git a/tests/Vocup.UnitTests/SettingsTests.cs b/tests/Vocup.UnitTests/SettingsTests.cs index 31ef4a1..380401d 100644 --- a/tests/Vocup.UnitTests/SettingsTests.cs +++ b/tests/Vocup.UnitTests/SettingsTests.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using Vocup.Settings; using Vocup.Settings.Core; +using Vocup.Settings.Model; using Xunit; namespace Vocup.UnitTests; @@ -18,7 +19,7 @@ public SettingsTests() { basename = $"vocup_{Guid.NewGuid()}.json"; options = new JsonSerializerOptions { WriteIndented = true }; - options.Converters.Add(new RectJsonConverter()); + options.Converters.Add(new PixelPointJsonConverter()); options.Converters.Add(new SizeJsonConverter()); } @@ -28,7 +29,12 @@ public async Task TestCreateSettings() SettingsLoaderBase loader = new(new(directory), basename); SettingsContext settings = await loader.LoadAsync(); + DateTime timestamp = DateTime.Now; + settings.Value.StartupCounter = 1; + settings.Value.WindowPosition = new(10, 10); + settings.Value.PracticeDialogSize = new(.5,.5); + settings.Value.RecentFiles.Add(new("test.txt", timestamp, timestamp)); await settings.DisposeAsync(); @@ -39,6 +45,11 @@ public async Task TestCreateSettings() Assert.NotNull(settingsValue); Assert.Equal(1, settingsValue.StartupCounter); + Assert.Equal(new(10, 10), settingsValue.WindowPosition); + Assert.Equal(new(.5, .5), settingsValue.PracticeDialogSize); + var recentFile = Assert.Single(settingsValue.RecentFiles); + + Assert.Equivalent(new RecentFile("test.txt", timestamp, timestamp), recentFile); } [Fact]