From 2e207a452323cc0c17e51eead8c8692d41d85e59 Mon Sep 17 00:00:00 2001 From: Michael Rogers <47461109+MRog40@users.noreply.github.com> Date: Wed, 31 Jul 2024 08:42:22 -0600 Subject: [PATCH] Add theme and plot thickness options (#847) * Add theme and plot thickness options * Cleanup * Fixed themes, and made it so the plot colors are updated when the theme is changed * I think supporting themes beyond dark, light, and black is unecessary for the time being. * recall theme from settings before instantiating plot * Check for null setting * committing to merge * Fix * Cleanup * Cleanup * Fix from comments. Thank you kamkie! * Fixing last UI bugs * Fix windowed plot not theming on relaunch, change theme, show plot --- .../Tree/NodeControls/BaseTextControl.cs | 6 +- .../Tree/NodeControls/NodeCheckBox.cs | 6 +- .../Tree/NodeControls/NodePlusMinus.cs | 7 +- Aga.Controls/Tree/TreeColumn.cs | 122 +++---- Aga.Controls/Tree/TreeViewAdv.Draw.cs | 9 +- Aga.Controls/Tree/TreeViewAdv.cs | 12 +- LibreHardwareMonitor/UI/AboutBox.cs | 4 +- LibreHardwareMonitor/UI/MainForm.Designer.cs | 61 +++- LibreHardwareMonitor/UI/MainForm.cs | 125 ++++++-- LibreHardwareMonitor/UI/PlotPanel.cs | 47 ++- LibreHardwareMonitor/UI/SplitContainerAdv.cs | 39 +-- LibreHardwareMonitor/UI/Theme.cs | 297 ++++++++++++++++++ LibreHardwareMonitor/UI/Themes/BlackTheme.cs | 39 +++ LibreHardwareMonitor/UI/Themes/DarkTheme.cs | 41 +++ LibreHardwareMonitor/UI/Themes/LightTheme.cs | 38 +++ .../UI/Themes/ThemedHScrollIndicator.cs | 63 ++++ .../UI/Themes/ThemedToolStripRenderer.cs | 71 +++++ .../UI/Themes/ThemedVScrollIndicator.cs | 63 ++++ 18 files changed, 930 insertions(+), 120 deletions(-) create mode 100644 LibreHardwareMonitor/UI/Theme.cs create mode 100644 LibreHardwareMonitor/UI/Themes/BlackTheme.cs create mode 100644 LibreHardwareMonitor/UI/Themes/DarkTheme.cs create mode 100644 LibreHardwareMonitor/UI/Themes/LightTheme.cs create mode 100644 LibreHardwareMonitor/UI/Themes/ThemedHScrollIndicator.cs create mode 100644 LibreHardwareMonitor/UI/Themes/ThemedToolStripRenderer.cs create mode 100644 LibreHardwareMonitor/UI/Themes/ThemedVScrollIndicator.cs diff --git a/Aga.Controls/Tree/NodeControls/BaseTextControl.cs b/Aga.Controls/Tree/NodeControls/BaseTextControl.cs index 016c7b0..20e5f84 100644 --- a/Aga.Controls/Tree/NodeControls/BaseTextControl.cs +++ b/Aga.Controls/Tree/NodeControls/BaseTextControl.cs @@ -219,7 +219,7 @@ namespace Aga.Controls.Tree.NodeControls private void CreateBrushes(TreeNodeAdv node, DrawContext context, string text, out Brush backgroundBrush, out Color textColor, out Font font, ref string label) { - textColor = SystemColors.ControlText; + textColor = Parent.ForeColor; backgroundBrush = null; font = context.Font; if (context.DrawSelection == DrawSelectionMode.Active) @@ -229,11 +229,11 @@ namespace Aga.Controls.Tree.NodeControls } else if (context.DrawSelection == DrawSelectionMode.Inactive) { - textColor = SystemColors.ControlText; + textColor = Parent.ForeColor; backgroundBrush = SystemBrushes.InactiveBorder; } else if (context.DrawSelection == DrawSelectionMode.FullRowSelect) - textColor = SystemColors.ControlText; + textColor = Parent.ForeColor; if (!context.Enabled) textColor = SystemColors.GrayText; diff --git a/Aga.Controls/Tree/NodeControls/NodeCheckBox.cs b/Aga.Controls/Tree/NodeControls/NodeCheckBox.cs index d9824f1..dbfb3cb 100644 --- a/Aga.Controls/Tree/NodeControls/NodeCheckBox.cs +++ b/Aga.Controls/Tree/NodeControls/NodeCheckBox.cs @@ -55,7 +55,11 @@ namespace Aga.Controls.Tree.NodeControls { Rectangle bounds = GetBounds(node, context); CheckState state = GetCheckState(node); - if (Application.RenderWithVisualStyles) + if (TreeViewAdv.CustomCheckRenderFunc != null) + { + TreeViewAdv.CustomCheckRenderFunc(context.Graphics, bounds, state == CheckState.Checked); + } + else if (Application.RenderWithVisualStyles) { VisualStyleRenderer renderer; int scaledX = node.Tree.GetScaledSize(ImageSize, false); diff --git a/Aga.Controls/Tree/NodeControls/NodePlusMinus.cs b/Aga.Controls/Tree/NodeControls/NodePlusMinus.cs index efde813..209f5d8 100644 --- a/Aga.Controls/Tree/NodeControls/NodePlusMinus.cs +++ b/Aga.Controls/Tree/NodeControls/NodePlusMinus.cs @@ -59,7 +59,12 @@ namespace Aga.Controls.Tree.NodeControls int scaledX = node.Tree.GetScaledSize(ImageSize, false); int scaledY = node.Tree.GetScaledSize(ImageSize); int dy = (int)Math.Round((float)(r.Height - scaledY) / 2); - if (Application.RenderWithVisualStyles) + if (TreeViewAdv.CustomPlusMinusRenderFunc != null) + { + TreeViewAdv.CustomPlusMinusRenderFunc(context.Graphics, r, node.IsExpanded); + return; + } + else if (Application.RenderWithVisualStyles) { VisualStyleRenderer renderer; if (node.IsExpanded) diff --git a/Aga.Controls/Tree/TreeColumn.cs b/Aga.Controls/Tree/TreeColumn.cs index fcaf03d..1dff0d7 100644 --- a/Aga.Controls/Tree/TreeColumn.cs +++ b/Aga.Controls/Tree/TreeColumn.cs @@ -249,34 +249,41 @@ namespace Aga.Controls.Tree private void DrawContent(Graphics gr, Rectangle bounds, Font font) { - Rectangle innerBounds = new Rectangle(bounds.X + HeaderLeftMargin, bounds.Y, - bounds.Width - HeaderLeftMargin - HeaderRightMargin, - bounds.Height); - - if (SortOrder != SortOrder.None) - innerBounds.Width -= (SortMarkSize.Width + SortOrderMarkMargin); - - Size maxTextSize = TextRenderer.MeasureText(gr, Header, font, innerBounds.Size, TextFormatFlags.NoPadding); - Size textSize = TextRenderer.MeasureText(gr, Header, font, innerBounds.Size, _baseHeaderFlags); - - if (SortOrder != SortOrder.None) + if (TreeViewAdv.CustomColumnTextRenderFunc != null) { - int tw = Math.Min(textSize.Width, innerBounds.Size.Width); - - int x = 0; - if (TextAlign == HorizontalAlignment.Left) - x = innerBounds.X + tw + SortOrderMarkMargin; - else if (TextAlign == HorizontalAlignment.Right) - x = innerBounds.Right + SortOrderMarkMargin; - else - x = innerBounds.X + tw + (innerBounds.Width - tw) / 2 + SortOrderMarkMargin; - DrawSortMark(gr, bounds, x); - } - - if (textSize.Width < maxTextSize.Width) - TextRenderer.DrawText(gr, Header, font, innerBounds, SystemColors.ControlText, _baseHeaderFlags | TextFormatFlags.Left); + TreeViewAdv.CustomColumnTextRenderFunc(gr, bounds, font, Header); + } else - TextRenderer.DrawText(gr, Header, font, innerBounds, SystemColors.ControlText, _headerFlags); + { + Rectangle innerBounds = new Rectangle(bounds.X + HeaderLeftMargin, bounds.Y, + bounds.Width - HeaderLeftMargin - HeaderRightMargin, + bounds.Height); + + if (SortOrder != SortOrder.None) + innerBounds.Width -= (SortMarkSize.Width + SortOrderMarkMargin); + + Size maxTextSize = TextRenderer.MeasureText(gr, Header, font, innerBounds.Size, TextFormatFlags.NoPadding); + Size textSize = TextRenderer.MeasureText(gr, Header, font, innerBounds.Size, _baseHeaderFlags); + + if (SortOrder != SortOrder.None) + { + int tw = Math.Min(textSize.Width, innerBounds.Size.Width); + + int x = 0; + if (TextAlign == HorizontalAlignment.Left) + x = innerBounds.X + tw + SortOrderMarkMargin; + else if (TextAlign == HorizontalAlignment.Right) + x = innerBounds.Right + SortOrderMarkMargin; + else + x = innerBounds.X + tw + (innerBounds.Width - tw) / 2 + SortOrderMarkMargin; + DrawSortMark(gr, bounds, x); + } + + if (textSize.Width < maxTextSize.Width) + TextRenderer.DrawText(gr, Header, font, innerBounds, SystemColors.ControlText, _baseHeaderFlags | TextFormatFlags.Left); + else + TextRenderer.DrawText(gr, Header, font, innerBounds, SystemColors.ControlText, _headerFlags); + } } private void DrawSortMark(Graphics gr, Rectangle bounds, int x) @@ -304,34 +311,41 @@ namespace Aga.Controls.Tree internal static void DrawBackground(Graphics gr, Rectangle bounds, bool pressed, bool hot) { - if (Application.RenderWithVisualStyles) - { - CreateRenderers(); - if (pressed) - _pressedRenderer.DrawBackground(gr, bounds); - else if (hot) - _hotRenderer.DrawBackground(gr, bounds); - else - _normalRenderer.DrawBackground(gr, bounds); - } - else - { - gr.FillRectangle(SystemBrushes.Control, bounds); - Pen p1 = SystemPens.ControlLightLight; - Pen p2 = SystemPens.ControlDark; - Pen p3 = SystemPens.ControlDarkDark; - if (pressed) - gr.DrawRectangle(p2, bounds.X, bounds.Y, bounds.Width, bounds.Height); - else - { - gr.DrawLine(p1, bounds.X, bounds.Y, bounds.Right, bounds.Y); - gr.DrawLine(p3, bounds.X, bounds.Bottom, bounds.Right, bounds.Bottom); - gr.DrawLine(p3, bounds.Right - 1, bounds.Y, bounds.Right - 1, bounds.Bottom - 1); - gr.DrawLine(p1, bounds.Left, bounds.Y + 1, bounds.Left, bounds.Bottom - 2); - gr.DrawLine(p2, bounds.Right - 2, bounds.Y + 1, bounds.Right - 2, bounds.Bottom - 2); - gr.DrawLine(p2, bounds.X, bounds.Bottom - 1, bounds.Right - 2, bounds.Bottom - 1); - } - } + if (TreeViewAdv.CustomColumnBackgroundRenderFunc != null) + { + TreeViewAdv.CustomColumnBackgroundRenderFunc(gr, bounds, pressed, hot); + } + else + { + if (Application.RenderWithVisualStyles) + { + CreateRenderers(); + if (pressed) + _pressedRenderer.DrawBackground(gr, bounds); + else if (hot) + _hotRenderer.DrawBackground(gr, bounds); + else + _normalRenderer.DrawBackground(gr, bounds); + } + else + { + gr.FillRectangle(SystemBrushes.Control, bounds); + Pen p1 = SystemPens.ControlLightLight; + Pen p2 = SystemPens.ControlDark; + Pen p3 = SystemPens.ControlDarkDark; + if (pressed) + gr.DrawRectangle(p2, bounds.X, bounds.Y, bounds.Width, bounds.Height); + else + { + gr.DrawLine(p1, bounds.X, bounds.Y, bounds.Right, bounds.Y); + gr.DrawLine(p3, bounds.X, bounds.Bottom, bounds.Right, bounds.Bottom); + gr.DrawLine(p3, bounds.Right - 1, bounds.Y, bounds.Right - 1, bounds.Bottom - 1); + gr.DrawLine(p1, bounds.Left, bounds.Y + 1, bounds.Left, bounds.Bottom - 2); + gr.DrawLine(p2, bounds.Right - 2, bounds.Y + 1, bounds.Right - 2, bounds.Bottom - 2); + gr.DrawLine(p2, bounds.X, bounds.Bottom - 1, bounds.Right - 2, bounds.Bottom - 1); + } + } + } } #endregion diff --git a/Aga.Controls/Tree/TreeViewAdv.Draw.cs b/Aga.Controls/Tree/TreeViewAdv.Draw.cs index f42b9cd..60f8662 100644 --- a/Aga.Controls/Tree/TreeViewAdv.Draw.cs +++ b/Aga.Controls/Tree/TreeViewAdv.Draw.cs @@ -136,7 +136,7 @@ namespace Aga.Controls.Tree OnRowDraw(e, node, context, row, rowRect); if ((GridLineStyle & GridLineStyle.Horizontal) == GridLineStyle.Horizontal) { - e.Graphics.DrawLine(LightGrayPen, 0, rowRect.Bottom, e.Graphics.ClipBounds.Right, rowRect.Bottom); + e.Graphics.DrawLine(CustomHorizontalLinePen, 0, rowRect.Bottom, e.Graphics.ClipBounds.Right, rowRect.Bottom); } if (FullRowSelect) @@ -147,12 +147,12 @@ namespace Aga.Controls.Tree Rectangle focusRect = new Rectangle(OffsetX, rowRect.Y, ClientRectangle.Width, rowRect.Height); if (context.DrawSelection == DrawSelectionMode.Active) { - e.Graphics.FillRectangle(GrayBrush, focusRect); + e.Graphics.FillRectangle(CustomSelectedRowBrush, focusRect); context.DrawSelection = DrawSelectionMode.FullRowSelect; } else { - e.Graphics.FillRectangle(GrayBrush, focusRect); + e.Graphics.FillRectangle(CustomSelectedRowBrush, focusRect); context.DrawSelection = DrawSelectionMode.None; } } @@ -164,9 +164,6 @@ namespace Aga.Controls.Tree DrawNode(node, context); } - private Brush GrayBrush = new SolidBrush(Color.FromArgb(240, 240, 240)); - private Pen LightGrayPen = new Pen(Color.FromArgb(247, 247, 247)); - private void DrawVerticalGridLines(Graphics gr, int y) { int x = 0; diff --git a/Aga.Controls/Tree/TreeViewAdv.cs b/Aga.Controls/Tree/TreeViewAdv.cs index cc9d144..f798aa5 100644 --- a/Aga.Controls/Tree/TreeViewAdv.cs +++ b/Aga.Controls/Tree/TreeViewAdv.cs @@ -23,7 +23,15 @@ namespace Aga.Controls.Tree /// public partial class TreeViewAdv : Control { - private const int LeftMargin = 7; + public static Action CustomCheckRenderFunc; + public static Action CustomColumnBackgroundRenderFunc; + public static Action CustomColumnTextRenderFunc; + public static Color CustomSelectedTextColor = SystemColors.ControlText; + public static Pen CustomHorizontalLinePen = new Pen(Color.FromArgb(247, 247, 247)); + public static Action CustomPlusMinusRenderFunc; + public static Brush CustomSelectedRowBrush = new SolidBrush(Color.FromArgb(240, 240, 240)); + + private const int LeftMargin = 7; internal const int ItemDragSensivity = 4; private readonly int _columnHeaderHeight; private const int DividerWidth = 9; @@ -250,6 +258,8 @@ namespace Aga.Controls.Tree Font = _font; ExpandingIcon.IconChanged += ExpandingIconChanged; + + BackColor = Color.Black; } public void SetDPI() diff --git a/LibreHardwareMonitor/UI/AboutBox.cs b/LibreHardwareMonitor/UI/AboutBox.cs index 41cf6f0..3ecd41a 100644 --- a/LibreHardwareMonitor/UI/AboutBox.cs +++ b/LibreHardwareMonitor/UI/AboutBox.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.Drawing; using System.Windows.Forms; +using LibreHardwareMonitor.UI.Themes; namespace LibreHardwareMonitor.UI; @@ -21,6 +22,7 @@ public sealed partial class AboutBox : Form projectLinkLabel.Links.Add(0, projectLinkLabel.Text.Length, "https://github.com/LibreHardwareMonitor/LibreHardwareMonitor"); licenseLinkLabel.Links.Remove(licenseLinkLabel.Links[0]); licenseLinkLabel.Links.Add(0, licenseLinkLabel.Text.Length, "https://www.mozilla.org/en-US/MPL/2.0/"); + Theme.Current.Apply(this); } private void LinkLabel_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) @@ -31,4 +33,4 @@ public sealed partial class AboutBox : Form } catch { } } -} \ No newline at end of file +} diff --git a/LibreHardwareMonitor/UI/MainForm.Designer.cs b/LibreHardwareMonitor/UI/MainForm.Designer.cs index 98e1ca9..4a89fd9 100644 --- a/LibreHardwareMonitor/UI/MainForm.Designer.cs +++ b/LibreHardwareMonitor/UI/MainForm.Designer.cs @@ -87,6 +87,12 @@ namespace LibreHardwareMonitor.UI this.plotWindowMenuItem = new LibreHardwareMonitor.UI.ToolStripRadioButtonMenuItem(); this.plotBottomMenuItem = new LibreHardwareMonitor.UI.ToolStripRadioButtonMenuItem(); this.plotRightMenuItem = new LibreHardwareMonitor.UI.ToolStripRadioButtonMenuItem(); + this.themeMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.strokeThicknessMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.strokeThickness1ptMenuItem = new LibreHardwareMonitor.UI.ToolStripRadioButtonMenuItem(); + this.strokeThickness2ptMenuItem = new LibreHardwareMonitor.UI.ToolStripRadioButtonMenuItem(); + this.strokeThickness3ptMenuItem = new LibreHardwareMonitor.UI.ToolStripRadioButtonMenuItem(); + this.strokeThickness4ptMenuItem = new LibreHardwareMonitor.UI.ToolStripRadioButtonMenuItem(); this.splitPlotPanelScalingMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.splitPanelPercentageScalingMenuItem = new LibreHardwareMonitor.UI.ToolStripRadioButtonMenuItem(); this.splitPanelFixedPlotScalingMenuItem = new LibreHardwareMonitor.UI.ToolStripRadioButtonMenuItem(); @@ -440,6 +446,8 @@ namespace LibreHardwareMonitor.UI this.separatorMenuItem, this.temperatureUnitsMenuItem, this.plotLocationMenuItem, + this.strokeThicknessMenuItem, + this.themeMenuItem, this.splitPlotPanelScalingMenuItem, this.logSeparatorMenuItem, this.logSensorsMenuItem, @@ -537,6 +545,51 @@ namespace LibreHardwareMonitor.UI this.plotRightMenuItem.Size = new System.Drawing.Size(118, 22); this.plotRightMenuItem.Text = "Right"; // + // themeMenuItem + // + this.themeMenuItem.Name = "themeMenuItem"; + this.themeMenuItem.Size = new System.Drawing.Size(278, 22); + this.themeMenuItem.Text = "Theme"; + // + // strokeThicknessMenuItem + // + this.strokeThicknessMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.strokeThickness1ptMenuItem, + this.strokeThickness2ptMenuItem, + this.strokeThickness3ptMenuItem, + this.strokeThickness4ptMenuItem}); + this.strokeThicknessMenuItem.Name = "strokeThicknessMenuItem"; + this.strokeThicknessMenuItem.Size = new System.Drawing.Size(278, 22); + this.strokeThicknessMenuItem.Text = "Stroke Thickness"; + // + // strokeThickness1ptMenuItem + // + this.strokeThickness1ptMenuItem.CheckOnClick = true; + this.strokeThickness1ptMenuItem.Name = "strokeThickness1ptMenuItem"; + this.strokeThickness1ptMenuItem.Size = new System.Drawing.Size(224, 22); + this.strokeThickness1ptMenuItem.Text = "1pt"; + // + // strokeThickness2ptMenuItem + // + this.strokeThickness2ptMenuItem.CheckOnClick = true; + this.strokeThickness2ptMenuItem.Name = "strokeThickness2ptMenuItem"; + this.strokeThickness2ptMenuItem.Size = new System.Drawing.Size(224, 22); + this.strokeThickness2ptMenuItem.Text = "2pt"; + // + // strokeThickness3ptMenuItem + // + this.strokeThickness3ptMenuItem.CheckOnClick = true; + this.strokeThickness3ptMenuItem.Name = "strokeThickness3ptMenuItem"; + this.strokeThickness3ptMenuItem.Size = new System.Drawing.Size(224, 22); + this.strokeThickness3ptMenuItem.Text = "3pt"; + // + // strokeThickness4ptMenuItem + // + this.strokeThickness4ptMenuItem.CheckOnClick = true; + this.strokeThickness4ptMenuItem.Name = "strokeThickness4ptMenuItem"; + this.strokeThickness4ptMenuItem.Size = new System.Drawing.Size(224, 26); + this.strokeThickness4ptMenuItem.Text = "4pt"; + // // attachedPlotPanelScalingMenuItem // this.splitPlotPanelScalingMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { @@ -910,8 +963,6 @@ namespace LibreHardwareMonitor.UI // // splitContainer // - this.splitContainer.Border3DStyle = System.Windows.Forms.Border3DStyle.Raised; - this.splitContainer.Color = System.Drawing.SystemColors.Control; this.splitContainer.Cursor = System.Windows.Forms.Cursors.Default; this.splitContainer.Location = new System.Drawing.Point(12, 12); this.splitContainer.Name = "splitContainer"; @@ -1100,6 +1151,12 @@ namespace LibreHardwareMonitor.UI private System.Windows.Forms.ToolStripMenuItem psuMenuItem; private System.Windows.Forms.ToolStripMenuItem batteryMenuItem; private System.ComponentModel.BackgroundWorker backgroundUpdater; + private System.Windows.Forms.ToolStripMenuItem themeMenuItem; + private System.Windows.Forms.ToolStripMenuItem strokeThicknessMenuItem; + private ToolStripRadioButtonMenuItem strokeThickness4ptMenuItem; + private ToolStripRadioButtonMenuItem strokeThickness1ptMenuItem; + private ToolStripRadioButtonMenuItem strokeThickness2ptMenuItem; + private ToolStripRadioButtonMenuItem strokeThickness3ptMenuItem; } } diff --git a/LibreHardwareMonitor/UI/MainForm.cs b/LibreHardwareMonitor/UI/MainForm.cs index 1f71cc5..e467f74 100644 --- a/LibreHardwareMonitor/UI/MainForm.cs +++ b/LibreHardwareMonitor/UI/MainForm.cs @@ -10,10 +10,12 @@ using System.ComponentModel; using System.Drawing; using System.IO; using System.Linq; +using System.Runtime.InteropServices; using System.Windows.Forms; using Aga.Controls.Tree; using Aga.Controls.Tree.NodeControls; using LibreHardwareMonitor.Hardware; +using LibreHardwareMonitor.UI.Themes; using LibreHardwareMonitor.Utilities; using LibreHardwareMonitor.Wmi; @@ -21,6 +23,7 @@ namespace LibreHardwareMonitor.UI; public sealed partial class MainForm : Form { + private ToolStripMenuItem _autoThemeMenuItem; private readonly UserOption _autoStart; private readonly Computer _computer; private readonly SensorGadget _gadget; @@ -30,7 +33,6 @@ public sealed partial class MainForm : Form private readonly UserOption _logSensors; private readonly UserOption _minimizeOnClose; private readonly UserOption _minimizeToTray; - private readonly Color[] _plotColorPalette; private readonly PlotPanel _plotPanel; private readonly UserOption _readBatterySensors; private readonly UserOption _readCpuSensors; @@ -59,6 +61,8 @@ public sealed partial class MainForm : Form private bool _selectionDragging; private IDictionary _sensorPlotColors = new Dictionary(); private UserOption _showPlot; + private UserRadioGroup _strokeThickness; + private double _plotStrokeThickness = 2; public MainForm() { @@ -89,6 +93,16 @@ public sealed partial class MainForm : Form Height = _settings.GetValue("mainForm.Height", 640) }; + Theme setTheme = Theme.All.FirstOrDefault(theme => _settings.GetValue("theme", "auto") == theme.Id); + if (setTheme != null) + { + Theme.Current = setTheme; + } + else + { + Theme.SetAutoTheme(); + } + _plotPanel = new PlotPanel(_settings, _unitManager) { Font = SystemFonts.MessageBoxFont, Dock = DockStyle.Fill }; nodeCheckBox.IsVisibleValueNeeded += NodeCheckBox_IsVisibleValueNeeded; @@ -118,7 +132,6 @@ public sealed partial class MainForm : Form // Unix treeView.RowHeight = Math.Max(treeView.RowHeight, 18); splitContainer.BorderStyle = BorderStyle.None; - splitContainer.Border3DStyle = Border3DStyle.Adjust; splitContainer.SplitterWidth = 4; treeView.BorderStyle = BorderStyle.Fixed3D; _plotPanel.BorderStyle = BorderStyle.Fixed3D; @@ -142,21 +155,6 @@ public sealed partial class MainForm : Form nodeTextBoxValue.ToolTipProvider = tooltipProvider; _logger = new Logger(_computer); - _plotColorPalette = new Color[13]; - _plotColorPalette[0] = Color.Blue; - _plotColorPalette[1] = Color.OrangeRed; - _plotColorPalette[2] = Color.Green; - _plotColorPalette[3] = Color.LightSeaGreen; - _plotColorPalette[4] = Color.Goldenrod; - _plotColorPalette[5] = Color.DarkViolet; - _plotColorPalette[6] = Color.YellowGreen; - _plotColorPalette[7] = Color.SaddleBrown; - _plotColorPalette[8] = Color.RoyalBlue; - _plotColorPalette[9] = Color.DeepPink; - _plotColorPalette[10] = Color.MediumSeaGreen; - _plotColorPalette[11] = Color.Olive; - _plotColorPalette[12] = Color.Firebrick; - _computer.HardwareAdded += HardwareAdded; _computer.HardwareRemoved += HardwareRemoved; _computer.Open(); @@ -432,6 +430,7 @@ public sealed partial class MainForm : Form _computer.Accept(new SensorVisitor(delegate(ISensor sensor) { sensor.ValuesTimeWindow = timeWindow; })); }; + InitializeTheme(); InitializePlotForm(); InitializeSplitter(); @@ -501,6 +500,71 @@ public sealed partial class MainForm : Form } } + private void InitializeTheme() + { + mainMenu.Renderer = new ThemedToolStripRenderer(); + treeContextMenu.Renderer = new ThemedToolStripRenderer(); + ThemedVScrollIndicator.AddToControl(treeView); + ThemedHScrollIndicator.AddToControl(treeView); + + string themeSetting = _settings.GetValue("theme", "auto"); + bool themeSelected = false; + + void ClearThemeMenu() + { + foreach (ToolStripItem x in themeMenuItem.DropDownItems) + { + if (x is ToolStripMenuItem tmi) + { + tmi.Checked = false; + } + } + } + + if (Theme.SupportsAutoThemeSwitching()) + { + _autoThemeMenuItem = new ToolStripMenuItem(); + _autoThemeMenuItem.Text = "Auto"; + _autoThemeMenuItem.Click += (o, e) => + { + ClearThemeMenu(); + _autoThemeMenuItem.Checked = true; + Theme.SetAutoTheme(); + _settings.SetValue("theme", "auto"); + PlotSelectionChanged(o, e); + }; + themeMenuItem.DropDownItems.Add(_autoThemeMenuItem); + } + + foreach (Theme theme in Theme.All) + { + ToolStripMenuItem item = new ToolStripMenuItem(); + item.Text = theme.DisplayName; + item.Click += (o, e) => + { + ClearThemeMenu(); + item.Checked = true; + Theme.Current = theme; + _settings.SetValue("theme", theme.Id); + PlotSelectionChanged(o, e); + }; + themeMenuItem.DropDownItems.Add(item); + + if (themeSetting == theme.Id) + { + item.PerformClick(); + themeSelected = true; + } + } + + if (!themeSelected) + { + themeMenuItem.DropDownItems[0].PerformClick(); + } + + Theme.Current.Apply(this); + } + private void InitializeSplitter() { splitContainer.SplitterDistance = _settings.GetValue("splitContainer.SplitterDistance", 400); @@ -528,7 +592,10 @@ public sealed partial class MainForm : Form if (_plotLocation.Value == 0) { if (_showPlot.Value && Visible) + { + Theme.Current.Apply(_plotForm); _plotForm.Show(); + } else _plotForm.Hide(); } @@ -540,6 +607,16 @@ public sealed partial class MainForm : Form treeView.Invalidate(); }; + _strokeThickness = new UserRadioGroup("plotStroke", 1, new[] { strokeThickness1ptMenuItem, strokeThickness2ptMenuItem, strokeThickness3ptMenuItem, strokeThickness4ptMenuItem }, _settings); + + _strokeThickness.Changed += (sender, e) => + { + _plotStrokeThickness = (_strokeThickness.Value >= 0 && _strokeThickness.Value <= 3) + ? _strokeThickness.Value + 1 + : 4; + _plotPanel.UpdateStrokeThickness(_plotStrokeThickness); + }; + _plotLocation.Changed += delegate { switch (_plotLocation.Value) @@ -632,6 +709,8 @@ public sealed partial class MainForm : Form else _plotForm.Hide(); }; + + Theme.Current.Apply(_plotForm); } private void InsertSorted(IList nodes, HardwareNode node) @@ -705,7 +784,7 @@ public sealed partial class MainForm : Form if (!sensorNode.PenColor.HasValue) { colors.Add(sensorNode.Sensor, - _plotColorPalette[colorIndex % _plotColorPalette.Length]); + Theme.Current.PlotColorPalette[colorIndex % Theme.Current.PlotColorPalette.Length]); } selected.Add(sensorNode.Sensor); @@ -729,7 +808,7 @@ public sealed partial class MainForm : Form Color curColor = colors[curSelectedSensor]; if (usedColors.Contains(curColor)) { - foreach (Color potentialNewColor in _plotColorPalette) + foreach (Color potentialNewColor in Theme.Current.PlotColorPalette) { if (!colors.Values.Contains(potentialNewColor)) { @@ -752,7 +831,7 @@ public sealed partial class MainForm : Form } _sensorPlotColors = colors; - _plotPanel.SetSensors(selected, colors); + _plotPanel.SetSensors(selected, colors, _plotStrokeThickness); } private void NodeTextBoxText_EditorShowing(object sender, CancelEventArgs e) @@ -909,7 +988,6 @@ public sealed partial class MainForm : Form { expandPersistNode.Expanded = info.Node.IsExpanded; } - return; } @@ -1065,6 +1143,7 @@ public sealed partial class MainForm : Form protected override void WndProc(ref Message m) { const int WM_SYSCOMMAND = 0x112; + const int WM_WININICHANGE = 0x001A; const int SC_MINIMIZE = 0xF020; const int SC_CLOSE = 0xF060; @@ -1072,6 +1151,10 @@ public sealed partial class MainForm : Form { SysTrayHideShow(); } + else if (m.Msg == WM_WININICHANGE && Marshal.PtrToStringUni(m.LParam) == "ImmersiveColorSet" && _autoThemeMenuItem?.Checked == true) + { + Theme.SetAutoTheme(); + } else if (_minimizeOnClose.Value && m.Msg == WM_SYSCOMMAND && m.WParam.ToInt64() == SC_CLOSE) { //Apparently the user wants to minimize rather than close diff --git a/LibreHardwareMonitor/UI/PlotPanel.cs b/LibreHardwareMonitor/UI/PlotPanel.cs index c435737..bb0802b 100644 --- a/LibreHardwareMonitor/UI/PlotPanel.cs +++ b/LibreHardwareMonitor/UI/PlotPanel.cs @@ -16,6 +16,7 @@ using OxyPlot.Annotations; using OxyPlot.Axes; using OxyPlot.WindowsForms; using OxyPlot.Series; +using LibreHardwareMonitor.UI.Themes; namespace LibreHardwareMonitor.UI; @@ -48,7 +49,7 @@ public class PlotPanel : UserControl SetDpi(); _model = CreatePlotModel(); - _plot = new PlotView { Dock = DockStyle.Fill, Model = _model, BackColor = Color.White, ContextMenuStrip = CreateMenu() }; + _plot = new PlotView { Dock = DockStyle.Fill, Model = _model, BackColor = Color.Black, ContextMenuStrip = CreateMenu() }; _plot.MouseDown += (sender, e) => { if (e.Button == MouseButtons.Right) @@ -72,6 +73,34 @@ public class PlotPanel : UserControl SuspendLayout(); Controls.Add(_plot); ResumeLayout(true); + _plot.ShowTracker(new TrackerHitResult()); + _plot.HideTracker(); + foreach (Control plotControl in _plot.Controls) + { + plotControl.BackColor = Theme.Current.PlotBackgroundColor; + plotControl.ForeColor = Theme.Current.PlotTextColor; + } + ApplyTheme(); + } + + public void ApplyTheme() + { + _model.Background = Theme.Current.PlotBackgroundColor.ToOxyColor(); + _model.PlotAreaBorderColor = Theme.Current.PlotBorderColor.ToOxyColor(); + foreach (Axis axis in _model.Axes) + { + axis.AxislineColor = Theme.Current.PlotBorderColor.ToOxyColor(); + axis.MajorGridlineColor = Theme.Current.PlotGridMajorColor.ToOxyColor(); + axis.MinorGridlineColor = Theme.Current.PlotGridMinorColor.ToOxyColor(); + axis.TextColor = Theme.Current.PlotTextColor.ToOxyColor(); + axis.TitleColor = Theme.Current.PlotTextColor.ToOxyColor(); + axis.MinorTicklineColor = Theme.Current.PlotBorderColor.ToOxyColor(); + axis.TicklineColor = Theme.Current.PlotBorderColor.ToOxyColor(); + } + foreach (LineAnnotation annotation in _model.Annotations.Select(x => x as LineAnnotation).Where(x => x != null)) + { + annotation.Color = Theme.Current.PlotBorderColor.ToOxyColor(); + } } public void SetCurrentSettings() @@ -88,6 +117,7 @@ public class PlotPanel : UserControl private ContextMenuStrip CreateMenu() { ContextMenuStrip menu = new ContextMenuStrip(); + menu.Renderer = new ThemedToolStripRenderer(); menu.Opening += (sender, e) => { if (_cancelContextMenu) @@ -227,7 +257,7 @@ public class PlotPanel : UserControl ClipByXAxis = false, ClipByYAxis = false, LineStyle = LineStyle.Solid, - Color = OxyColors.Black, + Color = Theme.Current.PlotBorderColor.ToOxyColor(), YAxisKey = typeName, StrokeThickness = 2, }; @@ -275,7 +305,7 @@ public class PlotPanel : UserControl _dpiYScale = _dpiY / defaultDpi; } - public void SetSensors(List sensors, IDictionary colors) + public void SetSensors(List sensors, IDictionary colors, double strokeThickness) { _model.Series.Clear(); var types = new HashSet(); @@ -304,7 +334,7 @@ public class PlotPanel : UserControl { ItemsSource = sensor.Values.Select(value => CreateDataPoint(sensor.SensorType, value)), Color = colors[sensor].ToOxyColor(), - StrokeThickness = 1, + StrokeThickness = strokeThickness, YAxisKey = _axes[sensor.SensorType].Key, Title = sensor.Hardware.Name + " " + sensor.Name }; @@ -325,6 +355,15 @@ public class PlotPanel : UserControl InvalidatePlot(); } + public void UpdateStrokeThickness(double strokeThickness) + { + foreach (LineSeries series in _model.Series) + { + series.StrokeThickness = strokeThickness; + } + InvalidatePlot(); + } + private void UpdateAxesPosition() { if (_stackedAxes.Value) diff --git a/LibreHardwareMonitor/UI/SplitContainerAdv.cs b/LibreHardwareMonitor/UI/SplitContainerAdv.cs index 4d92e28..7d06347 100644 --- a/LibreHardwareMonitor/UI/SplitContainerAdv.cs +++ b/LibreHardwareMonitor/UI/SplitContainerAdv.cs @@ -7,14 +7,14 @@ using System; using System.Drawing; using System.Windows.Forms; +using LibreHardwareMonitor.UI.Themes; namespace LibreHardwareMonitor.UI; public class SplitContainerAdv : SplitContainer { private int _delta; - private Border3DStyle _border3DStyle = Border3DStyle.Raised; - private Color _bgColor = SystemColors.Control; + private bool _mouseOver; public SplitContainerAdv() { @@ -32,10 +32,8 @@ public class SplitContainerAdv : SplitContainer Graphics g = e.Graphics; Rectangle r = SplitterRectangle; - using (SolidBrush brush = new SolidBrush(_bgColor)) + using (SolidBrush brush = new SolidBrush(_mouseOver ? Theme.Current.SplitterHoverColor : Theme.Current.SplitterColor)) g.FillRectangle(brush, r); - - ControlPaint.DrawBorder3D(g, r, _border3DStyle); } protected override void OnKeyDown(KeyEventArgs e) @@ -73,6 +71,13 @@ public class SplitContainerAdv : SplitContainer IsSplitterFixed = true; } + protected override void OnMouseEnter(EventArgs e) + { + base.OnMouseEnter(e); + _mouseOver = true; + Invalidate(); + } + protected override void OnMouseMove(MouseEventArgs e) { if (IsSplitterFixed) @@ -107,6 +112,8 @@ public class SplitContainerAdv : SplitContainer { base.OnMouseLeave(e); Cursor = Cursors.Default; + _mouseOver = false; + Invalidate(); } protected override void OnMouseUp(MouseEventArgs e) @@ -115,24 +122,4 @@ public class SplitContainerAdv : SplitContainer IsSplitterFixed = false; Cursor.Current = Cursors.Default; } - - public Border3DStyle Border3DStyle - { - get { return _border3DStyle; } - set - { - _border3DStyle = value; - Invalidate(false); - } - } - - public Color Color - { - get { return _bgColor; } - set - { - _bgColor = value; - Invalidate(false); - } - } -} \ No newline at end of file +} diff --git a/LibreHardwareMonitor/UI/Theme.cs b/LibreHardwareMonitor/UI/Theme.cs new file mode 100644 index 0000000..e7bf614 --- /dev/null +++ b/LibreHardwareMonitor/UI/Theme.cs @@ -0,0 +1,297 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Runtime.InteropServices; +using System.Windows.Forms; +using Aga.Controls.Tree; +using Microsoft.Win32; + +namespace LibreHardwareMonitor.UI.Themes +{ + public abstract class Theme + { + private static Theme _current = new LightTheme(); + public static Theme Current + { + get { return _current; } + set + { + _current = value; + foreach (Form form in Application.OpenForms) + { + _current.Apply(form); + } + + Init(); + } + } + + private static void Init() + { + TreeViewAdv.CustomPlusMinusRenderFunc = (g, rect, isExpanded) => + { + int x = rect.Left; + int y = rect.Top + 5; + int size = 8; + using (Brush brush = new SolidBrush(Current.BackgroundColor)) + { + g.FillRectangle(brush, x - 1, y - 1, size + 4, size + 4); + } + using (Pen pen = new Pen(Current.TreeOutlineColor)) + { + + g.DrawRectangle(pen, x, y, size, size); + g.DrawLine(pen, x + 2, y + (size / 2), x + size - 2, y + (size / 2)); + if (!isExpanded) + { + g.DrawLine(pen, x + (size / 2), y + 2, x + (size / 2), y + size - 2); + } + } + }; + + TreeViewAdv.CustomCheckRenderFunc = (g, rect, isChecked) => + { + int x = rect.Left; + int y = rect.Top + 1; + int size = 12; + using (Brush brush = new SolidBrush(Current.BackgroundColor)) + { + g.FillRectangle(brush, x - 1, y - 1, 12, 12); + } + using (Pen pen = new Pen(Current.TreeOutlineColor)) + { + g.DrawRectangle(pen, x, y, size, size); + if (isChecked) + { + x += 3; + y += 3; + g.DrawLine(pen, x, y + 3, x + 2, y + 5); + g.DrawLine(pen, x + 2, y + 5, x + 6, y + 1); + g.DrawLine(pen, x, y + 4, x + 2, y + 6); + g.DrawLine(pen, x + 2, y + 6, x + 6, y + 2); + } + } + }; + + TreeViewAdv.CustomColumnBackgroundRenderFunc = (g, rect, isPressed, isHot) => + { + using (Brush brush = new SolidBrush(Current.TreeBackgroundColor)) + { + g.FillRectangle(brush, rect); + } + using (Pen pen = new Pen(Current.TreeRowSepearatorColor)) + { + g.DrawLine(pen, rect.Left, rect.Top, rect.Right, rect.Top); + g.DrawLine(pen, rect.Left, rect.Top + 1, rect.Right, rect.Top + 1); + } + }; + + TreeViewAdv.CustomColumnTextRenderFunc = (g, rect, font, text) => + { + TextRenderer.DrawText(g, text, font, rect, Current.TreeTextColor, TextFormatFlags.Left); + }; + + TreeViewAdv.CustomHorizontalLinePen = new Pen(Current.TreeRowSepearatorColor); + TreeViewAdv.CustomSelectedRowBrush = new SolidBrush(Current.TreeSelectedBackgroundColor); + TreeViewAdv.CustomSelectedTextColor = Current.TreeSelectedTextColor; + } + + private static List _all; + public static List All + { + get + { + if (_all == null) + { + _all = new List(); + foreach (Type type in typeof(Theme).Assembly.GetTypes()) + { + if (type != typeof(Theme) && typeof(Theme).IsAssignableFrom(type)) + { + _all.Add((Theme)type.GetConstructor(new Type[] { }).Invoke(new object[] { })); + } + } + } + return _all.OrderBy(x => x.DisplayName).ToList(); + } + } + + public static bool SupportsAutoThemeSwitching() + { + if (Software.OperatingSystem.IsUnix) + { + return false; + } + + if (Registry.GetValue(@"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize", "AppsUseLightTheme", -1) is int useLightTheme) + { + return useLightTheme != -1; + } + return false; + } + + public static void SetAutoTheme() + { + if (Software.OperatingSystem.IsUnix) + { + return; + } + + if (Registry.GetValue(@"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize", "AppsUseLightTheme", 1) is int useLightTheme) + { + if (useLightTheme > 0) + { + Current = new LightTheme(); + } + else + { + Current = new DarkTheme(); + } + } + else + { + // Fallback incase registry fails + Current = new LightTheme(); + } + } + + public Theme(string id, string displayName) + { + Id = id; + DisplayName = displayName; + } + + public string Id { get; } + public string DisplayName { get; } + public abstract Color BackgroundColor { get; } + public abstract Color ForegroundColor { get; } + public abstract Color HyperlinkColor { get; } + public abstract Color LineColor { get; } + public abstract Color StrongLineColor { get; } + public abstract Color SelectedBackgroundColor { get; } + public abstract Color SelectedForegroundColor { get; } + + // button + public virtual Color ButtonBackgroundColor => BackgroundColor; + public virtual Color ButtonBorderColor => ForegroundColor; + public virtual Color ButtonHoverBackgroundColor => SelectedBackgroundColor; + public virtual Color ButtonPressedBackgroundColor => LineColor; + public virtual Color ButtonTextColor => ForegroundColor; + + // menu + public virtual Color MenuBackgroundColor => BackgroundColor; + public virtual Color MenuBorderColor => StrongLineColor; + public virtual Color MenuForegroundColor => ForegroundColor; + public virtual Color MenuSelectedBackgroundColor => SelectedBackgroundColor; + public virtual Color MenuSelectedForegroundColor => SelectedForegroundColor; + + // plot + public virtual Color PlotBackgroundColor => BackgroundColor; + public virtual Color PlotBorderColor => ForegroundColor; + public abstract Color[] PlotColorPalette { get; } + public abstract Color PlotGridMajorColor { get; } + public abstract Color PlotGridMinorColor { get; } + public virtual Color PlotTextColor => ForegroundColor; + + // scrollbar + public virtual Color ScrollbarBackground => BackgroundColor; + public virtual Color ScrollbarTrack => StrongLineColor; + + // splitter + public virtual Color SplitterColor => BackgroundColor; + public virtual Color SplitterHoverColor => SelectedBackgroundColor; + + // tree + public virtual Color TreeBackgroundColor => BackgroundColor; + public virtual Color TreeOutlineColor => ForegroundColor; + public virtual Color TreeSelectedBackgroundColor => SelectedBackgroundColor; + public virtual Color TreeTextColor => ForegroundColor; + public virtual Color TreeSelectedTextColor => SelectedForegroundColor; + public virtual Color TreeRowSepearatorColor => LineColor; + + // window + public virtual Color WindowTitlebarBackgroundColor => BackgroundColor; + public abstract bool WindowTitlebarFallbackToImmersiveDarkMode { get; } + public virtual Color WindowTitlebarForegroundColor => ForegroundColor; + + public void Apply(Form form) + { + if (IsWindows10OrGreater(22000)) + { + // Windows 11, Set the titlebar color based on theme + int color = ColorTranslator.ToWin32(WindowTitlebarBackgroundColor); + DwmSetWindowAttribute(form.Handle, DWMWA_CAPTION_COLOR, ref color, sizeof(int)); + color = ColorTranslator.ToWin32(WindowTitlebarForegroundColor); + DwmSetWindowAttribute(form.Handle, DWMWA_TEXT_COLOR, ref color, sizeof(int)); + } + else if (IsWindows10OrGreater(17763)) + { + // Windows 10, fallback to using "Immersive Dark Mode" instead + var attribute = DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1; + if (IsWindows10OrGreater(18985)) + { + // Windows 10 20H1 or later + attribute = DWMWA_USE_IMMERSIVE_DARK_MODE; + } + int useImmersiveDarkMode = WindowTitlebarFallbackToImmersiveDarkMode ? 1 : 0; + DwmSetWindowAttribute(form.Handle, (int)attribute, ref useImmersiveDarkMode, sizeof(int)); + } + form.BackColor = BackgroundColor; + foreach (Control control in form.Controls) + { + Apply(control); + } + } + + public void Apply(Control control) + { + if (control is Button button) + { + button.ForeColor = ButtonTextColor; + button.FlatStyle = FlatStyle.Flat; + button.FlatAppearance.BorderColor = ButtonBorderColor; + button.FlatAppearance.MouseOverBackColor = ButtonHoverBackgroundColor; + button.FlatAppearance.MouseDownBackColor = ButtonPressedBackgroundColor; + } + else if (control is LinkLabel linkLabel) + { + linkLabel.LinkColor = HyperlinkColor; + } + else if (control is PlotPanel plotPanel) + { + plotPanel.ApplyTheme(); + } + else if (control is TreeViewAdv treeView) + { + treeView.BackColor = TreeBackgroundColor; + treeView.ForeColor = TreeTextColor; + treeView.LineColor = TreeOutlineColor; + } + else + { + control.BackColor = BackgroundColor; + control.ForeColor = ForegroundColor; + } + + foreach (Control child in control.Controls) + { + Apply(child); + } + } + + [DllImport("dwmapi.dll")] + private static extern int DwmSetWindowAttribute(IntPtr hwnd, int attr, ref int attrValue, int attrSize); + + private const int DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 = 19; + private const int DWMWA_USE_IMMERSIVE_DARK_MODE = 20; + private const int DWMWA_BORDER_COLOR = 34; + private const int DWMWA_CAPTION_COLOR = 35; + private const int DWMWA_TEXT_COLOR = 36; + + private static bool IsWindows10OrGreater(int build = -1) + { + return !Software.OperatingSystem.IsUnix && Environment.OSVersion.Version.Major >= 10 && Environment.OSVersion.Version.Build >= build; + } + } +} diff --git a/LibreHardwareMonitor/UI/Themes/BlackTheme.cs b/LibreHardwareMonitor/UI/Themes/BlackTheme.cs new file mode 100644 index 0000000..897185b --- /dev/null +++ b/LibreHardwareMonitor/UI/Themes/BlackTheme.cs @@ -0,0 +1,39 @@ +using System.Drawing; +using System.Linq; + +namespace LibreHardwareMonitor.UI.Themes +{ + public class BlackTheme : LightTheme + { + private readonly Color[] _plotColorPalette; + public override Color ForegroundColor => Color.FromArgb(218, 218, 218); + public override Color BackgroundColor => Color.FromArgb(0, 0, 0); + public override Color HyperlinkColor => Color.FromArgb(144, 220, 232); + public override Color SelectedForegroundColor => ForegroundColor; + public override Color SelectedBackgroundColor => ColorTranslator.FromHtml("#090A17"); + public override Color LineColor => ColorTranslator.FromHtml("#070A12"); + public override Color StrongLineColor => ColorTranslator.FromHtml("#091217"); + public override Color[] PlotColorPalette => _plotColorPalette; + public override Color PlotGridMajorColor => Color.FromArgb(73, 73, 73); + public override Color PlotGridMinorColor => Color.FromArgb(33, 33, 33); + public override bool WindowTitlebarFallbackToImmersiveDarkMode => true; + + public BlackTheme() : base("black", "Black") + { + string[] colors = { + "#FF2525", + "#1200FF", + "#00FF5B", + "#FFE53B", + "#00FFFF", + "#FF0A6C", + "#2D27FF", + "#FF2CDF", + "#00E1FD", + "#0A5057" + }; + + _plotColorPalette = colors.Select(color => ColorTranslator.FromHtml(color)).ToArray(); + } + } +} diff --git a/LibreHardwareMonitor/UI/Themes/DarkTheme.cs b/LibreHardwareMonitor/UI/Themes/DarkTheme.cs new file mode 100644 index 0000000..462de55 --- /dev/null +++ b/LibreHardwareMonitor/UI/Themes/DarkTheme.cs @@ -0,0 +1,41 @@ +using System.Drawing; +using System.Linq; + +namespace LibreHardwareMonitor.UI.Themes +{ + public class DarkTheme : LightTheme + { + private readonly Color[] _plotColorPalette; + public override Color ForegroundColor => Color.FromArgb(233, 233, 233); + public override Color BackgroundColor => Color.FromArgb(30, 30, 30); + public override Color HyperlinkColor => Color.FromArgb(144, 220, 232); + public override Color SelectedForegroundColor => ForegroundColor; + public override Color SelectedBackgroundColor => Color.FromArgb(45, 45, 45); + public override Color LineColor => Color.FromArgb(38, 38, 38); + public override Color StrongLineColor => Color.FromArgb(53, 53, 53); + public override Color[] PlotColorPalette => _plotColorPalette; + public override Color PlotGridMajorColor => Color.FromArgb(93, 93, 93); + public override Color PlotGridMinorColor => Color.FromArgb(53, 53, 53); + public override bool WindowTitlebarFallbackToImmersiveDarkMode => true; + + public DarkTheme() : base("dark", "Dark") + { + string[] colors = { + "#F07178", + "#82AAFF", + "#C3E88D", + "#FFCB6B", + "#009688", + "#89DDF3", + "#FFE082", + "#7986CB", + "#C792EA", + "#FF5370", + "#73d1c8", + "#F78C6A" + }; + + _plotColorPalette = colors.Select(color => ColorTranslator.FromHtml(color)).ToArray(); + } + } +} diff --git a/LibreHardwareMonitor/UI/Themes/LightTheme.cs b/LibreHardwareMonitor/UI/Themes/LightTheme.cs new file mode 100644 index 0000000..0612acf --- /dev/null +++ b/LibreHardwareMonitor/UI/Themes/LightTheme.cs @@ -0,0 +1,38 @@ +using System.Drawing; + +namespace LibreHardwareMonitor.UI.Themes +{ + public class LightTheme : Theme + { + private readonly Color[] _plotColorPalette = new Color[] { + Color.Blue, + Color.OrangeRed, + Color.Green, + Color.LightSeaGreen, + Color.Goldenrod, + Color.DarkViolet, + Color.YellowGreen, + Color.SaddleBrown, + Color.RoyalBlue, + Color.DeepPink, + Color.MediumSeaGreen, + Color.Olive, + Color.Firebrick + }; + + public LightTheme(string id, string displayName) : base(id, displayName) { } + public override Color ForegroundColor => Color.FromArgb(0, 0, 0); + public override Color BackgroundColor => Color.FromArgb(255, 255, 255); + public override Color HyperlinkColor => Color.FromArgb(0, 0, 255); + public override Color SelectedForegroundColor => ForegroundColor; + public override Color SelectedBackgroundColor => Color.FromArgb(240, 240, 240); + public override Color LineColor => Color.FromArgb(247, 247, 247); + public override Color StrongLineColor => Color.FromArgb(209, 209, 209); + public override Color[] PlotColorPalette => _plotColorPalette; + public override Color PlotGridMajorColor => Color.FromArgb(192, 192, 192); + public override Color PlotGridMinorColor => Color.FromArgb(232, 232, 232); + public override bool WindowTitlebarFallbackToImmersiveDarkMode => false; + + public LightTheme() : base("light", "Light") { } + } +} diff --git a/LibreHardwareMonitor/UI/Themes/ThemedHScrollIndicator.cs b/LibreHardwareMonitor/UI/Themes/ThemedHScrollIndicator.cs new file mode 100644 index 0000000..d66438b --- /dev/null +++ b/LibreHardwareMonitor/UI/Themes/ThemedHScrollIndicator.cs @@ -0,0 +1,63 @@ +using System.Drawing; +using System.Windows.Forms; + +namespace LibreHardwareMonitor.UI.Themes +{ + public class ThemedHScrollIndicator : Control + { + private readonly HScrollBar _scrollbar; + + public static void AddToControl(Control control) + { + foreach (Control child in control.Controls) + { + if (child is HScrollBar scrollbar) + { + control.Controls.Add(new ThemedHScrollIndicator(scrollbar)); + return; + } + } + } + + public ThemedHScrollIndicator(HScrollBar scrollBar) + { + _scrollbar = scrollBar; + + Height = 8; + SetStyle(ControlStyles.ResizeRedraw, true); + SetStyle(ControlStyles.AllPaintingInWmPaint, true); + SetStyle(ControlStyles.UserPaint, true); + SetStyle(ControlStyles.OptimizedDoubleBuffer, true); + Left = 0; + Top = scrollBar.Parent.Height - Height; + Size = new Size(scrollBar.Parent.Width, Height); + Anchor = AnchorStyles.Right | AnchorStyles.Left | AnchorStyles.Bottom; + Visible = scrollBar.Visible; + + scrollBar.VisibleChanged += (s, e) => Visible = (s as ScrollBar).Visible; + scrollBar.Scroll += (s, e) => Invalidate(); + scrollBar.ValueChanged += (s, e) => Invalidate(); + + scrollBar.Height = 0; + } + + protected override void OnPaint(PaintEventArgs e) + { + Graphics g = e.Graphics; + + using (SolidBrush brush = new SolidBrush(Theme.Current.ScrollbarBackground)) + g.FillRectangle(brush, new Rectangle(0, 0, Bounds.Width, Bounds.Height)); + + int width = Bounds.Width; + int range = _scrollbar.Maximum - _scrollbar.Minimum; + + if (range > 0) + { + int start = width * (_scrollbar.Value - _scrollbar.Minimum) / range; + int end = width * (_scrollbar.Value - _scrollbar.Minimum + _scrollbar.LargeChange) / range; + using (SolidBrush brush = new SolidBrush(Theme.Current.ScrollbarTrack)) + g.FillRectangle(brush, new Rectangle(start, 2, end - start, Bounds.Height - 4)); + } + } + } +} diff --git a/LibreHardwareMonitor/UI/Themes/ThemedToolStripRenderer.cs b/LibreHardwareMonitor/UI/Themes/ThemedToolStripRenderer.cs new file mode 100644 index 0000000..32a1eda --- /dev/null +++ b/LibreHardwareMonitor/UI/Themes/ThemedToolStripRenderer.cs @@ -0,0 +1,71 @@ +using System.Drawing; +using System.Windows.Forms; + +namespace LibreHardwareMonitor.UI.Themes +{ + public class ThemedToolStripRenderer : ToolStripRenderer + { + protected override void OnRenderSeparator(ToolStripSeparatorRenderEventArgs e) + { + if (e.Item is not ToolStripSeparator) + { + base.OnRenderSeparator(e); + return; + } + + Rectangle bounds = new(Point.Empty, e.Item.Size); + using (Brush brush = new SolidBrush(Theme.Current.MenuBackgroundColor)) + e.Graphics.FillRectangle(brush, bounds); + } + + protected override void OnRenderArrow(ToolStripArrowRenderEventArgs e) + { + e.ArrowColor = e.Item.Selected ? Theme.Current.MenuSelectedForegroundColor : Theme.Current.MenuForegroundColor; + base.OnRenderArrow(e); + } + + protected override void OnRenderItemCheck(ToolStripItemImageRenderEventArgs e) + { + using (Pen pen = new Pen(e.Item.Selected ? Theme.Current.MenuSelectedForegroundColor : Theme.Current.MenuForegroundColor)) + { + int x = 10; + int y = 6; + e.Graphics.DrawLine(pen, x, y + 3, x + 2, y + 5); + e.Graphics.DrawLine(pen, x + 2, y + 5, x + 6, y + 1); + e.Graphics.DrawLine(pen, x, y + 4, x + 2, y + 6); + e.Graphics.DrawLine(pen, x + 2, y + 6, x + 6, y + 2); + } + } + + protected override void OnRenderItemText(ToolStripItemTextRenderEventArgs e) + { + e.TextColor = e.Item.Selected ? Theme.Current.MenuSelectedForegroundColor : Theme.Current.MenuForegroundColor; + base.OnRenderItemText(e); + } + + protected override void OnRenderToolStripBorder(ToolStripRenderEventArgs e) + { + if (e.ToolStrip.Parent is not Form) + { + Rectangle bounds = new(Point.Empty, new Size(e.ToolStrip.Width - 1, e.ToolStrip.Height - 1)); + using (Pen pen = new Pen(Theme.Current.MenuBorderColor)) + e.Graphics.DrawRectangle(pen, bounds); + } + } + + protected override void OnRenderToolStripBackground(ToolStripRenderEventArgs e) + { + Rectangle bounds = new(Point.Empty, e.ToolStrip.Size); + using (Brush brush = new SolidBrush(Theme.Current.MenuBackgroundColor)) + e.Graphics.FillRectangle(brush, bounds); + } + + protected override void OnRenderMenuItemBackground(ToolStripItemRenderEventArgs e) + { + Rectangle bounds = new(Point.Empty, e.Item.Size); + + using (Brush brush = new SolidBrush(e.Item.Selected ? Theme.Current.MenuSelectedBackgroundColor : Theme.Current.MenuBackgroundColor)) + e.Graphics.FillRectangle(brush, bounds); + } + } +} diff --git a/LibreHardwareMonitor/UI/Themes/ThemedVScrollIndicator.cs b/LibreHardwareMonitor/UI/Themes/ThemedVScrollIndicator.cs new file mode 100644 index 0000000..6cdb70a --- /dev/null +++ b/LibreHardwareMonitor/UI/Themes/ThemedVScrollIndicator.cs @@ -0,0 +1,63 @@ +using System.Drawing; +using System.Windows.Forms; + +namespace LibreHardwareMonitor.UI.Themes +{ + public class ThemedVScrollIndicator : Control + { + private readonly VScrollBar _scrollbar; + + public static void AddToControl(Control control) + { + foreach (Control child in control.Controls) + { + if (child is VScrollBar scrollbar) + { + control.Controls.Add(new ThemedVScrollIndicator(scrollbar)); + return; + } + } + } + + public ThemedVScrollIndicator(VScrollBar scrollBar) + { + _scrollbar = scrollBar; + + Width = 8; + SetStyle(ControlStyles.ResizeRedraw, true); + SetStyle(ControlStyles.AllPaintingInWmPaint, true); + SetStyle(ControlStyles.UserPaint, true); + SetStyle(ControlStyles.OptimizedDoubleBuffer, true); + Left = scrollBar.Parent.Width - Width; + Top = 0; + Size = new Size(Width, scrollBar.Parent.Height); + Anchor = AnchorStyles.Right | AnchorStyles.Top | AnchorStyles.Bottom; + Visible = scrollBar.Visible; + + scrollBar.VisibleChanged += (s, e) => Visible = (s as ScrollBar).Visible; + scrollBar.Scroll += (s, e) => Invalidate(); + scrollBar.ValueChanged += (s, e) => Invalidate(); + + scrollBar.Width = 0; + } + + protected override void OnPaint(PaintEventArgs e) + { + Graphics g = e.Graphics; + + using (SolidBrush brush = new SolidBrush(Theme.Current.ScrollbarBackground)) + g.FillRectangle(brush, new Rectangle(0, 0, Bounds.Width, Bounds.Height)); + + int height = Bounds.Height; + int range = _scrollbar.Maximum - _scrollbar.Minimum; + + if (range > 0) + { + int start = height * (_scrollbar.Value - _scrollbar.Minimum) / range; + int end = height * (_scrollbar.Value - _scrollbar.Minimum + _scrollbar.LargeChange) / range; + using (SolidBrush brush = new SolidBrush(Theme.Current.ScrollbarTrack)) + g.FillRectangle(brush, new Rectangle(2, start, Bounds.Width - 4, end - start)); + } + } + } +}