changed settings saving (#3)
Reviewed-on: #3 Co-authored-by: Aleksandr Neychev <alexnejchev73@gmail.com>
This commit was merged in pull request #3.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
using System.Drawing;
|
||||
using System.ComponentModel;
|
||||
using System.Text.Json.Serialization;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
@@ -8,11 +8,10 @@ namespace CursorLang.Core.Models;
|
||||
/// The application settings, as the settings window writes them to settings.json.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The colours are <see cref="System.Drawing.Color"/> rather than
|
||||
/// <c>System.Windows.Media.Color</c>. The former lives in System.Drawing.Primitives,
|
||||
/// which is part of the base runtime and brings neither WPF nor GDI+ along; the latter
|
||||
/// is WindowsBase, and this type is read by the agent, which must stay clear of it.
|
||||
/// The settings window turns them into brushes in its converters.
|
||||
/// Everything about the popup — the side, the offset, the font size, the opacity and
|
||||
/// both colours — belongs to a placement mode rather than to the application, and is
|
||||
/// kept in a <see cref="PopupModeSettings"/> of its own for each of them. So the
|
||||
/// settings of a mode are still there after a trip through the other two.
|
||||
///
|
||||
/// Both processes hold an instance of this, but only the settings window writes: the
|
||||
/// agent re-reads the file and pours the fresh values into the instance it already has,
|
||||
@@ -32,43 +31,6 @@ public sealed partial class AppSettings : ObservableObject
|
||||
[ObservableProperty]
|
||||
private PopupPlacementMode _placementMode = PopupPlacementMode.AtCursor;
|
||||
|
||||
// The side and the offset are stored per mode: the cursor and the caret call for
|
||||
// different settings, and switching the mode does not reset them
|
||||
|
||||
/// <summary>The side of the cursor the popup is put on.</summary>
|
||||
[ObservableProperty]
|
||||
private AnchorSide _cursorSide = AnchorSide.BottomRight;
|
||||
|
||||
/// <summary>The offset from the cursor in WPF units.</summary>
|
||||
[ObservableProperty]
|
||||
private double _cursorOffset = 16;
|
||||
|
||||
/// <summary>The side of the caret the popup is put on.</summary>
|
||||
[ObservableProperty]
|
||||
private AnchorSide _caretSide = AnchorSide.BottomRight;
|
||||
|
||||
/// <summary>The offset from the caret in WPF units.</summary>
|
||||
[ObservableProperty]
|
||||
private double _caretOffset = 16;
|
||||
|
||||
/// <summary>
|
||||
/// The place on the monitor for the <see cref="PopupPlacementMode.FixedPoint"/> mode.
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
private ScreenPosition _screenPosition = ScreenPosition.BottomRight;
|
||||
|
||||
/// <summary>The offset from the monitor edge in WPF units.</summary>
|
||||
[ObservableProperty]
|
||||
private double _screenMargin = 24;
|
||||
|
||||
/// <summary>The size of the layout name in the popup, in WPF units.</summary>
|
||||
[ObservableProperty]
|
||||
private double _fontSize = 20;
|
||||
|
||||
/// <summary>The popup opacity: 1.0 is fully opaque.</summary>
|
||||
[ObservableProperty]
|
||||
private double _opacity = 0.9;
|
||||
|
||||
/// <summary>How long the popup stays on screen, in milliseconds.</summary>
|
||||
[ObservableProperty]
|
||||
private double _durationMilliseconds = 500;
|
||||
@@ -87,13 +49,44 @@ public sealed partial class AppSettings : ObservableObject
|
||||
[ObservableProperty]
|
||||
private double _capsLockHoldMilliseconds = 300;
|
||||
|
||||
/// <summary>The fill of the popup. The opacity is set by <see cref="Opacity"/>.</summary>
|
||||
[ObservableProperty]
|
||||
private Color _backgroundColor = Color.FromArgb(0xFF, 0x20, 0x20, 0x20);
|
||||
public AppSettings()
|
||||
{
|
||||
AtCursor.PropertyChanged += OnModeChanged;
|
||||
AtCaret.PropertyChanged += OnModeChanged;
|
||||
FixedPoint.PropertyChanged += OnModeChanged;
|
||||
}
|
||||
|
||||
/// <summary>The colour of the layout name in the popup.</summary>
|
||||
[ObservableProperty]
|
||||
private Color _foregroundColor = Color.FromArgb(0xFF, 0xFF, 0xFF, 0xFF);
|
||||
/// <summary>The <see cref="PopupPlacementMode.AtCursor"/> mode.</summary>
|
||||
/// <remarks>
|
||||
/// The modes are handed out rather than replaced — the instance is the same for the
|
||||
/// life of the settings, so a binding and a subscription to it hold. That is what
|
||||
/// the creation handling is for: without it the deserializer would want a setter,
|
||||
/// and reading the file would swap the object everything is bound to.
|
||||
/// </remarks>
|
||||
[JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)]
|
||||
public CursorModeSettings AtCursor { get; } = new();
|
||||
|
||||
/// <summary>The <see cref="PopupPlacementMode.AtCaret"/> mode.</summary>
|
||||
[JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)]
|
||||
public CaretModeSettings AtCaret { get; } = new();
|
||||
|
||||
/// <summary>The <see cref="PopupPlacementMode.FixedPoint"/> mode.</summary>
|
||||
[JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)]
|
||||
public FixedPointModeSettings FixedPoint { get; } = new();
|
||||
|
||||
/// <summary>The settings of the mode currently chosen.</summary>
|
||||
/// <remarks>
|
||||
/// The settings window binds the look through here, and the popup asks for it here
|
||||
/// too: what is on screen is always the settings of the mode in force. A change of
|
||||
/// <see cref="PlacementMode"/> announces this as changed, so the bindings follow.
|
||||
/// </remarks>
|
||||
[JsonIgnore]
|
||||
public PopupModeSettings Current => PlacementMode switch
|
||||
{
|
||||
PopupPlacementMode.AtCaret => AtCaret,
|
||||
PopupPlacementMode.FixedPoint => FixedPoint,
|
||||
_ => AtCursor,
|
||||
};
|
||||
|
||||
/// <summary><see cref="DurationMilliseconds"/> as a <see cref="TimeSpan"/>.</summary>
|
||||
[JsonIgnore]
|
||||
@@ -110,25 +103,43 @@ public sealed partial class AppSettings : ObservableObject
|
||||
/// <remarks>
|
||||
/// This is how the agent learns about an edit: the settings window is a separate
|
||||
/// process, so the fresh values arrive as a freshly parsed instance and are poured
|
||||
/// into the one everything is already bound to, rather than replacing it.
|
||||
/// into the one everything is already bound to, rather than replacing it. The modes
|
||||
/// are filled the same way, and for the same reason.
|
||||
/// </remarks>
|
||||
public void CopyFrom(AppSettings other)
|
||||
{
|
||||
Language = other.Language;
|
||||
Theme = other.Theme;
|
||||
PlacementMode = other.PlacementMode;
|
||||
CursorSide = other.CursorSide;
|
||||
CursorOffset = other.CursorOffset;
|
||||
CaretSide = other.CaretSide;
|
||||
CaretOffset = other.CaretOffset;
|
||||
ScreenPosition = other.ScreenPosition;
|
||||
ScreenMargin = other.ScreenMargin;
|
||||
FontSize = other.FontSize;
|
||||
Opacity = other.Opacity;
|
||||
DurationMilliseconds = other.DurationMilliseconds;
|
||||
UseCapsLockHotkey = other.UseCapsLockHotkey;
|
||||
CapsLockHoldMilliseconds = other.CapsLockHoldMilliseconds;
|
||||
BackgroundColor = other.BackgroundColor;
|
||||
ForegroundColor = other.ForegroundColor;
|
||||
|
||||
AtCursor.CopyFrom(other.AtCursor);
|
||||
AtCaret.CopyFrom(other.AtCaret);
|
||||
FixedPoint.CopyFrom(other.FixedPoint);
|
||||
}
|
||||
|
||||
// The mode in force decides what the look means, so a switch of the mode is a
|
||||
// change of everything bound through Current
|
||||
partial void OnPlacementModeChanged(PopupPlacementMode value) => OnPropertyChanged(nameof(Current));
|
||||
|
||||
/// <summary>
|
||||
/// Passes a change made inside a mode on as a change of the settings.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A mode is an object of its own, so an edit in it is not an edit of this one as
|
||||
/// far as <see cref="INotifyPropertyChanged"/> goes. The settings window binds
|
||||
/// through the path and hears the mode itself, but the service that writes the file
|
||||
/// listens to the settings alone — and without this a font size dragged in the
|
||||
/// window would never reach the disk. The name says where the change happened:
|
||||
/// "AtCaret.FontSize".
|
||||
/// </remarks>
|
||||
private void OnModeChanged(object? sender, PropertyChangedEventArgs e) =>
|
||||
OnPropertyChanged($"{NameOfMode(sender)}.{e.PropertyName}");
|
||||
|
||||
private string NameOfMode(object? mode) =>
|
||||
ReferenceEquals(mode, AtCaret) ? nameof(AtCaret)
|
||||
: ReferenceEquals(mode, FixedPoint) ? nameof(FixedPoint)
|
||||
: nameof(AtCursor);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user