using Lattice.Themes.Core.Tokens; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Media; namespace Lattice.Themes; /// /// Менеджер тем для Lattice Framework. /// public sealed class ThemeManager { public static ThemeManager Current { get; } = new(); private ThemePack? _currentTheme; private readonly Dictionary _registeredThemes = new(); public ThemePack? CurrentTheme => _currentTheme; public event EventHandler? ThemeChanged; private ThemeManager() { } /// /// Регистрирует тему в менеджере. /// public void RegisterTheme(ThemePack theme) { if (theme == null) throw new ArgumentNullException(nameof(theme)); _registeredThemes[theme.Name] = theme; } /// /// Получает зарегистрированную тему по имени. /// public ThemePack? GetTheme(string name) { _registeredThemes.TryGetValue(name, out var theme); return theme; } /// /// Получает список всех зарегистрированных тем. /// public IReadOnlyCollection GetRegisteredThemes() { return _registeredThemes.Values.ToList(); } /// /// Получение информации о теме. /// /// /// public ThemeInfo GetThemeInfo(string themeName) { if (!_registeredThemes.TryGetValue(themeName, out var theme)) return null; return new ThemeInfo { Name = theme.Name, Description = theme.Description, Version = theme.Version, IsDark = theme.IsDark, TokenCount = CountTokensInTheme(theme) }; } /// /// Применяет тему по имени. /// public void ApplyTheme(string themeName) { if (!_registeredThemes.TryGetValue(themeName, out var theme)) { throw new ArgumentException($"Theme '{themeName}' is not registered.", nameof(themeName)); } ApplyTheme(theme); } /// /// Применяет указанную тему. /// public void ApplyTheme(ThemePack theme) { if (theme == null) throw new ArgumentNullException(nameof(theme)); if (_currentTheme == theme) return; var old = _currentTheme; _currentTheme = theme; try { ReplaceApplicationResources(theme); ThemeChanged?.Invoke(this, new ThemeChangedEventArgs(old!, theme)); } catch (Exception ex) { // В случае ошибки возвращаемся к старой теме _currentTheme = old; if (old != null) { ReplaceApplicationResources(old); } throw new InvalidOperationException($"Failed to apply theme '{theme.Name}'.", ex); } } /// /// Загружает ресурсы темы в указанный словарь ресурсов. /// public void LoadThemeIntoDictionary(ResourceDictionary targetDictionary, ThemePack theme) { if (targetDictionary == null) throw new ArgumentNullException(nameof(targetDictionary)); if (theme == null) throw new ArgumentNullException(nameof(theme)); // Очищаем старые словари Lattice for (int i = targetDictionary.MergedDictionaries.Count - 1; i >= 0; i--) { if (targetDictionary.MergedDictionaries[i] is ThemeDictionary) targetDictionary.MergedDictionaries.RemoveAt(i); } // Добавляем новые словари темы foreach (var uri in theme.GetResourceUris()) { try { var themeDict = new ThemeDictionary { Source = uri }; targetDictionary.MergedDictionaries.Add(themeDict); } catch (Exception ex) { throw new InvalidOperationException($"Failed to load theme resource from '{uri}'.", ex); } } } private int CountTokensInTheme(ThemePack theme) { try { var dict = new ResourceDictionary(); LoadThemeIntoDictionary(dict, theme); return dict.Count; } catch { return 0; } } private void ReplaceApplicationResources(ThemePack theme) { var app = Application.Current; if (app == null) return; var root = app.Resources; LoadThemeIntoDictionary(root, theme); ForceUpdateUI(); } private void ForceUpdateUI() { foreach (var window in WindowTracker.Windows) { if (window.Content is FrameworkElement root) RefreshElement(root); } } private void RefreshElement(FrameworkElement element) { var stack = new Stack(); stack.Push(element); while (stack.Count > 0) { var current = stack.Pop(); // Пересоздаём Template только у Control if (current is Control control) { var template = control.Template; control.Template = null; control.Template = template; } else if (current is ContentPresenter contentPresenter) { // Обновляем ContentPresenter var content = contentPresenter.Content; contentPresenter.Content = null; contentPresenter.Content = content; } // Добавляем детей в стек int count = VisualTreeHelper.GetChildrenCount(current); for (int i = 0; i < count; i++) { if (VisualTreeHelper.GetChild(current, i) is FrameworkElement child) stack.Push(child); } } } /// /// Проверяет, что все необходимые токены определены в текущей теме. /// public bool ValidateThemeTokens() { if (_currentTheme == null) return false; var app = Application.Current; if (app == null) return false; var requiredTokens = LatticeTokens.GetAllTokens().Values; var missingTokens = new List(); foreach (var token in requiredTokens) { if (!app.Resources.ContainsKey(token)) { missingTokens.Add(token); } } if (missingTokens.Any()) { System.Diagnostics.Debug.WriteLine($"Missing theme tokens: {string.Join(", ", missingTokens)}"); return false; } return true; } /// /// Получает значение токена из текущей темы. /// public object? GetTokenValue(string tokenKey) { var app = Application.Current; if (app == null) return null; if (app.Resources.TryGetValue(tokenKey, out var value)) { return value; } return null; } /// /// Получает значение токена с приведением к указанному типу. /// public T? GetTokenValue(string tokenKey) { object? value = GetTokenValue(tokenKey); if (value is T typedValue) return typedValue; return default; } } /// /// Информация о теме. /// public class ThemeInfo { /// /// Название темы. /// public string Name { get; set; } public string Description { get; set; } public string Version { get; set; } public bool IsDark { get; set; } public int TokenCount { get; set; } }