using System.IO; using System.Text.Json; using System.Text.Json.Serialization; namespace DeskCore; public enum DesignMode { System, Light, Dark } public enum ViewMode { Small, Medium, Large, List } public class CategoryMeta { public string? Glyph { get; set; } public string? Color { get; set; } // Die folgenden Box-Felder stammen aus Version 1.0.x und werden nur noch beim Umstieg auf FenceDef gelesen public bool Fence { get; set; } public double? FenceX { get; set; } public double? FenceY { get; set; } public double FenceW { get; set; } = 340; public double FenceH { get; set; } = 260; public bool FenceCollapsed { get; set; } } /// Eine Start-Gruppe: ein Klick startet mehrere Programme nacheinander. public class LaunchGroup { public string Name { get; set; } = ""; /// Pfade in Startreihenfolge (meist Verknüpfungen aus den Kategorien). public List Items { get; set; } = []; /// Wartezeit zwischen zwei Programmen in Sekunden. public double DelaySeconds { get; set; } = 1; public string? Color { get; set; } } public class SortRule { /// "Name" = Name enthält …, "Typ" = Dateityp ist … (z. B. "Bilder" oder "pdf, docx") public string Kind { get; set; } = "Name"; public string Contains { get; set; } = ""; public string Category { get; set; } = ""; [JsonIgnore] public string KindText => Kind == "Typ" ? "Dateityp ist" : "Name enthält"; } /// Ein Tab einer Desktop-Box: eine Kategorie oder ein beliebiger Ordner ("Ordner-Portal"). public class FenceTab { public const string CategoryKind = "Kategorie", FolderKind = "Ordner"; public string Kind { get; set; } = CategoryKind; /// Kategoriename bzw. Ordnerpfad. public string Value { get; set; } = ""; [JsonIgnore] public bool IsFolder => Kind == FolderKind; [JsonIgnore] public string Title => IsFolder ? Path.GetFileName(Value.TrimEnd('\\')) is { Length: > 0 } n ? n : Value : Value; } /// Eine Desktop-Box mit Position, Tabs und Aussehen. public class FenceDef { public string Id { get; set; } = Guid.NewGuid().ToString("N"); public List Tabs { get; set; } = []; public int ActiveTab { get; set; } public double? X { get; set; } public double? Y { get; set; } public double W { get; set; } = 340; public double H { get; set; } = 260; public bool Collapsed { get; set; } /// Gesperrt: nicht verschieb- oder vergrößerbar. public bool Locked { get; set; } /// Hintergrundfarbe (#RRGGBB) oder null = passend zum Design. public string? Background { get; set; } /// Deckkraft des Hintergrunds 0.2–1. public double Opacity { get; set; } = 0.92; /// Milchglas-Effekt (Hintergrund verschwommen). public bool Blur { get; set; } public ViewMode IconSize { get; set; } = ViewMode.Medium; /// "Name", "Typ" oder "Datum" public string Sort { get; set; } = "Name"; /// Icon-Tönung (#RRGGBB) oder null. public string? Tint { get; set; } /// Desktop-Seite, auf der die Box liegt (0 = erste Seite). public int Page { get; set; } [JsonIgnore] public FenceTab? Active => Tabs.Count == 0 ? null : Tabs[Math.Clamp(ActiveTab, 0, Tabs.Count - 1)]; } /// Gespeicherte Anordnung aller Desktop-Boxen. public class FenceSnapshot { public string Name { get; set; } = ""; public DateTime Created { get; set; } = DateTime.Now; public List Fences { get; set; } = []; [JsonIgnore] public string Info => $"{Fences.Count} {(Fences.Count == 1 ? "Box" : "Boxen")} · gespeichert am {Created:dd.MM.yyyy HH:mm}"; } /// Alle Einstellungen, gespeichert in %APPDATA%\DeskCore\settings.json. public class AppSettings { public DesignMode Design { get; set; } = DesignMode.System; public ViewMode View { get; set; } = ViewMode.Medium; public bool TrayEnabled { get; set; } = true; public bool CloseToTray { get; set; } = true; public bool TrayHintShown { get; set; } public bool HotkeyEnabled { get; set; } = true; public int HotkeyModifiers { get; set; } = Hotkey.ModWin | Hotkey.ModShift; public int HotkeyKey { get; set; } = 0x44; // D public bool AutoSort { get; set; } public bool AutoSortRemoveFromDesktop { get; set; } public List Rules { get; set; } = []; public bool FencesEnabled { get; set; } = true; public List Fences { get; set; } = []; public List Snapshots { get; set; } = []; /// Box-Positionen je Bildschirm-Anordnung (Signatur → Box-Id → x, y, w, h). public Dictionary> ScreenLayouts { get; set; } = []; /// Chamäleon: Boxen blenden sich aus, bis die Maus darüberfährt. public bool FenceChameleon { get; set; } /// Doppelklick auf eine freie Desktop-Stelle blendet Icons und Boxen aus/ein. public bool QuickHide { get; set; } = true; /// Peek: Tastenkürzel holt alle Boxen vor die Fenster. public bool PeekEnabled { get; set; } = true; public int PeekModifiers { get; set; } = Hotkey.ModWin | Hotkey.ModShift; public int PeekKey { get; set; } = 0x42; // B wie "Boxen" /// Desktop-Seiten: jede Seite hat ihre eigenen Boxen. public List Pages { get; set; } = ["Seite 1"]; public int CurrentPage { get; set; } /// Mausrad über einer freien Desktop-Stelle wechselt die Seite. public bool PageWheel { get; set; } = true; /// Rechte Maustaste auf freier Desktop-Fläche ziehen → neue Box. public bool DrawFences { get; set; } = true; /// Tönung der echten Desktop-Icons (#RRGGBB) oder null = aus. public string? DesktopTint { get; set; } public double DesktopTintStrength { get; set; } = 0.55; /// Verknüpfungen zu deinstallierten Programmen automatisch entfernen. public bool RemoveDeadShortcuts { get; set; } = true; /// Tote Verknüpfungen, die der Nutzer per Rückgängig behalten wollte. public List KeptShortcuts { get; set; } = []; // Fenster merken (WPF-Einheiten) public double? WindowLeft { get; set; } public double? WindowTop { get; set; } public double? WindowWidth { get; set; } public double? WindowHeight { get; set; } public bool WindowMaximized { get; set; } // Updates public bool AutoUpdateCheck { get; set; } = true; public DateTime? LastUpdateCheck { get; set; } public string? SkippedVersion { get; set; } public List CategoryOrder { get; set; } = []; public List LaunchGroups { get; set; } = []; public Dictionary Categories { get; set; } = new(StringComparer.OrdinalIgnoreCase); // ---------- Laden/Speichern ---------- public static readonly string Folder = Migration.Pick(Environment.SpecialFolder.ApplicationData); private static readonly string FilePath = Path.Combine(Folder, "settings.json"); public static readonly JsonSerializerOptions Json = new() { WriteIndented = true, Converters = { new JsonStringEnumConverter() }, }; public static AppSettings Current { get; private set; } = Load(); public static event Action? Saved; private static AppSettings Load() { AppSettings s; try { s = File.Exists(FilePath) ? JsonSerializer.Deserialize(File.ReadAllText(FilePath), Json) ?? new() : new(); } catch { s = new(); } s.Categories = new(s.Categories ?? [], StringComparer.OrdinalIgnoreCase); s.Rules ??= []; s.CategoryOrder ??= []; s.KeptShortcuts ??= []; s.LaunchGroups ??= []; s.Fences ??= []; s.Snapshots ??= []; s.ScreenLayouts ??= []; if (s.Pages is null || s.Pages.Count == 0) s.Pages = ["Seite 1"]; s.CurrentPage = Math.Clamp(s.CurrentPage, 0, s.Pages.Count - 1); // Umstieg von 1.0.x: Box gehörte fest zu einer Kategorie → eigenständige Box mit einem Tab foreach (var (name, meta) in s.Categories) { if (!meta.Fence) continue; s.Fences.Add(new FenceDef { Tabs = [new FenceTab { Value = name }], X = meta.FenceX, Y = meta.FenceY, W = meta.FenceW, H = meta.FenceH, Collapsed = meta.FenceCollapsed, }); meta.Fence = false; } // Übernahme der alten Einstellung aus Version 2 var old = Path.Combine(Folder, "design.txt"); if (File.Exists(old)) { try { var v = File.ReadAllText(old).Trim(); s.Design = v == "Light" ? DesignMode.Light : v == "Dark" ? DesignMode.Dark : DesignMode.System; File.Delete(old); } catch { } } return s; } public void Save() { try { Directory.CreateDirectory(Folder); File.WriteAllText(FilePath, JsonSerializer.Serialize(this, Json)); } catch { } Saved?.Invoke(); } public CategoryMeta Meta(string category) { if (!Categories.TryGetValue(category, out var m)) Categories[category] = m = new CategoryMeta(); return m; } public void RenameCategory(string oldName, string newName) { if (Categories.Remove(oldName, out var m)) Categories[newName] = m; for (int i = 0; i < CategoryOrder.Count; i++) if (string.Equals(CategoryOrder[i], oldName, StringComparison.OrdinalIgnoreCase)) CategoryOrder[i] = newName; foreach (var r in Rules) if (string.Equals(r.Category, oldName, StringComparison.OrdinalIgnoreCase)) r.Category = newName; foreach (var t in Fences.SelectMany(f => f.Tabs)) if (!t.IsFolder && string.Equals(t.Value, oldName, StringComparison.OrdinalIgnoreCase)) t.Value = newName; // Start-Gruppen zeigen auf Dateien in der Kategorie → Pfade mit umbenennen var oldDir = Storage.CategoryPath(oldName).TrimEnd('\\') + "\\"; var newDir = Storage.CategoryPath(newName).TrimEnd('\\') + "\\"; foreach (var g in LaunchGroups) for (int i = 0; i < g.Items.Count; i++) if (g.Items[i].StartsWith(oldDir, StringComparison.OrdinalIgnoreCase)) g.Items[i] = newDir + g.Items[i][oldDir.Length..]; } /// Wird diese Kategorie in irgendeiner Box angezeigt? public bool HasFence(string category) => Fences.Any(f => f.Tabs.Any(t => !t.IsFolder && string.Equals(t.Value, category, StringComparison.OrdinalIgnoreCase))); /// Kategorie als eigene Box anzeigen bzw. aus allen Boxen entfernen. public void SetFence(string category, bool on) { if (on) { if (!HasFence(category)) Fences.Add(new FenceDef { Page = CurrentPage, Tabs = [new FenceTab { Value = category }] }); FencesEnabled = true; } else { foreach (var f in Fences) f.Tabs.RemoveAll(t => !t.IsFolder && string.Equals(t.Value, category, StringComparison.OrdinalIgnoreCase)); Fences.RemoveAll(f => f.Tabs.Count == 0); } } /// Tiefe Kopie (für Snapshots). public static List CloneFences(List fences) => JsonSerializer.Deserialize>(JsonSerializer.Serialize(fences, Json), Json) ?? []; /// Datei wurde umbenannt/verschoben → Start-Gruppen zeigen auf den neuen Pfad. public void ReplaceLaunchPath(string oldPath, string newPath) { bool changed = false; foreach (var g in LaunchGroups) for (int i = 0; i < g.Items.Count; i++) if (string.Equals(g.Items[i], oldPath, StringComparison.OrdinalIgnoreCase)) { g.Items[i] = newPath; changed = true; } if (changed) Save(); } public void RemoveCategory(string name) { foreach (var f in Fences) f.Tabs.RemoveAll(t => !t.IsFolder && string.Equals(t.Value, name, StringComparison.OrdinalIgnoreCase)); Fences.RemoveAll(f => f.Tabs.Count == 0); Categories.Remove(name); CategoryOrder.RemoveAll(n => string.Equals(n, name, StringComparison.OrdinalIgnoreCase)); Rules.RemoveAll(r => string.Equals(r.Category, name, StringComparison.OrdinalIgnoreCase)); } }