145 lines
4.3 KiB
C#
145 lines
4.3 KiB
C#
using System.ComponentModel;
|
|
using System.Windows;
|
|
using System.Windows.Interop;
|
|
using CursorLang.Interop;
|
|
using CursorLang.Models;
|
|
using Microsoft.Win32;
|
|
|
|
namespace CursorLang.Services;
|
|
|
|
/// <summary>
|
|
/// Держит в ресурсах приложения палитру выбранной темы и подменяет её
|
|
/// при смене настройки — окна перекрашиваются без перезапуска.
|
|
/// </summary>
|
|
public sealed class ThemeService : IThemeService, IDisposable
|
|
{
|
|
private const string PersonalizeKey =
|
|
@"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize";
|
|
|
|
private readonly AppSettings _settings;
|
|
private readonly List<Window> _windows = [];
|
|
private ResourceDictionary? _palette;
|
|
private AppTheme _current;
|
|
|
|
public ThemeService(AppSettings settings)
|
|
{
|
|
_settings = settings;
|
|
_settings.PropertyChanged += OnSettingsChanged;
|
|
SystemEvents.UserPreferenceChanged += OnUserPreferenceChanged;
|
|
Apply();
|
|
}
|
|
|
|
/// <summary>Тема, которую видит пользователь: <c>System</c> здесь уже разрешён.</summary>
|
|
public AppTheme CurrentTheme => _current;
|
|
|
|
public void Register(Window window)
|
|
{
|
|
_windows.Add(window);
|
|
window.Closed += OnWindowClosed;
|
|
|
|
if (new WindowInteropHelper(window).Handle == IntPtr.Zero)
|
|
{
|
|
window.SourceInitialized += OnWindowSourceInitialized;
|
|
return;
|
|
}
|
|
|
|
ApplyTitleBar(window);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_settings.PropertyChanged -= OnSettingsChanged;
|
|
SystemEvents.UserPreferenceChanged -= OnUserPreferenceChanged;
|
|
|
|
foreach (Window window in _windows)
|
|
{
|
|
window.Closed -= OnWindowClosed;
|
|
window.SourceInitialized -= OnWindowSourceInitialized;
|
|
}
|
|
|
|
_windows.Clear();
|
|
}
|
|
|
|
/// <summary>Тема приложений в настройках Windows.</summary>
|
|
private static AppTheme DetectSystemTheme()
|
|
{
|
|
try
|
|
{
|
|
using RegistryKey? key = Registry.CurrentUser.OpenSubKey(PersonalizeKey);
|
|
return key?.GetValue("AppsUseLightTheme") is int light && light == 0
|
|
? AppTheme.Dark
|
|
: AppTheme.Light;
|
|
}
|
|
catch (Exception e) when (e is System.Security.SecurityException or UnauthorizedAccessException)
|
|
{
|
|
return AppTheme.Light;
|
|
}
|
|
}
|
|
|
|
private void OnSettingsChanged(object? sender, PropertyChangedEventArgs e)
|
|
{
|
|
if (e.PropertyName == nameof(AppSettings.Theme))
|
|
{
|
|
Apply();
|
|
}
|
|
}
|
|
|
|
// Windows сообщает о смене оформления не из потока интерфейса
|
|
private void OnUserPreferenceChanged(object? sender, UserPreferenceChangedEventArgs e) =>
|
|
Application.Current?.Dispatcher.InvokeAsync(Apply);
|
|
|
|
private void Apply()
|
|
{
|
|
AppTheme theme = _settings.Theme == AppTheme.System ? DetectSystemTheme() : _settings.Theme;
|
|
if (_palette is not null && theme == _current)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_current = theme;
|
|
|
|
var next = new ResourceDictionary
|
|
{
|
|
Source = new Uri($"pack://application:,,,/Themes/{theme}.xaml", UriKind.Absolute),
|
|
};
|
|
|
|
ICollection<ResourceDictionary> dictionaries = Application.Current.Resources.MergedDictionaries;
|
|
dictionaries.Add(next);
|
|
|
|
if (_palette is not null)
|
|
{
|
|
dictionaries.Remove(_palette);
|
|
}
|
|
|
|
_palette = next;
|
|
|
|
foreach (Window window in _windows)
|
|
{
|
|
ApplyTitleBar(window);
|
|
}
|
|
}
|
|
|
|
private void ApplyTitleBar(Window window) =>
|
|
WindowThemeNative.SetDarkTitleBar(
|
|
new WindowInteropHelper(window).Handle,
|
|
_current == AppTheme.Dark);
|
|
|
|
private void OnWindowSourceInitialized(object? sender, EventArgs e)
|
|
{
|
|
if (sender is Window window)
|
|
{
|
|
window.SourceInitialized -= OnWindowSourceInitialized;
|
|
ApplyTitleBar(window);
|
|
}
|
|
}
|
|
|
|
private void OnWindowClosed(object? sender, EventArgs e)
|
|
{
|
|
if (sender is Window window)
|
|
{
|
|
window.Closed -= OnWindowClosed;
|
|
_windows.Remove(window);
|
|
}
|
|
}
|
|
}
|