using System.Text.Json; using Terminal.Gui.Drawing; using Terminal.Gui.Configuration; using Attribute = Terminal.Gui.Drawing.Attribute; namespace EchoHub.Client.Themes; public static class ThemeManager { private static readonly string ThemeDir = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".echohub", "themes"); private static readonly JsonSerializerOptions JsonOptions = new() { WriteIndented = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; private static readonly Theme DefaultTheme = new() { Name = "Default", Base = new ThemeColors { Foreground = "Gray", Background = "Black", FocusForeground = "White", FocusBackground = "DarkGray" }, Menu = new ThemeColors { Foreground = "Gray", Background = "Black", FocusForeground = "White", FocusBackground = "DarkGray" }, Dialog = new ThemeColors { Foreground = "White", Background = "DarkGray", FocusForeground = "Black", FocusBackground = "Gray" }, Status = new ThemeColors { Foreground = "White", Background = "DarkGray", FocusForeground = "White", FocusBackground = "DarkGray" } }; private static readonly Theme ClassicTheme = new() { Name = "Classic", Base = new ThemeColors { Foreground = "White", Background = "Blue", FocusForeground = "Black", FocusBackground = "Cyan" }, Menu = new ThemeColors { Foreground = "White", Background = "DarkGray", FocusForeground = "White", FocusBackground = "Black" }, Dialog = new ThemeColors { Foreground = "White", Background = "DarkGray", FocusForeground = "Black", FocusBackground = "Cyan" }, Status = new ThemeColors { Foreground = "White", Background = "DarkGray", FocusForeground = "White", FocusBackground = "DarkGray" } }; private static readonly Theme LightTheme = new() { Name = "Light", Base = new ThemeColors { Foreground = "Black", Background = "White", FocusForeground = "White", FocusBackground = "Blue" }, Menu = new ThemeColors { Foreground = "White", Background = "Blue", FocusForeground = "BrightYellow", FocusBackground = "Blue" }, Dialog = new ThemeColors { Foreground = "Black", Background = "White", FocusForeground = "White", FocusBackground = "Blue" }, Status = new ThemeColors { Foreground = "White", Background = "Blue", FocusForeground = "White", FocusBackground = "Blue" } }; private static readonly Theme HackerTheme = new() { Name = "Hacker", Base = new ThemeColors { Foreground = "BrightGreen", Background = "Black", FocusForeground = "Black", FocusBackground = "Green" }, Menu = new ThemeColors { Foreground = "BrightGreen", Background = "Black", FocusForeground = "Black", FocusBackground = "BrightGreen" }, Dialog = new ThemeColors { Foreground = "BrightGreen", Background = "Black", FocusForeground = "Black", FocusBackground = "Green" }, Status = new ThemeColors { Foreground = "BrightGreen", Background = "Black", FocusForeground = "BrightGreen", FocusBackground = "Black" } }; private static readonly Theme SolarizedTheme = new() { Name = "Solarized", Base = new ThemeColors { Foreground = "Cyan", Background = "Black", FocusForeground = "BrightYellow", FocusBackground = "DarkGray" }, Menu = new ThemeColors { Foreground = "BrightCyan", Background = "DarkGray", FocusForeground = "BrightYellow", FocusBackground = "Black" }, Dialog = new ThemeColors { Foreground = "Cyan", Background = "DarkGray", FocusForeground = "BrightYellow", FocusBackground = "Black" }, Status = new ThemeColors { Foreground = "BrightCyan", Background = "DarkGray", FocusForeground = "BrightCyan", FocusBackground = "DarkGray" } }; private static readonly Theme DraculaTheme = new() { Name = "Dracula", Base = new ThemeColors { Foreground = "White", Background = "Black", FocusForeground = "Black", FocusBackground = "Magenta" }, Menu = new ThemeColors { Foreground = "BrightMagenta", Background = "Black", FocusForeground = "Black", FocusBackground = "BrightMagenta" }, Dialog = new ThemeColors { Foreground = "White", Background = "DarkGray", FocusForeground = "Black", FocusBackground = "Magenta" }, Status = new ThemeColors { Foreground = "BrightMagenta", Background = "Black", FocusForeground = "BrightMagenta", FocusBackground = "Black" } }; private static readonly Theme MonokaiTheme = new() { Name = "Monokai", Base = new ThemeColors { Foreground = "White", Background = "Black", FocusForeground = "Black", FocusBackground = "BrightYellow" }, Menu = new ThemeColors { Foreground = "BrightYellow", Background = "Black", FocusForeground = "Black", FocusBackground = "BrightYellow" }, Dialog = new ThemeColors { Foreground = "White", Background = "DarkGray", FocusForeground = "Black", FocusBackground = "BrightYellow" }, Status = new ThemeColors { Foreground = "BrightYellow", Background = "Black", FocusForeground = "BrightYellow", FocusBackground = "Black" } }; private static readonly Theme NordTheme = new() { Name = "Nord", Base = new ThemeColors { Foreground = "White", Background = "DarkBlue", FocusForeground = "BrightCyan", FocusBackground = "Blue" }, Menu = new ThemeColors { Foreground = "White", Background = "Blue", FocusForeground = "BrightCyan", FocusBackground = "DarkBlue" }, Dialog = new ThemeColors { Foreground = "White", Background = "Blue", FocusForeground = "BrightCyan", FocusBackground = "DarkBlue" }, Status = new ThemeColors { Foreground = "BrightCyan", Background = "DarkBlue", FocusForeground = "BrightCyan", FocusBackground = "DarkBlue" } }; private static readonly Theme GruvboxTheme = new() { Name = "Gruvbox", Base = new ThemeColors { Foreground = "BrightYellow", Background = "Black", FocusForeground = "Black", FocusBackground = "DarkYellow" }, Menu = new ThemeColors { Foreground = "BrightYellow", Background = "Black", FocusForeground = "Black", FocusBackground = "DarkYellow" }, Dialog = new ThemeColors { Foreground = "BrightYellow", Background = "DarkGray", FocusForeground = "Black", FocusBackground = "DarkYellow" }, Status = new ThemeColors { Foreground = "DarkYellow", Background = "Black", FocusForeground = "DarkYellow", FocusBackground = "Black" } }; private static readonly Theme OceanTheme = new() { Name = "Ocean", Base = new ThemeColors { Foreground = "BrightCyan", Background = "DarkBlue", FocusForeground = "White", FocusBackground = "DarkCyan" }, Menu = new ThemeColors { Foreground = "White", Background = "DarkCyan", FocusForeground = "BrightCyan", FocusBackground = "DarkBlue" }, Dialog = new ThemeColors { Foreground = "White", Background = "DarkCyan", FocusForeground = "BrightCyan", FocusBackground = "DarkBlue" }, Status = new ThemeColors { Foreground = "BrightCyan", Background = "DarkCyan", FocusForeground = "BrightCyan", FocusBackground = "DarkCyan" } }; private static readonly Theme HighContrastTheme = new() { Name = "HighContrast", Base = new ThemeColors { Foreground = "BrightYellow", Background = "Black", FocusForeground = "Black", FocusBackground = "BrightYellow" }, Menu = new ThemeColors { Foreground = "BrightYellow", Background = "Black", FocusForeground = "Black", FocusBackground = "BrightYellow" }, Dialog = new ThemeColors { Foreground = "BrightYellow", Background = "Black", FocusForeground = "Black", FocusBackground = "BrightYellow" }, Status = new ThemeColors { Foreground = "BrightYellow", Background = "Black", FocusForeground = "BrightYellow", FocusBackground = "Black" } }; private static readonly Theme RosePineTheme = new() { Name = "RosePine", Base = new ThemeColors { Foreground = "White", Background = "Black", FocusForeground = "Black", FocusBackground = "DarkMagenta" }, Menu = new ThemeColors { Foreground = "Magenta", Background = "Black", FocusForeground = "White", FocusBackground = "DarkMagenta" }, Dialog = new ThemeColors { Foreground = "White", Background = "DarkGray", FocusForeground = "White", FocusBackground = "DarkMagenta" }, Status = new ThemeColors { Foreground = "Magenta", Background = "Black", FocusForeground = "Magenta", FocusBackground = "Black" } }; private static readonly Theme TransparentTheme = new() { Name = "Transparent", Base = new ThemeColors { Foreground = "White", Background = "None", FocusForeground = "BrightCyan", FocusBackground = "None" }, Menu = new ThemeColors { Foreground = "White", Background = "None", FocusForeground = "BrightCyan", FocusBackground = "None" }, Dialog = new ThemeColors { Foreground = "White", Background = "DarkGray", FocusForeground = "BrightCyan", FocusBackground = "Black" }, Status = new ThemeColors { Foreground = "Gray", Background = "None", FocusForeground = "Gray", FocusBackground = "None" } }; private static readonly List BuiltInThemes = [ DefaultTheme, TransparentTheme, ClassicTheme, LightTheme, HackerTheme, SolarizedTheme, DraculaTheme, MonokaiTheme, NordTheme, GruvboxTheme, OceanTheme, HighContrastTheme, RosePineTheme ]; public static List GetAvailableThemes() { var themes = new List(BuiltInThemes); try { if (Directory.Exists(ThemeDir)) { foreach (var file in Directory.GetFiles(ThemeDir, "*.json")) { try { var json = File.ReadAllText(file); var theme = JsonSerializer.Deserialize(json, JsonOptions); if (theme is not null && !string.IsNullOrWhiteSpace(theme.Name)) { // Skip if a built-in theme already has this name if (!themes.Exists(t => string.Equals(t.Name, theme.Name, StringComparison.OrdinalIgnoreCase))) { themes.Add(theme); } } } catch { // Skip malformed theme files } } } } catch { // If we can't read the theme directory, just return built-ins } return themes; } public static Theme GetTheme(string name) { var themes = GetAvailableThemes(); return themes.Find(t => string.Equals(t.Name, name, StringComparison.OrdinalIgnoreCase)) ?? DefaultTheme; } public static void ApplyTheme(Theme theme) { SchemeManager.AddScheme("Base", BuildColorScheme(theme.Base)); SchemeManager.AddScheme("Menu", BuildColorScheme(theme.Menu)); SchemeManager.AddScheme("Dialog", BuildColorScheme(theme.Dialog)); } public static void SaveTheme(Theme theme) { try { Directory.CreateDirectory(ThemeDir); var fileName = $"{theme.Name}.json"; var filePath = Path.Combine(ThemeDir, fileName); var json = JsonSerializer.Serialize(theme, JsonOptions); File.WriteAllText(filePath, json); } catch { // Silently fail — theme save is best-effort } } private static Scheme BuildColorScheme(ThemeColors colors) { var normal = new Attribute(ParseColor(colors.Foreground), ParseColor(colors.Background)); var focus = new Attribute(ParseColor(colors.FocusForeground), ParseColor(colors.FocusBackground)); return new Scheme { Normal = normal, Focus = focus, HotNormal = normal, HotFocus = focus, Disabled = normal }; } private static Color ParseColor(string colorName) { if (Color.TryParse(colorName, out var color)) return color ?? Color.White; return Color.White; } }