lightweight variant (#1)
Reviewed-on: #1 Co-authored-by: Aleksandr Neychev <alexnejchev73@gmail.com>
This commit was merged in pull request #1.
This commit is contained in:
@@ -0,0 +1,207 @@
|
||||
using System.ComponentModel;
|
||||
using System.Drawing;
|
||||
using System.Windows.Data;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CursorLang.Core.Models;
|
||||
using CursorLang.Core.Services;
|
||||
|
||||
namespace CursorLang.Settings.ViewModels;
|
||||
|
||||
/// <summary>
|
||||
/// An option in a list. The caption changes together with the language, while the
|
||||
/// object itself lives for as long as the window does: recreating the list items
|
||||
/// makes the ComboBox drop the selected value.
|
||||
/// </summary>
|
||||
public sealed class EnumOption<T> : ObservableObject where T : struct, Enum
|
||||
{
|
||||
private string _display;
|
||||
|
||||
public EnumOption(T value, string display)
|
||||
{
|
||||
Value = value;
|
||||
_display = display;
|
||||
}
|
||||
|
||||
public T Value { get; }
|
||||
|
||||
public string Display
|
||||
{
|
||||
get => _display;
|
||||
set => SetProperty(ref _display, value);
|
||||
}
|
||||
|
||||
// Accessibility tools take the name of the list item from here
|
||||
public override string ToString() => Display;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The settings window. The values are edited right in <see cref="AppSettings"/>,
|
||||
/// so the popup picks them up at once, without an "Apply" button.
|
||||
/// </summary>
|
||||
public sealed partial class SettingsViewModel : ObservableObject, IDisposable
|
||||
{
|
||||
private static readonly Color[] Palette =
|
||||
[
|
||||
Color.FromArgb(0x20, 0x20, 0x20),
|
||||
Color.FromArgb(0x00, 0x00, 0x00),
|
||||
Color.FromArgb(0x1E, 0x3A, 0x8A),
|
||||
Color.FromArgb(0x0F, 0x76, 0x6E),
|
||||
Color.FromArgb(0x7C, 0x2D, 0x12),
|
||||
Color.FromArgb(0x86, 0x19, 0x8F),
|
||||
Color.FromArgb(0xB9, 0x1C, 0x1C),
|
||||
Color.FromArgb(0xF5, 0xF5, 0xF5),
|
||||
];
|
||||
|
||||
private static readonly Color[] ForegroundPalette =
|
||||
[
|
||||
Color.FromArgb(0xFF, 0xFF, 0xFF),
|
||||
Color.FromArgb(0xD4, 0xD4, 0xD4),
|
||||
Color.FromArgb(0x00, 0x00, 0x00),
|
||||
Color.FromArgb(0xFA, 0xCC, 0x15),
|
||||
Color.FromArgb(0x4A, 0xDE, 0x80),
|
||||
Color.FromArgb(0x60, 0xA5, 0xFA),
|
||||
Color.FromArgb(0xF9, 0x73, 0x16),
|
||||
Color.FromArgb(0xF8, 0x71, 0x71),
|
||||
];
|
||||
|
||||
private readonly IStartupService _startup;
|
||||
|
||||
private StartupState _startupState = StartupState.Unavailable;
|
||||
|
||||
public SettingsViewModel(
|
||||
AppSettings settings,
|
||||
ILocalizationService localization,
|
||||
IStartupService startup,
|
||||
UpdateViewModel updates)
|
||||
{
|
||||
Settings = settings;
|
||||
Localization = localization;
|
||||
Updates = updates;
|
||||
_startup = startup;
|
||||
|
||||
// The interface language is a setting like any other and is stored in the same place
|
||||
Localization.CurrentLanguage = settings.Language;
|
||||
Settings.PropertyChanged += OnSettingsChanged;
|
||||
Localization.PropertyChanged += OnLocalizationChanged;
|
||||
|
||||
Themes = CreateOptions<AppTheme>();
|
||||
PlacementModes = CreateOptions<PopupPlacementMode>();
|
||||
AnchorSides = CreateOptions<AnchorSide>();
|
||||
ScreenPositions = CreateOptions<ScreenPosition>();
|
||||
}
|
||||
|
||||
public AppSettings Settings { get; }
|
||||
|
||||
public ILocalizationService Localization { get; }
|
||||
|
||||
/// <summary>The updates section: it has a state and commands of its own.</summary>
|
||||
public UpdateViewModel Updates { get; }
|
||||
|
||||
public IReadOnlyList<Color> BackgroundPalette { get; } = Palette;
|
||||
|
||||
public IReadOnlyList<Color> TextPalette { get; } = ForegroundPalette;
|
||||
|
||||
public IReadOnlyList<EnumOption<AppTheme>> Themes { get; }
|
||||
|
||||
public IReadOnlyList<EnumOption<PopupPlacementMode>> PlacementModes { get; }
|
||||
|
||||
public IReadOnlyList<EnumOption<AnchorSide>> AnchorSides { get; }
|
||||
|
||||
public IReadOnlyList<EnumOption<ScreenPosition>> ScreenPositions { get; }
|
||||
|
||||
/// <summary>Whether to show the startup setting.</summary>
|
||||
public bool IsStartupAvailable => _startupState != StartupState.Unavailable;
|
||||
|
||||
/// <summary>Startup is up to the application rather than to Windows.</summary>
|
||||
public bool CanChangeStartup => _startupState is StartupState.Enabled or StartupState.Disabled;
|
||||
|
||||
/// <summary>It has to be explained why the setting does not give in.</summary>
|
||||
public bool IsStartupLocked => IsStartupAvailable && !CanChangeStartup;
|
||||
|
||||
/// <summary>
|
||||
/// Start the application together with Windows.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The value is not stored in the application settings: Windows knows it, and it
|
||||
/// may be changed behind our back — in the "Startup apps" section. So the property
|
||||
/// answers from the last known state of the task every time.
|
||||
/// </remarks>
|
||||
public bool RunAtStartup
|
||||
{
|
||||
get => _startupState is StartupState.Enabled or StartupState.EnabledByPolicy;
|
||||
set
|
||||
{
|
||||
if (value == RunAtStartup)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_ = ApplyStartupAsync(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asks Windows about the state of startup. Called after the window is shown: the
|
||||
/// answer has to be waited for, while the settings must open right away.
|
||||
/// </summary>
|
||||
public async Task InitializeAsync() => UpdateStartupState(await _startup.GetStateAsync());
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Settings.PropertyChanged -= OnSettingsChanged;
|
||||
Localization.PropertyChanged -= OnLocalizationChanged;
|
||||
}
|
||||
|
||||
private async Task ApplyStartupAsync(bool enabled) =>
|
||||
UpdateStartupState(await _startup.SetEnabledAsync(enabled));
|
||||
|
||||
private void UpdateStartupState(StartupState state)
|
||||
{
|
||||
_startupState = state;
|
||||
|
||||
// The change is reported even when the state is the same: the checkbox has
|
||||
// already been toggled, and only a re-read value can put it back
|
||||
OnPropertyChanged(nameof(RunAtStartup));
|
||||
OnPropertyChanged(nameof(IsStartupAvailable));
|
||||
OnPropertyChanged(nameof(CanChangeStartup));
|
||||
OnPropertyChanged(nameof(IsStartupLocked));
|
||||
}
|
||||
|
||||
private void OnSettingsChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == nameof(AppSettings.Language))
|
||||
{
|
||||
Localization.CurrentLanguage = Settings.Language;
|
||||
}
|
||||
}
|
||||
|
||||
// The option captions come from the resources, so on a language change we update
|
||||
// only the text — the list items themselves stay the same
|
||||
private void OnLocalizationChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName != Binding.IndexerName)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Translate(Themes);
|
||||
Translate(PlacementModes);
|
||||
Translate(AnchorSides);
|
||||
Translate(ScreenPositions);
|
||||
}
|
||||
|
||||
private EnumOption<T>[] CreateOptions<T>() where T : struct, Enum =>
|
||||
[.. Enum.GetValues<T>().Select(value => new EnumOption<T>(value, GetDisplayName(value)))];
|
||||
|
||||
private void Translate<T>(IReadOnlyList<EnumOption<T>> options) where T : struct, Enum
|
||||
{
|
||||
foreach (EnumOption<T> option in options)
|
||||
{
|
||||
option.Display = GetDisplayName(option.Value);
|
||||
}
|
||||
}
|
||||
|
||||
// The resource key is built from the type name and the value: PopupPlacementMode_AtCursor
|
||||
private string GetDisplayName<T>(T value) where T : struct, Enum =>
|
||||
Localization[$"{typeof(T).Name}_{value}"];
|
||||
}
|
||||
Reference in New Issue
Block a user