diff --git a/BuildTime.txt b/BuildTime.txt index f4c10f2..da9c901 100644 --- a/BuildTime.txt +++ b/BuildTime.txt @@ -1 +1 @@ -2026-03-26 20:06:05 \ No newline at end of file +2026-03-26 20:36:31 \ No newline at end of file diff --git a/GitCommit.txt b/GitCommit.txt index 6e0ab9a..9cf38b9 100644 --- a/GitCommit.txt +++ b/GitCommit.txt @@ -1 +1 @@ -2c2f93c \ No newline at end of file +2c2ee0f \ No newline at end of file diff --git a/Gui/Apps/Settings.cs b/Gui/Apps/Settings.cs index b5c7250..dbe08c6 100644 --- a/Gui/Apps/Settings.cs +++ b/Gui/Apps/Settings.cs @@ -164,6 +164,14 @@ namespace CMLeonOS.Gui.Apps SettingsManager.GUI_MouseSensitivity = value; } + private void ThemeChanged(int index, string themeName) + { + SettingsManager.GUI_Theme = themeName; + UITheme.ApplyTheme(themeName); + UITheme.RefreshOpenWindows(wm); + ShowAppearanceCategory(); + } + private void ShowAppearanceCategory() { if (currentCategoryWindow != null) @@ -193,16 +201,36 @@ namespace CMLeonOS.Gui.Apps skipToGui.CheckBoxChanged = SkipToGuiChanged; wm.AddWindow(skipToGui); - appearance.DrawString("Wallpaper", Color.Gray, 12, 132); - appearance.DrawString(GetWallpaperLabel(), Color.Black, 12, 152); - appearance.DrawString("Use a BMP file or restore the default wallpaper.", Color.Gray, 12, 170); + appearance.DrawString("Theme", Color.Gray, 12, 126); - Button chooseWallpaper = new Button(appearance, 12, 192, 132, 24); + Dropdown themeDropdown = new Dropdown(appearance, 12, 146, 180, 24); + string[] themes = UITheme.GetThemeNames(); + for (int i = 0; i < themes.Length; i++) + { + themeDropdown.Items.Add(themes[i]); + } + themeDropdown.RefreshItems(); + for (int i = 0; i < themes.Length; i++) + { + if (themes[i] == SettingsManager.GUI_Theme) + { + themeDropdown.SelectedIndex = i; + break; + } + } + themeDropdown.SelectionChanged = ThemeChanged; + wm.AddWindow(themeDropdown); + + appearance.DrawString("Wallpaper", Color.Gray, 12, 184); + appearance.DrawString(GetWallpaperLabel(), Color.Black, 12, 204); + appearance.DrawString("Use a BMP file or restore the default wallpaper.", Color.Gray, 12, 222); + + Button chooseWallpaper = new Button(appearance, 12, 244, 132, 24); chooseWallpaper.Text = "Choose BMP"; chooseWallpaper.OnClick = (_, _) => OpenWallpaperBrowser(); wm.AddWindow(chooseWallpaper); - Button defaultWallpaper = new Button(appearance, 154, 192, 132, 24); + Button defaultWallpaper = new Button(appearance, 154, 244, 132, 24); defaultWallpaper.Text = "Use Default"; defaultWallpaper.OnClick = (_, _) => ApplyWallpaper(string.Empty); wm.AddWindow(defaultWallpaper); diff --git a/Gui/Gui.cs b/Gui/Gui.cs index b296953..9429ec1 100644 --- a/Gui/Gui.cs +++ b/Gui/Gui.cs @@ -16,6 +16,7 @@ using CMLeonOS; using CMLeonOS.Logger; +using CMLeonOS.Settings; using System; namespace CMLeonOS.Gui @@ -51,6 +52,9 @@ namespace CMLeonOS.Gui Console.WriteLine("Loading apps..."); AppManager.LoadAllApps(); + SettingsManager.LoadSettings(); + UILib.UITheme.ApplyTheme(SettingsManager.GUI_Theme); + ProcessManager.AddProcess(windowManager); ProcessManager.AddProcess(windowManager, new SettingsService()).Start(); diff --git a/Gui/UILib/AppWindow.cs b/Gui/UILib/AppWindow.cs index ce2dd81..b04b235 100644 --- a/Gui/UILib/AppWindow.cs +++ b/Gui/UILib/AppWindow.cs @@ -357,5 +357,11 @@ namespace CMLeonOS.Gui.UILib } wm.Update(decorationWindow); } + + internal void RefreshTheme() + { + wm.Update(this); + RenderDecoration(); + } } } diff --git a/Gui/UILib/TreeNode.cs b/Gui/UILib/TreeNode.cs index 375c150..d7cfde6 100644 --- a/Gui/UILib/TreeNode.cs +++ b/Gui/UILib/TreeNode.cs @@ -13,6 +13,9 @@ namespace CMLeonOS.Gui.UILib internal string Text { get; set; } internal object Tag { get; set; } internal bool Expanded { get; set; } = false; + internal int AnimatedChildCount { get; set; } = 0; + internal bool ExpandingAnimation { get; set; } = false; + internal bool CollapsingAnimation { get; set; } = false; internal List Children { get; } = new List(); } } diff --git a/Gui/UILib/TreeView.cs b/Gui/UILib/TreeView.cs index 88c675c..5957b44 100644 --- a/Gui/UILib/TreeView.cs +++ b/Gui/UILib/TreeView.cs @@ -66,9 +66,16 @@ namespace CMLeonOS.Gui.UILib visibleNodes.Add(node); visibleLevels.Add(level); - if (node.Expanded) + if (node.Expanded || node.ExpandingAnimation || node.CollapsingAnimation) { - for (int i = 0; i < node.Children.Count; i++) + int visibleChildCount = node.Children.Count; + + if (node.ExpandingAnimation || node.CollapsingAnimation) + { + visibleChildCount = Math.Min(node.Children.Count, Math.Max(0, node.AnimatedChildCount)); + } + + for (int i = 0; i < visibleChildCount; i++) { AddVisibleNode(node.Children[i], level + 1); } @@ -90,7 +97,19 @@ namespace CMLeonOS.Gui.UILib if (node.Children.Count > 0 && x >= indentX && x <= indentX + 10) { - node.Expanded = !node.Expanded; + if (node.Expanded || node.ExpandingAnimation) + { + node.ExpandingAnimation = false; + node.CollapsingAnimation = true; + node.AnimatedChildCount = node.Children.Count; + } + else + { + node.Expanded = true; + node.CollapsingAnimation = false; + node.ExpandingAnimation = true; + node.AnimatedChildCount = 0; + } Render(); return; } @@ -106,6 +125,7 @@ namespace CMLeonOS.Gui.UILib DrawRectangle(0, 0, Width, Height, Border); BuildVisibleNodes(); + bool hasAnimation = false; int maxRows = Math.Min(visibleNodes.Count, Math.Max(0, Height / RowHeight)); for (int i = 0; i < maxRows; i++) @@ -124,7 +144,7 @@ namespace CMLeonOS.Gui.UILib { DrawRectangle(indentX, rowY + 6, 10, 10, Border); DrawHorizontalLine(6, indentX + 2, rowY + 11, Foreground); - if (!node.Expanded) + if (!node.Expanded || node.CollapsingAnimation) { DrawVerticalLine(6, indentX + 5, rowY + 8, Foreground); } @@ -134,7 +154,46 @@ namespace CMLeonOS.Gui.UILib DrawString(node.Text, Foreground, textX, rowY + 3); } + UpdateNodeAnimations(Nodes, ref hasAnimation); + + if (hasAnimation) + { + WM.UpdateQueue.Enqueue(this); + } + WM.Update(this); } + + private void UpdateNodeAnimations(List nodes, ref bool hasAnimation) + { + for (int i = 0; i < nodes.Count; i++) + { + TreeNode node = nodes[i]; + + if (node.ExpandingAnimation) + { + hasAnimation = true; + node.AnimatedChildCount++; + if (node.AnimatedChildCount >= node.Children.Count) + { + node.AnimatedChildCount = node.Children.Count; + node.ExpandingAnimation = false; + } + } + else if (node.CollapsingAnimation) + { + hasAnimation = true; + node.AnimatedChildCount--; + if (node.AnimatedChildCount <= 0) + { + node.AnimatedChildCount = 0; + node.CollapsingAnimation = false; + node.Expanded = false; + } + } + + UpdateNodeAnimations(node.Children, ref hasAnimation); + } + } } } diff --git a/Gui/UILib/UITheme.cs b/Gui/UILib/UITheme.cs index a5069ca..f84de0d 100644 --- a/Gui/UILib/UITheme.cs +++ b/Gui/UILib/UITheme.cs @@ -20,23 +20,136 @@ namespace CMLeonOS.Gui.UILib { internal static class UITheme { - internal static readonly Color WindowTitleBackground = Color.FromArgb(26, 34, 46); - internal static readonly Color WindowTitleTopHighlight = Color.FromArgb(52, 67, 90); - internal static readonly Color WindowTitleBottomBorder = Color.FromArgb(14, 18, 26); - internal static readonly Color WindowTitleText = Color.FromArgb(239, 244, 252); - internal static readonly Color WindowButtonBackground = Color.FromArgb(38, 49, 66); + internal static Color WindowTitleBackground { get; private set; } + internal static Color WindowTitleTopHighlight { get; private set; } + internal static Color WindowTitleBottomBorder { get; private set; } + internal static Color WindowTitleText { get; private set; } + internal static Color WindowButtonBackground { get; private set; } - internal static readonly Color Surface = Color.FromArgb(245, 248, 252); - internal static readonly Color SurfaceMuted = Color.FromArgb(228, 235, 245); - internal static readonly Color SurfaceBorder = Color.FromArgb(149, 164, 183); - internal static readonly Color TextPrimary = Color.FromArgb(33, 43, 56); - internal static readonly Color TextSecondary = Color.FromArgb(96, 109, 128); + internal static Color Surface { get; private set; } + internal static Color SurfaceMuted { get; private set; } + internal static Color SurfaceBorder { get; private set; } + internal static Color TextPrimary { get; private set; } + internal static Color TextSecondary { get; private set; } - internal static readonly Color Accent = Color.FromArgb(45, 117, 222); - internal static readonly Color AccentDark = Color.FromArgb(28, 89, 184); - internal static readonly Color AccentLight = Color.FromArgb(204, 225, 255); + internal static Color Accent { get; private set; } + internal static Color AccentDark { get; private set; } + internal static Color AccentLight { get; private set; } - internal static readonly Color Success = Color.FromArgb(45, 152, 99); - internal static readonly Color Warning = Color.FromArgb(210, 139, 54); + internal static Color Success { get; private set; } + internal static Color Warning { get; private set; } + + internal static string CurrentThemeName { get; private set; } = "Default"; + + internal static string[] GetThemeNames() + { + return new string[] { "Default", "Graphite", "Forest" }; + } + + internal static void ApplyTheme(string themeName) + { + string name = string.IsNullOrWhiteSpace(themeName) ? "Default" : themeName.Trim(); + + switch (name) + { + case "Graphite": + WindowTitleBackground = Color.FromArgb(31, 34, 40); + WindowTitleTopHighlight = Color.FromArgb(62, 69, 81); + WindowTitleBottomBorder = Color.FromArgb(18, 20, 24); + WindowTitleText = Color.FromArgb(239, 242, 247); + WindowButtonBackground = Color.FromArgb(47, 53, 61); + + Surface = Color.FromArgb(236, 239, 244); + SurfaceMuted = Color.FromArgb(214, 220, 228); + SurfaceBorder = Color.FromArgb(124, 134, 149); + TextPrimary = Color.FromArgb(36, 42, 52); + TextSecondary = Color.FromArgb(92, 101, 115); + + Accent = Color.FromArgb(90, 131, 212); + AccentDark = Color.FromArgb(64, 100, 176); + AccentLight = Color.FromArgb(205, 220, 247); + + Success = Color.FromArgb(61, 145, 108); + Warning = Color.FromArgb(196, 138, 60); + CurrentThemeName = "Graphite"; + break; + + case "Forest": + WindowTitleBackground = Color.FromArgb(24, 48, 36); + WindowTitleTopHighlight = Color.FromArgb(60, 98, 77); + WindowTitleBottomBorder = Color.FromArgb(13, 28, 20); + WindowTitleText = Color.FromArgb(240, 248, 243); + WindowButtonBackground = Color.FromArgb(38, 67, 52); + + Surface = Color.FromArgb(242, 248, 244); + SurfaceMuted = Color.FromArgb(220, 232, 224); + SurfaceBorder = Color.FromArgb(134, 160, 145); + TextPrimary = Color.FromArgb(33, 53, 41); + TextSecondary = Color.FromArgb(95, 116, 104); + + Accent = Color.FromArgb(53, 144, 98); + AccentDark = Color.FromArgb(37, 112, 74); + AccentLight = Color.FromArgb(202, 236, 217); + + Success = Color.FromArgb(42, 153, 92); + Warning = Color.FromArgb(191, 137, 55); + CurrentThemeName = "Forest"; + break; + + default: + WindowTitleBackground = Color.FromArgb(26, 34, 46); + WindowTitleTopHighlight = Color.FromArgb(52, 67, 90); + WindowTitleBottomBorder = Color.FromArgb(14, 18, 26); + WindowTitleText = Color.FromArgb(239, 244, 252); + WindowButtonBackground = Color.FromArgb(38, 49, 66); + + Surface = Color.FromArgb(245, 248, 252); + SurfaceMuted = Color.FromArgb(228, 235, 245); + SurfaceBorder = Color.FromArgb(149, 164, 183); + TextPrimary = Color.FromArgb(33, 43, 56); + TextSecondary = Color.FromArgb(96, 109, 128); + + Accent = Color.FromArgb(45, 117, 222); + AccentDark = Color.FromArgb(28, 89, 184); + AccentLight = Color.FromArgb(204, 225, 255); + + Success = Color.FromArgb(45, 152, 99); + Warning = Color.FromArgb(210, 139, 54); + CurrentThemeName = "Default"; + break; + } + } + + internal static void RefreshOpenWindows(CMLeonOS.Gui.WindowManager wm) + { + if (wm == null) + { + return; + } + + for (int i = 0; i < wm.Windows.Count; i++) + { + CMLeonOS.Gui.Window window = wm.Windows[i]; + if (window is AppWindow appWindow) + { + appWindow.RefreshTheme(); + } + else if (window is Control control) + { + control.Render(); + } + else + { + wm.Update(window); + } + } + + wm.RerenderAll(); + } + + static UITheme() + { + ApplyTheme("Default"); + } } } diff --git a/Settings/Settings.cs b/Settings/Settings.cs index dc6c34c..f1041c1 100644 --- a/Settings/Settings.cs +++ b/Settings/Settings.cs @@ -39,6 +39,7 @@ namespace CMLeonOS.Settings { "GUI_ScreenWidth", "1280" }, { "GUI_ScreenHeight", "800" }, { "GUI_WallpaperPath", "" }, + { "GUI_Theme", "Default" }, { "GUI_DarkNotepad", "false" }, { "SkipToGui", "false" } }; @@ -208,6 +209,23 @@ namespace CMLeonOS.Settings } } + public static string GUI_Theme + { + get + { + if (settings.TryGetValue("GUI_Theme", out string value)) + { + return string.IsNullOrWhiteSpace(value) ? "Default" : value; + } + return "Default"; + } + set + { + settings["GUI_Theme"] = string.IsNullOrWhiteSpace(value) ? "Default" : value; + SaveSettings(); + } + } + public static bool GUI_DarkNotepad { get