DragAndDrop core
This commit is contained in:
293
Lattice.Themes.Core/ThemeManager.cs
Normal file
293
Lattice.Themes.Core/ThemeManager.cs
Normal file
@@ -0,0 +1,293 @@
|
||||
using Lattice.Themes.Core.Tokens;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
|
||||
namespace Lattice.Themes;
|
||||
|
||||
/// <summary>
|
||||
/// Менеджер тем для Lattice Framework.
|
||||
/// </summary>
|
||||
public sealed class ThemeManager
|
||||
{
|
||||
public static ThemeManager Current { get; } = new();
|
||||
|
||||
private ThemePack? _currentTheme;
|
||||
private readonly Dictionary<string, ThemePack> _registeredThemes = new();
|
||||
|
||||
public ThemePack? CurrentTheme => _currentTheme;
|
||||
|
||||
public event EventHandler<ThemeChangedEventArgs>? ThemeChanged;
|
||||
|
||||
private ThemeManager() { }
|
||||
|
||||
/// <summary>
|
||||
/// Регистрирует тему в менеджере.
|
||||
/// </summary>
|
||||
public void RegisterTheme(ThemePack theme)
|
||||
{
|
||||
if (theme == null)
|
||||
throw new ArgumentNullException(nameof(theme));
|
||||
|
||||
_registeredThemes[theme.Name] = theme;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получает зарегистрированную тему по имени.
|
||||
/// </summary>
|
||||
public ThemePack? GetTheme(string name)
|
||||
{
|
||||
_registeredThemes.TryGetValue(name, out var theme);
|
||||
return theme;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получает список всех зарегистрированных тем.
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<ThemePack> GetRegisteredThemes()
|
||||
{
|
||||
return _registeredThemes.Values.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получение информации о теме.
|
||||
/// </summary>
|
||||
/// <param name="themeName"></param>
|
||||
/// <returns></returns>
|
||||
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)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Применяет тему по имени.
|
||||
/// </summary>
|
||||
public void ApplyTheme(string themeName)
|
||||
{
|
||||
if (!_registeredThemes.TryGetValue(themeName, out var theme))
|
||||
{
|
||||
throw new ArgumentException($"Theme '{themeName}' is not registered.", nameof(themeName));
|
||||
}
|
||||
|
||||
ApplyTheme(theme);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Применяет указанную тему.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Загружает ресурсы темы в указанный словарь ресурсов.
|
||||
/// </summary>
|
||||
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<FrameworkElement>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Проверяет, что все необходимые токены определены в текущей теме.
|
||||
/// </summary>
|
||||
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<string>();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получает значение токена из текущей темы.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Получает значение токена с приведением к указанному типу.
|
||||
/// </summary>
|
||||
public T? GetTokenValue<T>(string tokenKey)
|
||||
{
|
||||
object? value = GetTokenValue(tokenKey);
|
||||
|
||||
if (value is T typedValue)
|
||||
return typedValue;
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Информация о теме.
|
||||
/// </summary>
|
||||
public class ThemeInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Название темы.
|
||||
/// </summary>
|
||||
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; }
|
||||
}
|
||||
Reference in New Issue
Block a user