using System.ComponentModel; using System.Windows; using System.Windows.Interop; using CursorLang.Interop; using CursorLang.Models; using Microsoft.Win32; namespace CursorLang.Services; /// /// Держит в ресурсах приложения палитру выбранной темы и подменяет её /// при смене настройки — окна перекрашиваются без перезапуска. /// public sealed class ThemeService : IThemeService, IDisposable { private const string PersonalizeKey = @"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize"; private readonly AppSettings _settings; private readonly List _windows = []; private ResourceDictionary? _palette; private AppTheme _current; public ThemeService(AppSettings settings) { _settings = settings; _settings.PropertyChanged += OnSettingsChanged; SystemEvents.UserPreferenceChanged += OnUserPreferenceChanged; Apply(); } /// Тема, которую видит пользователь: System здесь уже разрешён. 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(); } /// Тема приложений в настройках Windows. 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 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); } } }