using System.Drawing; using System.Text.Json.Serialization; using CommunityToolkit.Mvvm.ComponentModel; namespace CursorLang.Core.Models; /// /// The application settings, as the settings window writes them to settings.json. /// /// /// The colours are rather than /// System.Windows.Media.Color. 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. /// /// 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, /// so everything subscribed to it stays subscribed. See . /// public sealed partial class AppSettings : ObservableObject { /// The interface language as a culture code: "ru", "en". [ObservableProperty] private string _language = "en"; /// The look of the settings window. [ObservableProperty] private AppTheme _theme = AppTheme.System; /// Where the popup is shown: at the cursor, at the caret or at a fixed point. [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 /// The side of the cursor the popup is put on. [ObservableProperty] private AnchorSide _cursorSide = AnchorSide.BottomRight; /// The offset from the cursor in WPF units. [ObservableProperty] private double _cursorOffset = 16; /// The side of the caret the popup is put on. [ObservableProperty] private AnchorSide _caretSide = AnchorSide.BottomRight; /// The offset from the caret in WPF units. [ObservableProperty] private double _caretOffset = 16; /// /// The place on the monitor for the mode. /// [ObservableProperty] private ScreenPosition _screenPosition = ScreenPosition.BottomRight; /// The offset from the monitor edge in WPF units. [ObservableProperty] private double _screenMargin = 24; /// The size of the layout name in the popup, in WPF units. [ObservableProperty] private double _fontSize = 20; /// The popup opacity: 1.0 is fully opaque. [ObservableProperty] private double _opacity = 0.9; /// How long the popup stays on screen, in milliseconds. [ObservableProperty] private double _durationMilliseconds = 500; /// /// Intercept Caps Lock and switch the layout with it instead of changing the case. /// Off by default: the application must not change the behaviour of the system /// until it is asked to. /// [ObservableProperty] private bool _useCapsLockHotkey; /// /// After how long a Caps Lock hold cancels the switch, in milliseconds. /// [ObservableProperty] private double _capsLockHoldMilliseconds = 300; /// /// Ask the repository about new versions on startup. A check can always be /// started by hand — this setting turns off only the automatic one. /// [ObservableProperty] private bool _checkForUpdates = true; /// /// When the application last asked about new versions successfully. /// Stored so as not to go to the network on every startup. /// [ObservableProperty] private DateTimeOffset? _lastUpdateCheck; /// The fill of the popup. The opacity is set by . [ObservableProperty] private Color _backgroundColor = Color.FromArgb(0xFF, 0x20, 0x20, 0x20); /// The colour of the layout name in the popup. [ObservableProperty] private Color _foregroundColor = Color.FromArgb(0xFF, 0xFF, 0xFF, 0xFF); /// as a . [JsonIgnore] public TimeSpan Duration => TimeSpan.FromMilliseconds(DurationMilliseconds); /// as a . [JsonIgnore] public TimeSpan CapsLockHoldDelay => TimeSpan.FromMilliseconds(CapsLockHoldMilliseconds); /// /// Takes the values of another instance over, raising a change notification for /// every property that has actually moved. /// /// /// 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. /// 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; CheckForUpdates = other.CheckForUpdates; LastUpdateCheck = other.LastUpdateCheck; BackgroundColor = other.BackgroundColor; ForegroundColor = other.ForegroundColor; } }