Skip to content

Commit d54ea9d

Browse files
committed
Theme tool strips from the dock palette and share marker colors
The renderer and colour table lived inside LogTabWindow and carried a palette of their own, so the toolbars drifted from the dock panel colours. Move both into LogExpert.UI/Theming, build them on the VS2015DarkTheme palette the dock panel already uses, and install them process-wide from ToolStripTheme so every strip in ManagerRenderMode picks them up. PaintHelper gains GetDefaultBackColor, GetDefaultForeColor and GetBookmarkMarkerTextColor(bool). The grid, the colour combo box, the highlight dialog and the LED indicator now take their colours from those instead of repeating the dark-mode ternaries, which is what let the highlight preview and the grid disagree. Adds tests for the renderer, the marker colour and the dark LED lookup.
1 parent cd8217a commit d54ea9d

11 files changed

Lines changed: 540 additions & 158 deletions

File tree

‎src/LogExpert.UI.Tests/Services/LedIndicatorServiceTests.cs‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,34 @@ public void ClearDirty_ClearsStateAndRaisesCleanIcon ()
451451
Assert.That(raisedIcon, Is.SameAs(_service.GetIcon(50, new LedState())));
452452
}
453453

454+
[TestCase(false, 100)]
455+
[TestCase(true, 170)]
456+
public void GetInactiveLedColor_FollowsColorMode (bool darkMode, int expectedGray)
457+
{
458+
var color = LedIndicatorService.GetInactiveLedColor(darkMode);
459+
460+
Assert.Multiple(() =>
461+
{
462+
Assert.That(color.R, Is.EqualTo(expectedGray));
463+
Assert.That(color.G, Is.EqualTo(expectedGray));
464+
Assert.That(color.B, Is.EqualTo(expectedGray));
465+
});
466+
}
467+
468+
[TestCase(false, 160)]
469+
[TestCase(true, 175)]
470+
public void GetTailOffLedColor_FollowsColorMode (bool darkMode, int expectedGray)
471+
{
472+
var color = LedIndicatorService.GetTailOffLedColor(darkMode);
473+
474+
Assert.Multiple(() =>
475+
{
476+
Assert.That(color.R, Is.EqualTo(expectedGray));
477+
Assert.That(color.G, Is.EqualTo(expectedGray));
478+
Assert.That(color.B, Is.EqualTo(expectedGray));
479+
});
480+
}
481+
454482
private static LogWindow CreateLogWindow ()
455483
{
456484
var configManagerMock = new Mock<IConfigManager>();
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
using System.Drawing.Imaging;
2+
using System.Runtime.Versioning;
3+
4+
using LogExpert.UI.Theming;
5+
6+
using NUnit.Framework;
7+
8+
using WeifenLuo.WinFormsUI.Docking;
9+
10+
namespace LogExpert.UI.Tests.Theming;
11+
12+
[TestFixture]
13+
[Apartment(ApartmentState.STA)] // Required for WinForms components
14+
[SupportedOSPlatform("windows")]
15+
public class DarkToolStripRendererTests
16+
{
17+
[Test]
18+
public void Constructor_WithTheVS2015DarkPalette_Succeeds ()
19+
{
20+
// Guards the wiring in ToolStripTheme: the theme has to expose its palette before any
21+
// window exists, otherwise the renderer cannot be built at start-up.
22+
var theme = new VS2015DarkTheme();
23+
24+
var renderer = new DarkToolStripRenderer(theme.ColorPalette);
25+
26+
Assert.That(renderer, Is.Not.Null);
27+
}
28+
29+
[Test]
30+
public void ShouldInvertIcon_DarkMonochromeGlyph_ReturnsTrue ()
31+
{
32+
// The resource glyphs are black-on-transparent PNGs.
33+
using var glyph = CreateIcon(Color.Black);
34+
35+
Assert.That(DarkToolStripRenderer.ShouldInvertIcon(glyph), Is.True);
36+
}
37+
38+
[Test]
39+
public void ShouldInvertIcon_ColouredIcon_ReturnsFalse ()
40+
{
41+
// Inverting a blue icon turned it orange; coloured icons must keep their colours.
42+
using var icon = CreateIcon(Color.FromArgb(0, 120, 215));
43+
44+
Assert.That(DarkToolStripRenderer.ShouldInvertIcon(icon), Is.False);
45+
}
46+
47+
[Test]
48+
public void ShouldInvertIcon_LightMonochromeGlyph_ReturnsFalse ()
49+
{
50+
// A light glyph is already visible on the dark strip; inverting would make it invisible.
51+
using var glyph = CreateIcon(Color.White);
52+
53+
Assert.That(DarkToolStripRenderer.ShouldInvertIcon(glyph), Is.False);
54+
}
55+
56+
[Test]
57+
public void ShouldInvertIcon_EmptyImage_ReturnsFalse ()
58+
{
59+
using var bitmap = new Bitmap(16, 16, PixelFormat.Format32bppArgb);
60+
61+
Assert.That(DarkToolStripRenderer.ShouldInvertIcon(bitmap), Is.False);
62+
}
63+
64+
[Test]
65+
public void ShouldInvertIcon_GreyscaleGlyphWithAntiAliasing_ReturnsTrue ()
66+
{
67+
// Real glyphs are anti-aliased, so their edge pixels are grey rather than black. The
68+
// tolerance has to accept them; otherwise half the toolbar would stay dark.
69+
using var bitmap = new Bitmap(16, 16, PixelFormat.Format32bppArgb);
70+
71+
using (var graphics = Graphics.FromImage(bitmap))
72+
using (var brush = new SolidBrush(Color.FromArgb(90, 90, 90)))
73+
{
74+
graphics.FillRectangle(brush, 2, 2, 12, 12);
75+
}
76+
77+
Assert.That(DarkToolStripRenderer.ShouldInvertIcon(bitmap), Is.True);
78+
}
79+
80+
private static Bitmap CreateIcon (Color color)
81+
{
82+
var bitmap = new Bitmap(16, 16, PixelFormat.Format32bppArgb);
83+
84+
using var graphics = Graphics.FromImage(bitmap);
85+
using var brush = new SolidBrush(color);
86+
graphics.FillRectangle(brush, 2, 2, 12, 12);
87+
88+
return bitmap;
89+
}
90+
}

‎src/LogExpert.UI.Tests/UI/PaintHelperTests.cs‎

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,73 @@ public void GetForeColorFromHighlightEntry_NoEntryInDarkMode_FallsBackToLightCol
7272
Assert.That(IsLight(foreColor), Is.True, $"expected a light fallback color but got {foreColor}");
7373
}
7474

75+
[Test]
76+
public void GetDefaultForeColor_DarkMode_UsesTheSoftOffWhite ()
77+
{
78+
// 220,220,220 rather than pure white; the grid, the color combo box and the highlight
79+
// preview have to agree on it.
80+
var foreColor = PaintHelper.GetDefaultForeColor(darkMode: true);
81+
82+
Assert.That(foreColor.ToArgb(), Is.EqualTo(Color.FromArgb(220, 220, 220).ToArgb()));
83+
}
84+
85+
[Test]
86+
public void GetDefaultForeColor_LightMode_UsesBlack ()
87+
{
88+
var foreColor = PaintHelper.GetDefaultForeColor(darkMode: false);
89+
90+
Assert.That(foreColor, Is.EqualTo(Color.Black));
91+
}
92+
93+
[Test]
94+
public void GetDefaultBackColor_MatchesTheWindowBackgroundOfEachColorMode ()
95+
{
96+
// 32,32,32 is the surface the grid paints cells with in dark mode; the highlight
97+
// preview has to use the same one instead of SystemColors.Window.
98+
Assert.Multiple(() =>
99+
{
100+
Assert.That(PaintHelper.GetDefaultBackColor(darkMode: true), Is.EqualTo(Color.FromArgb(32, 32, 32)));
101+
Assert.That(PaintHelper.GetDefaultBackColor(darkMode: false), Is.EqualTo(Color.White));
102+
});
103+
}
104+
105+
[Test]
106+
public void GetForeColorFromHighlightEntry_EntryWithoutForeColor_UsesTheDefaultForeColor ()
107+
{
108+
// HighlightEntry.ForegroundColor defaults to Color.Empty ("not set"). Both callers of
109+
// a highlight without a color must end up with exactly the same result.
110+
Assert.Multiple(() =>
111+
{
112+
Assert.That(PaintHelper.GetForeColorFromHighlightEntry(null, darkMode: true),
113+
Is.EqualTo(PaintHelper.GetDefaultForeColor(darkMode: true)));
114+
Assert.That(PaintHelper.GetForeColorFromHighlightEntry(new HighlightEntry(), darkMode: false),
115+
Is.EqualTo(PaintHelper.GetDefaultForeColor(darkMode: false)));
116+
});
117+
}
118+
119+
[Test]
120+
public void GetBookmarkMarkerTextColor_LightMode_KeepsDarkOrange ()
121+
{
122+
var markerColor = PaintHelper.GetBookmarkMarkerTextColor(darkMode: false);
123+
124+
Assert.That(markerColor.ToArgb(), Is.EqualTo(Color.FromArgb(255, 190, 100, 0).ToArgb()));
125+
}
126+
127+
[Test]
128+
public void GetBookmarkMarkerTextColor_DarkMode_IsBrighterAndStaysOrange ()
129+
{
130+
var dark = PaintHelper.GetBookmarkMarkerTextColor(darkMode: true);
131+
var light = PaintHelper.GetBookmarkMarkerTextColor(darkMode: false);
132+
133+
Assert.Multiple(() =>
134+
{
135+
Assert.That(dark.GetBrightness(), Is.GreaterThan(light.GetBrightness()),
136+
$"expected a brighter dark-mode marker but got {dark} vs {light}");
137+
Assert.That(dark.R, Is.GreaterThan(dark.G), "marker is expected to stay orange");
138+
Assert.That(dark.G, Is.GreaterThan(dark.B), "marker is expected to stay orange");
139+
});
140+
}
141+
75142
[Test]
76143
public void GetForeColorFromHighlightEntry_NoEntryInLightMode_FallsBackToBlack ()
77144
{

‎src/LogExpert.UI/Controls/ColorComboBox.cs‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
using System.Drawing.Drawing2D;
33
using System.Runtime.Versioning;
44

5+
using LogExpert.UI.Entities;
6+
57
namespace LogExpert.UI.Controls;
68

79
[SupportedOSPlatform("windows")]
@@ -68,12 +70,16 @@ private void OnColorComboBoxDrawItem (object sender, DrawItemEventArgs e)
6870
if (e.Index >= 0)
6971
{
7072
var darkMode = Application.IsDarkModeEnabled;
71-
var textBrush = darkMode ? Brushes.White : Brushes.Black;
72-
var borderPen = darkMode ? Pens.White : Pens.Black;
73+
74+
// Same off-white the grid uses, so the swatch labels match the log view.
75+
var textColor = PaintHelper.GetDefaultForeColor(darkMode);
76+
using var textBrush = new SolidBrush(textColor);
77+
using var borderPen = new Pen(textColor);
7378

7479
Rectangle rectangle = new(4, e.Bounds.Top + 2, 30, e.Bounds.Height - 4);
7580
var rectColor = (Color)Items[e.Index];
76-
e.Graphics.FillRectangle(new SolidBrush(rectColor), rectangle);
81+
using var swatchBrush = new SolidBrush(rectColor);
82+
e.Graphics.FillRectangle(swatchBrush, rectangle);
7783
e.Graphics.DrawRectangle(borderPen, rectangle);
7884

7985
if (e.Index == 0)

‎src/LogExpert.UI/Controls/LogWindow/LogWindow.cs‎

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,13 @@ internal partial class LogWindow : DockContent, ILogPaintContextUI, ILogView, IL
5858
private const string FONT_VERDANA = "Verdana";
5959
private static readonly Logger _logger = LogManager.GetCurrentClassLogger();
6060

61+
// Bookmark marker colors: the dark variants are more saturated so the small square keeps
62+
// enough contrast against the dark grid.
63+
private static readonly Color _bookmarkColor = Color.FromArgb(165, 200, 225);
64+
private static readonly Color _autoBookmarkColor = Color.FromArgb(180, 210, 180);
65+
private static readonly Color _bookmarkColorDark = Color.FromArgb(140, 195, 240);
66+
private static readonly Color _autoBookmarkColorDark = Color.FromArgb(150, 215, 150);
67+
6168
private readonly Image _advancedButtonImage;
6269

6370
private readonly BookmarkDataProvider _bookmarkProvider = new();
@@ -352,14 +359,15 @@ public LogWindow (ILogWindowCoordinator logWindowCoordinator, string fileName, b
352359
#region Properties
353360

354361
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
355-
public Color BookmarkColor { get; set; } = Color.FromArgb(165, 200, 225);
362+
public Color BookmarkColor { get; set; } = _bookmarkColor;
356363

357364
/// <summary>
358365
/// Color used to paint the bookmark marker in column 0 for auto-generated (highlight-triggered) bookmarks. Uses a
359-
/// lighter/more desaturated shade to distinguish from manual bookmarks.
366+
/// slightly different shade to distinguish from manual bookmarks; the dark variant is brighter, not dimmer,
367+
/// because the marker has to stand out against the dark grid.
360368
/// </summary>
361369
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
362-
public Color AutoBookmarkColor { get; set; } = Color.FromArgb(180, 210, 180);
370+
public Color AutoBookmarkColor { get; set; } = _autoBookmarkColor;
363371

364372
public ILogLineMemoryColumnizer CurrentColumnizer
365373
{
@@ -2742,12 +2750,12 @@ private void CreateDefaultViewStyle ()
27422750
PaintHelper.ApplyGridViewTheme(dataGridView, darkMode);
27432751
PaintHelper.ApplyGridViewTheme(filterGridView, darkMode);
27442752

2745-
// Bookmark marker column colors need more saturation against dark backgrounds.
2746-
if (darkMode)
2747-
{
2748-
BookmarkColor = Color.FromArgb(140, 195, 240); // brighter blue than light-mode #A5C8E1
2749-
AutoBookmarkColor = Color.FromArgb(150, 215, 150); // brighter green than light-mode #B4D2B4
2750-
}
2753+
// Bookmark marker colors need more saturation against dark backgrounds. Both variants
2754+
// are assigned explicitly: the color mode is fixed for the lifetime of the process
2755+
// (Program.SetColorMode runs before the first window is created), so this never has to
2756+
// be re-applied at runtime.
2757+
BookmarkColor = darkMode ? _bookmarkColorDark : _bookmarkColor;
2758+
AutoBookmarkColor = darkMode ? _autoBookmarkColorDark : _autoBookmarkColor;
27512759
}
27522760

27532761
[SupportedOSPlatform("windows")]
@@ -6642,7 +6650,7 @@ public void CellPainting (bool focused, int rowIndex, int columnIndex, bool isFi
66426650
var fontName = isFilteredGridView ? FONT_VERDANA : FONT_COURIER_NEW;
66436651
var stringToDraw = isFilteredGridView ? "!" : "i";
66446652

6645-
using var brush2 = new SolidBrush(PaintHelper.BookmarkMarkerTextColor);
6653+
using var brush2 = new SolidBrush(PaintHelper.GetBookmarkMarkerTextColor(Application.IsDarkModeEnabled));
66466654
using var font = new Font(fontName, Preferences.Font.Size, FontStyle.Bold);
66476655
e.Graphics.DrawString(stringToDraw, font, brush2, new RectangleF(rect.Left, rect.Top, rect.Width, rect.Height), _format);
66486656
}

‎src/LogExpert.UI/Dialogs/Highlight/HighlightEntryDialog.cs‎

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@
22
using System.Data;
33
using System.Globalization;
44
using System.Text.RegularExpressions;
5-
using System.Windows.Forms;
65

76
using ColumnizerLib;
87

98
using LogExpert.Core.Classes.Highlight;
109
using LogExpert.Core.Helpers;
1110
using LogExpert.Dialogs;
1211
using LogExpert.UI.Controls;
12+
using LogExpert.UI.Entities;
1313

1414
namespace LogExpert.UI.Dialogs.Highlight;
1515

@@ -405,11 +405,16 @@ private void UpdatePreview ()
405405
? Resources.HighlightEntryEditDialog_UI_Preview_Placeholder
406406
: text;
407407

408-
_previewLabel.ForeColor = GetSelectedColor(_colorBoxForeground, Application.IsDarkModeEnabled ? Color.White : Color.Black);
408+
// Preview with the same defaults the grid paints with. SystemColors.Window stays white
409+
// in dark mode, which would put the off-white preview text on a white background.
410+
var darkMode = Application.IsDarkModeEnabled;
411+
var defaultBackColor = PaintHelper.GetDefaultBackColor(darkMode);
412+
413+
_previewLabel.ForeColor = GetSelectedColor(_colorBoxForeground, PaintHelper.GetDefaultForeColor(darkMode));
409414

410415
_previewLabel.BackColor = _checkBoxNoBackground != null && _checkBoxNoBackground.Checked
411-
? SystemColors.Window
412-
: GetSelectedColor(_colorBoxBackground, SystemColors.Window);
416+
? defaultBackColor
417+
: GetSelectedColor(_colorBoxBackground, defaultBackColor);
413418

414419
var bold = _checkBoxBold != null && _checkBoxBold.Checked;
415420
var baseFont = Font;

0 commit comments

Comments
 (0)