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
This commit is contained in:
Michael Rogers
2024-07-31 16:42:22 +02:00
committed by GitHub
parent 46746e7a86
commit 2e207a4523
18 changed files with 930 additions and 120 deletions
+3 -1
View File
@@ -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 { }
}
}
}
+59 -2
View File
@@ -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;
}
}
+104 -21
View File
@@ -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<ISensor, Color> _sensorPlotColors = new Dictionary<ISensor, Color>();
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<Node> 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
+43 -4
View File
@@ -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<ISensor> sensors, IDictionary<ISensor, Color> colors)
public void SetSensors(List<ISensor> sensors, IDictionary<ISensor, Color> colors, double strokeThickness)
{
_model.Series.Clear();
var types = new HashSet<SensorType>();
@@ -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)
+13 -26
View File
@@ -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);
}
}
}
}
+297
View File
@@ -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<Theme> _all;
public static List<Theme> All
{
get
{
if (_all == null)
{
_all = new List<Theme>();
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;
}
}
}
@@ -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();
}
}
}
@@ -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();
}
}
}
@@ -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") { }
}
}
@@ -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));
}
}
}
}
@@ -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);
}
}
}
@@ -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));
}
}
}
}