Files
2026-07-13 13:13:17 +08:00

264 lines
8.3 KiB
C#

#nullable enable
using System.Diagnostics.CodeAnalysis;
using System.IO;
using T3.Core.DataTypes.Vector;
using T3.Core.Settings;
using T3.Editor.Gui.UiHelpers;
using T3.Serialization;
namespace T3.Editor.Gui.Styling;
public static class ThemeHandling
{
/// <summary>
/// Requires user settings to be loaded already.
/// </summary>
internal static void Initialize()
{
InitializeFactoryDefault();
LoadThemes();
ApplyUserConfigTheme();
}
internal static void SetThemeAsUserTheme(ColorTheme theme)
{
UserSettings.Config.ColorThemeName = theme.Name;
UserSettings.Save();
ApplyTheme(theme);
T3Style.Apply();
}
internal static void SaveTheme(ColorTheme theme)
{
Directory.CreateDirectory(ThemeFolder);
theme.Name = theme.Name.Trim();
if (string.IsNullOrEmpty(theme.Name))
{
theme.Name = "untitled";
}
var combine = GetThemeFilepath(theme);
var filepath = combine;
StoreAllColors(theme);
JsonUtils.TrySaveJson(theme, filepath);
LoadThemes();
}
internal static void DeleteTheme(ColorTheme theme)
{
var filepath = GetThemeFilepath(theme);
if (!File.Exists(filepath))
{
Log.Warning($"{filepath} does not exist?");
return;
}
File.Delete(filepath);
ApplyTheme(FactoryTheme);
LoadThemes();
UserSettings.Config.ColorThemeName = string.Empty;
}
internal static ColorTheme GetUserOrFactoryTheme()
{
var selectedThemeName = UserSettings.Config.ColorThemeName;
if (string.IsNullOrWhiteSpace(selectedThemeName))
{
return FactoryTheme;
}
var userTheme = Themes.FirstOrDefault(t => t.Name == selectedThemeName);
if (userTheme == null)
{
Log.Warning($"Couldn't load {selectedThemeName}");
return FactoryTheme;
}
return userTheme;
}
private static void LoadThemes()
{
Themes.Clear();
Directory.CreateDirectory(ThemeFolder);
Directory.CreateDirectory(DefaultThemeFolder);
// Load from both default and user folders. User files with the same
// name take precedence over shipped defaults — but defaults are NOT
// copied into the user folder until the user explicitly saves.
var userFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var filepath in Directory.EnumerateFiles(ThemeFolder))
userFiles.Add(Path.GetFileName(filepath));
var defaultFileCount = 0;
foreach (var filepath in Directory.EnumerateFiles(DefaultThemeFolder))
{
defaultFileCount++;
if (userFiles.Contains(Path.GetFileName(filepath)))
continue; // user override exists
LoadThemeFile(filepath);
}
foreach (var filepath in Directory.EnumerateFiles(ThemeFolder))
{
LoadThemeFile(filepath);
}
// The shipped themes live next to the executable; if they're missing the dropdown silently
// collapses to whatever the user saved. Surface that instead of leaving it a mystery.
if (defaultFileCount == 0)
Log.Warning($"No default themes found in {DefaultThemeFolder}. Only user themes will be listed.");
}
private static void LoadThemeFile(string filepath)
{
try
{
var t = JsonUtils.TryLoadingJson<ColorTheme>(filepath);
if (t == null)
{
Log.Debug($"Failed to load theme {filepath}");
return;
}
Themes.Add(t);
}
catch (Exception e)
{
Log.Error($"Failed to load {filepath} : {e.Message}");
}
}
private static void ApplyUserConfigTheme()
{
var userTheme = GetUserOrFactoryTheme();
ApplyTheme(userTheme);
}
/// <summary>
/// Applies the colors to T3StyleColors
/// </summary>
/// <param name="theme"></param>
private static void ApplyTheme(ColorTheme theme)
{
var colorFields = typeof(UiColors).GetFields();
foreach (var colorField in colorFields)
{
if (colorField.GetValue(ColorThemeEditor.Dummy) is not Color)
continue;
if (!theme.Colors.TryGetValue(colorField.Name, out var colorValue))
continue;
colorField.SetValue(ColorThemeEditor.Dummy, new Color(colorValue));
}
var variationFields = typeof(ColorVariations).GetFields();
foreach (var varField in variationFields)
{
if (varField.GetValue(ColorThemeEditor.Dummy) is not ColorVariation)
continue;
if (!theme.Variations.TryGetValue(varField.Name, out var variation))
{
// Back-compat: themes saved before the annotation->section rename
var legacyName = varField.Name.Replace("Section", "Annotation");
if (legacyName == varField.Name || !theme.Variations.TryGetValue(legacyName, out variation))
continue;
}
varField.SetValue(ColorThemeEditor.Dummy, variation.Clone());
}
FrameStats.Current.UiColorsChanged = true;
T3Style.Apply();
}
private static string GetThemeFilepath(ColorTheme theme)
{
return Path.Combine(ThemeFolder, theme.Name + ".json");
}
private static void StoreAllColors(ColorTheme theme)
{
var colorFields = typeof(UiColors).GetFields();
foreach (var colorField in colorFields)
{
if (colorField.GetValue(ColorThemeEditor.Dummy) is not Color color)
continue;
theme.Colors[colorField.Name] = color;
}
var variationFields = typeof(ColorVariations).GetFields();
foreach (var varField in variationFields)
{
if (varField.GetValue(ColorThemeEditor.Dummy) is not ColorVariation variation)
continue;
theme.Variations[varField.Name] = variation;
}
}
private static void InitializeFactoryDefault()
{
FactoryTheme = new ThemeHandling.ColorTheme();
var colorFields = typeof(UiColors).GetFields();
foreach (var f in colorFields)
{
if (f.GetValue(ColorThemeEditor.Dummy) is not Color color)
continue;
FactoryTheme.Colors[f.Name] = color;
}
var variationFields = typeof(ColorVariations).GetFields();
foreach (var v in variationFields)
{
if (v.GetValue(ColorThemeEditor.Dummy) is not ColorVariation variation)
continue;
FactoryTheme.Variations[v.Name] = variation;
}
}
internal static readonly List<ColorTheme> Themes = [];
internal static string ThemeFolder => Path.Combine(FileLocations.SettingsDirectory, FileLocations.ThemeSubFolder);
private static string DefaultThemeFolder => Path.Combine(FileLocations.ReadOnlySettingsPath, FileLocations.ThemeSubFolder);
internal static ColorTheme FactoryTheme = null!;
// This public for serialization
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
public sealed class ColorTheme
{
public string Name = "untitled";
public string Author = "unknown";
public Dictionary<string, Vector4> Colors = new();
public Dictionary<string, ColorVariation> Variations = new();
public ColorTheme Clone()
{
return new ColorTheme()
{
Name = Name,
Author = Author,
Colors = Colors.ToDictionary(entry => entry.Key,
entry => entry.Value),
Variations = Variations.ToDictionary(entry => entry.Key,
entry => entry.Value),
};
}
}
}