Skip to content

Commit

Permalink
Add settings files
Browse files Browse the repository at this point in the history
  • Loading branch information
Mattias1 committed Aug 28, 2023
1 parent b3a35d2 commit 6c47768
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 4 deletions.
4 changes: 4 additions & 0 deletions AvaloniaExtensions/CanvasComponentBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ public void SwitchToComponent<T>() {
FindComponent<ExtendedWindow>(this).SwitchToComponent<T>();
}

public T GetSettings<T>() where T : class {
return FindComponent<ExtendedWindow>(this).GetSettings<T>();
}

public void Quit() {
if (Application.Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktopApp) {
throw new InvalidOperationException("This method only works for desktop applications");
Expand Down
72 changes: 72 additions & 0 deletions AvaloniaExtensions/ExtendedWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,23 @@
using Avalonia.Markup.Declarative;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.Json;

namespace AvaloniaExtensions;

public sealed class ExtendedWindow : Window {
private readonly Dictionary<Type, ViewBase> _components;
private readonly Dictionary<Type, Func<ViewBase>> _lazyComponents;
private readonly Dictionary<Type, SettingsFile> _settingsFiles;

private ExtendedWindow() {
_components = new Dictionary<Type, ViewBase>();
_lazyComponents = new Dictionary<Type, Func<ViewBase>>();
_settingsFiles = new Dictionary<Type, SettingsFile>();
}

// --- Miscellaneous functions ---
public ExtendedWindow WithSize(Size size, Size minSize) {
MinWidth = minSize.Width;
MinHeight = minSize.Height;
Expand All @@ -27,6 +32,7 @@ public ExtendedWindow WithSize(double width, double height) {
return this;
}

// --- Components (that make up the 'screens' or 'views' of the application) ---
public void SwitchToComponent<T>() {
Content = FindComponent<T>();
}
Expand Down Expand Up @@ -61,10 +67,76 @@ public ExtendedWindow AddLazyComponent<T>(Func<T> componentFunc) where T : ViewB
return this;
}

// --- Settings files ---
protected override void OnClosing(WindowClosingEventArgs e) {
SaveSettings();
base.OnClosing(e);
}

/// <summary>
/// Add a settings file. It'll save the settings file when closing the app.
/// </summary>
/// <param name="path">The location of the settings file. You can use './filename.json' to save it in the apps dir.</param>
/// <typeparam name="T">The type of the settings class</typeparam>
/// <returns></returns>
public ExtendedWindow WithSettingsFile<T>(string path) where T : class, new() {
var settings = LoadOrCreateSettings<T>(path);
_settingsFiles.Add(typeof(T), new SettingsFile(path, settings));
return this;
}

private T LoadOrCreateSettings<T>(string path) where T : class, new() {
try {
using var stream = File.OpenRead(CompletePath(path));
var result = JsonSerializer.Deserialize<T>(stream);
return result ?? new T();
} catch (Exception e) {
Console.Error.WriteLine("An avalonia extensions app encountered an error while loading a settings file." +
$"Settings type: '{typeof(T)}', error: '{e.Message}'.");
return new T();
}
}

private void SaveSettings() {
foreach (var (type, settingsFile) in _settingsFiles) {
try {
using var stream = File.Create(CompletePath(settingsFile.Path));
JsonSerializer.Serialize(stream, settingsFile.Settings);
} catch (Exception e) {
Console.Error.WriteLine("An avalonia extensions app encountered an error while saving a settings file." +
$"Settings type: '{type}', error: '{e.Message}'.");
}
}
}

private string CompletePath(string path) {
if (string.IsNullOrWhiteSpace(path)) {
path = "./settings.json";
}
if (path.Substring(0, 2) == "./") {
path = (AssetExtensions.StartupPath ?? "") + path.Substring(1);
}
return path;
}

public T GetSettings<T>() where T : class {
if (!_settingsFiles.TryGetValue(typeof(T), out SettingsFile? settingsFile)) {
throw new InvalidOperationException($"Cannot find settings with type {typeof(T)}. " +
$"You can add it with the '{nameof(WithSettingsFile)}' method.");
}
if (settingsFile.Settings is not T settings) {
throw new InvalidOperationException($"Cannot cast settings with type {typeof(T)} to its type, weird.");
}
return settings;
}

// --- Initialisation ---
public static ExtendedWindow Init<T>(string windowTitle) where T : ViewBase, new() => Init(windowTitle, new T());
public static ExtendedWindow Init<T>(string windowTitle, T initialComponent) where T : ViewBase {
var window = new ExtendedWindow().AddInitialComponent(initialComponent);
window.Title = windowTitle;
return window;
}

private record SettingsFile(string Path, object Settings);
}
1 change: 1 addition & 0 deletions ExampleApp/MainComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ protected override void InitializeControls() {
AddImage(AssetExtensions.LoadBitmap("assets/smiley.png")).TopCenterInPanel().YCenter(btnHelloWorld);

AddButton("Settings", _ => SwitchToComponent<SettingsComponent>()).BottomRightInPanel();
AddButton("Quit", _ => Quit()).LeftOf();

AddRadio("r-group-1", "Left 2", AddTextIfChecked("Left radio two")).BottomLeftInPanel();
var radioLeft1 = AddRadio("r-group-1", "Left 1", AddTextIfChecked("Left radio one")).Above();
Expand Down
1 change: 1 addition & 0 deletions ExampleApp/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@

AppBuilderExtensions.Init().StartDesktopApp(() => ExtendedWindow.Init<MainComponent>("Example app")
.AddLazyComponent<SettingsComponent>()
.WithSettingsFile<SettingsComponent.ExampleSettings>("./example-settings.json")
.WithSize(size: new Size(800, 500), minSize: new Size(600, 350))
.Icon(AssetExtensions.LoadWindowIcon("assets/smiley.png")));
38 changes: 34 additions & 4 deletions ExampleApp/SettingsComponent.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,43 @@
using Avalonia.Controls;
using Avalonia.Interactivity;
using AvaloniaExtensions;

namespace ExampleApp;

public class SettingsComponent : CanvasComponentBase {
private ExampleSettings? _settings;
private ExampleSettings Settings => _settings ??= GetSettings<ExampleSettings>();

private CheckBox _cbExampleToggle = null!;

protected override void InitializeControls() {
AddTextBlock("This is an imaginary settings component").TopLeftInPanel();
AddButton("Cancel", _ => SwitchToComponent<MainComponent>()).BottomRightInPanel();
AddButton("Ok", _ => SwitchToComponent<MainComponent>()).LeftOf();
AddTextBlock("This is a simple settings component").TopLeftInPanel();
_cbExampleToggle = AddCheckBox("Something to toggle").Below();

AddButton("Cancel", OnCancelClick).BottomRightInPanel();
AddButton("Ok", OnSaveClick).LeftOf();
}

protected override void OnInitialized() {
base.OnInitialized();
LoadSettings();
}

private void OnSaveClick(RoutedEventArgs e) {
Settings.ExampleToggle = _cbExampleToggle.IsChecked ?? false;
SwitchToComponent<MainComponent>();
}

private void OnCancelClick(RoutedEventArgs e) {
LoadSettings();
SwitchToComponent<MainComponent>();
}

private void LoadSettings() {
_cbExampleToggle.IsChecked = Settings.ExampleToggle;
}

AddButton("Quit", _ => Quit()).BottomLeftInPanel();
public class ExampleSettings {
public bool ExampleToggle { get; set; }
}
}

0 comments on commit 6c47768

Please sign in to comment.