Skip to content

Commit 12c0630

Browse files
mfkeeq199458trae-solo-agent
authored andcommitted
feat(ui): tune dark theme colors and make ToolStrip icons visible
## Summary of changes ### ToolStrip dark adaptation (LogTabWindow.cs) - Add ConfigureToolStripDark(): installs a ToolStripManager global renderer with a VS Dark-derived color palette. Bypasses ToolStripContainer's TopToolStripPanel which forces RenderMode=System on every child bar. - Add DarkToolStripRenderer: OnRenderItemImage inverts dark-mode PNG glyphs via ColorMatrix so toolbar icons render as light-on-transparent against the dark bar; OnRenderItemText forces light foreground text. - Add DarkToolStripColorTable: override ProfessionalColorTable with VS2015 Dark Theme surface/highlight/accent colors. ### Hard-coded color fixes - PaintHelper.cs: soft off-white foreground (220 220 220) instead of pure #FFFFFF to reduce eye strain; centralize bookmark marker color as BookmarkMarkerTextColor property (brighter orange on dark). - LogWindow.cs: bookmark column colors get more saturation on dark. - LedIndicatorService.cs: off/neutral LEDs brightened on dark; signal colors (red/orange/yellow/green) kept theme-independent. - ColorComboBox.cs: border / text / hatch brush adapt to Application.IsDarkModeEnabled. - HighlightEntryDialog.cs: preview label foreground fallback chooses Color.White on dark instead of always falling back to Color.Black. ## Test plan - [x] Build passes with 0 errors - [x] OnRenderItemImage called and e.Image non-null at runtime - [ ] Run dark mode and verify: toolbar icons visible, colors readable, dropdowns, hover/pressed states, dialog previews ## Risk - ToolStripManager.VisualStylesEnabled = false is app-global (only set in dark mode). Other dialogs with ToolStrips not yet verified. - ColorMatrix inverts ALL RGB channels — correct for monochrome glyphs but would flip semantic colored icons if any are added later.
1 parent e249fb2 commit 12c0630

6 files changed

Lines changed: 196 additions & 14 deletions

File tree

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

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,23 +67,28 @@ private void OnColorComboBoxDrawItem (object sender, DrawItemEventArgs e)
6767
e.DrawBackground();
6868
if (e.Index >= 0)
6969
{
70+
var darkMode = Application.IsDarkModeEnabled;
71+
var textBrush = darkMode ? Brushes.White : Brushes.Black;
72+
var borderPen = darkMode ? Pens.White : Pens.Black;
73+
7074
Rectangle rectangle = new(4, e.Bounds.Top + 2, 30, e.Bounds.Height - 4);
7175
var rectColor = (Color)Items[e.Index];
7276
e.Graphics.FillRectangle(new SolidBrush(rectColor), rectangle);
73-
e.Graphics.DrawRectangle(Pens.Black, rectangle);
77+
e.Graphics.DrawRectangle(borderPen, rectangle);
7478

7579
if (e.Index == 0)
7680
{
77-
e.Graphics.DrawString(Resources.ColorComboBox_UI_ColorComboBox_Text_Custom, e.Font, Brushes.Black, new PointF(42, e.Bounds.Top + 2));
81+
e.Graphics.DrawString(Resources.ColorComboBox_UI_ColorComboBox_Text_Custom, e.Font, textBrush, new PointF(42, e.Bounds.Top + 2));
7882
}
7983
else
8084
{
81-
e.Graphics.DrawString(((Color)Items[e.Index]).Name, e.Font, Brushes.Black, new PointF(42, e.Bounds.Top + 2));
85+
e.Graphics.DrawString(((Color)Items[e.Index]).Name, e.Font, textBrush, new PointF(42, e.Bounds.Top + 2));
8286
}
8387

8488
if (!Enabled)
8589
{
86-
HatchBrush brush = new(HatchStyle.Percent50, Color.LightGray, Color.FromArgb(10, Color.LightGray));
90+
var hatchLight = darkMode ? Color.DarkGray : Color.LightGray;
91+
HatchBrush brush = new(HatchStyle.Percent50, hatchLight, Color.FromArgb(10, hatchLight));
8792
rectangle.Inflate(1, 1);
8893
e.Graphics.FillRectangle(brush, rectangle);
8994
brush.Dispose();

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2741,6 +2741,13 @@ private void CreateDefaultViewStyle ()
27412741
filterGridView.RowsDefaultCellStyle = PaintHelper.GetDataGridDefaultRowStyle(darkMode);
27422742
PaintHelper.ApplyGridViewTheme(dataGridView, darkMode);
27432743
PaintHelper.ApplyGridViewTheme(filterGridView, darkMode);
2744+
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+
}
27442751
}
27452752

27462753
[SupportedOSPlatform("windows")]
@@ -6635,7 +6642,7 @@ public void CellPainting (bool focused, int rowIndex, int columnIndex, bool isFi
66356642
var fontName = isFilteredGridView ? FONT_VERDANA : FONT_COURIER_NEW;
66366643
var stringToDraw = isFilteredGridView ? "!" : "i";
66376644

6638-
using var brush2 = new SolidBrush(Color.FromArgb(255, 190, 100, 0)); //dark orange
6645+
using var brush2 = new SolidBrush(PaintHelper.BookmarkMarkerTextColor);
66396646
using var font = new Font(fontName, Preferences.Font.Size, FontStyle.Bold);
66406647
e.Graphics.DrawString(stringToDraw, font, brush2, new RectangleF(rect.Left, rect.Top, rect.Width, rect.Height), _format);
66416648
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Data;
33
using System.Globalization;
44
using System.Text.RegularExpressions;
5+
using System.Windows.Forms;
56

67
using ColumnizerLib;
78

@@ -404,7 +405,7 @@ private void UpdatePreview ()
404405
? Resources.HighlightEntryEditDialog_UI_Preview_Placeholder
405406
: text;
406407

407-
_previewLabel.ForeColor = GetSelectedColor(_colorBoxForeground, Color.Black);
408+
_previewLabel.ForeColor = GetSelectedColor(_colorBoxForeground, Application.IsDarkModeEnabled ? Color.White : Color.Black);
408409

409410
_previewLabel.BackColor = _checkBoxNoBackground != null && _checkBoxNoBackground.Checked
410411
? SystemColors.Window

‎src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.cs‎

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ public LogTabWindow (string[] fileNames, int instanceNumber, bool showInstanceNu
9393
InitializeComponent();
9494

9595
ConfigureDockPanel();
96+
ConfigureToolStripDark();
9697

9798
_tabController = new TabController(dockPanel);
9899
InitializeTabControllerEvents();
@@ -384,6 +385,38 @@ private void ConfigureDockPanel ()
384385
dockPanel.DocumentStyle = DocumentStyle.DockingWindow;
385386
}
386387

388+
/// <summary>
389+
/// Switches the main ToolStrip and MenuStrip family to a dark ProfessionalColorTable.
390+
/// WinForms ToolStrip does not honour Application.SetColorMode(Dark); the default
391+
/// ToolStripProfessionalRenderer keeps drawing light gradients regardless.
392+
/// Icons are re-coloured on-the-fly by DarkToolStripRenderer (see below).
393+
///
394+
/// ToolStripContainer.TopToolStripPanel forces RenderMode = System on every child
395+
/// ToolStrip, which overrides any per-toolstrip Renderer we set here. We bypass that
396+
/// by installing the renderer globally via ToolStripManager.Renderer and switching
397+
/// every ToolStrip / MenuStrip to ManagerRenderMode — then each parent panel stops
398+
/// fighting us.
399+
/// </summary>
400+
private void ConfigureToolStripDark ()
401+
{
402+
if (!Application.IsDarkModeEnabled)
403+
{
404+
return;
405+
}
406+
407+
var renderer = new DarkToolStripRenderer(new DarkToolStripColorTable());
408+
409+
// Global override — every ToolStrip that opts into ManagerRenderMode uses this.
410+
ToolStripManager.Renderer = renderer;
411+
ToolStripManager.VisualStylesEnabled = false;
412+
413+
buttonToolStrip.RenderMode = ToolStripRenderMode.ManagerRenderMode;
414+
externalToolsToolStrip.RenderMode = ToolStripRenderMode.ManagerRenderMode;
415+
mainMenuStrip.RenderMode = ToolStripRenderMode.ManagerRenderMode;
416+
statusStrip.RenderMode = ToolStripRenderMode.ManagerRenderMode;
417+
toolStripContainer.TopToolStripPanel.RenderMode = ToolStripRenderMode.ManagerRenderMode;
418+
}
419+
387420
private void ApplyTextResources ()
388421
{
389422
mainMenuStrip.Text = Resources.LogTabWindow_UI_MenuStrip_MainMenu;
@@ -2781,4 +2814,120 @@ private class LogWindowData
27812814

27822815
#endregion
27832816
}
2817+
2818+
/// <summary>
2819+
/// ToolStripProfessionalRenderer subclass that inverts icon colours at paint time.
2820+
/// All icons on the main ToolStrip family are light-mode PNGs with dark strokes —
2821+
/// painting them as-is on a dark ToolStrip background renders them invisible.
2822+
/// This renderer applies a ColorMatrix in OnRenderItemImage so glyphs go from
2823+
/// near-black → near-white during paint, regardless of how or when each item's
2824+
/// Image property was assigned.
2825+
/// </summary>
2826+
[SupportedOSPlatform("windows")]
2827+
private sealed class DarkToolStripRenderer : ToolStripProfessionalRenderer
2828+
{
2829+
// Invert RGB channels (black↔white) while preserving alpha.
2830+
private static readonly System.Drawing.Imaging.ColorMatrix _invertMatrix = new(
2831+
[
2832+
[ -1f, 0f, 0f, 0f, 0f ],
2833+
[ 0f, -1f, 0f, 0f, 0f ],
2834+
[ 0f, 0f, -1f, 0f, 0f ],
2835+
[ 0f, 0f, 0f, 1f, 0f ],
2836+
[ 1f, 1f, 1f, 0f, 1f ],
2837+
]);
2838+
2839+
private static readonly System.Drawing.Imaging.ImageAttributes _imageAttrs = new();
2840+
2841+
static DarkToolStripRenderer ()
2842+
{
2843+
_imageAttrs.SetColorMatrix(_invertMatrix);
2844+
}
2845+
2846+
public DarkToolStripRenderer (ProfessionalColorTable colorTable) : base(colorTable)
2847+
{
2848+
}
2849+
2850+
protected override void OnRenderItemImage (ToolStripItemImageRenderEventArgs e)
2851+
{
2852+
if (e.Image == null)
2853+
{
2854+
return;
2855+
}
2856+
2857+
// Original icons are light-mode PNGs with dark strokes on transparent background.
2858+
// Invert RGB channels so glyphs become light-on-transparent against the dark bar,
2859+
// while preserving alpha (so transparency stays clean — no opaque white halo).
2860+
e.Graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
2861+
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
2862+
e.Graphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
2863+
e.Graphics.DrawImage(e.Image, e.ImageRectangle, 0, 0, e.Image.Width, e.Image.Height, GraphicsUnit.Pixel, _imageAttrs);
2864+
}
2865+
2866+
// Force text to use a light foreground so it stays readable on the dark background.
2867+
protected override void OnRenderItemText (ToolStripItemTextRenderEventArgs e)
2868+
{
2869+
var tc = e.TextColor;
2870+
if (tc.GetBrightness() <= 0.5f)
2871+
{
2872+
tc = e.Item.Enabled ? Color.FromArgb(220, 220, 220) : Color.FromArgb(140, 140, 140);
2873+
}
2874+
2875+
var rect = e.TextRectangle;
2876+
TextRenderer.DrawText(e.Graphics, e.Text, e.TextFont, rect, tc, e.TextFormat);
2877+
}
2878+
}
2879+
2880+
/// <summary>
2881+
/// Minimal dark palette for ToolStripProfessionalRenderer. WinForms does not switch the
2882+
/// default ProfessionalColorTable to dark under Application.SetColorMode(Dark), so we
2883+
/// override the essential surface/accent colors here.
2884+
/// </summary>
2885+
private sealed class DarkToolStripColorTable : ProfessionalColorTable
2886+
{
2887+
private static readonly Color _bg = Color.FromArgb(37, 37, 38); // Visual Studio dark
2888+
private static readonly Color _border = Color.FromArgb(70, 70, 70);
2889+
private static readonly Color _hover = Color.FromArgb(62, 62, 66);
2890+
private static readonly Color _press = Color.FromArgb(0, 122, 204); // VS blue accent
2891+
private static readonly Color _menuBorder = Color.FromArgb(100, 100, 100);
2892+
private static readonly Color _sep = Color.FromArgb(80, 80, 80);
2893+
2894+
public override Color ToolStripPanelGradientBegin => _bg;
2895+
public override Color ToolStripPanelGradientEnd => _bg;
2896+
public override Color ToolStripBorder => _border;
2897+
public override Color ToolStripDropDownBackground => _bg;
2898+
2899+
public override Color MenuItemSelected => _hover;
2900+
public override Color MenuItemSelectedGradientBegin => _hover;
2901+
public override Color MenuItemSelectedGradientEnd => _hover;
2902+
public override Color MenuItemPressedGradientBegin => _press;
2903+
public override Color MenuItemPressedGradientEnd => _press;
2904+
public override Color MenuBorder => _menuBorder;
2905+
public override Color MenuItemBorder => _menuBorder;
2906+
2907+
public override Color SeparatorDark => _sep;
2908+
public override Color SeparatorLight => _border;
2909+
2910+
public override Color ImageMarginGradientBegin => _bg;
2911+
public override Color ImageMarginGradientMiddle => _bg;
2912+
public override Color ImageMarginGradientEnd => _bg;
2913+
public override Color ImageMarginRevealedGradientBegin => _hover;
2914+
public override Color ImageMarginRevealedGradientMiddle => _hover;
2915+
public override Color ImageMarginRevealedGradientEnd => _hover;
2916+
2917+
public override Color ButtonSelectedBorder => _border;
2918+
public override Color ButtonSelectedGradientBegin => _hover;
2919+
public override Color ButtonSelectedGradientMiddle => _hover;
2920+
public override Color ButtonSelectedGradientEnd => _hover;
2921+
public override Color ButtonPressedBorder => _press;
2922+
public override Color ButtonPressedGradientBegin => _press;
2923+
public override Color ButtonPressedGradientMiddle => _press;
2924+
public override Color ButtonPressedGradientEnd => _press;
2925+
2926+
public override Color GripDark => _sep;
2927+
public override Color GripLight => _border;
2928+
2929+
public override Color CheckBackground => _bg;
2930+
public override Color CheckSelectedBackground => _hover;
2931+
public override Color CheckPressedBackground => _press;
2932+
}
27842933
}

‎src/LogExpert.UI/Entities/PaintHelper.cs‎

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ public static void CellPainting (ILogPaintContextUI logPaintCtx, bool focused, i
7575

7676
if (bookmark.Text.Length > 0)
7777
{
78-
using var brush2 = new SolidBrush(Color.FromArgb(255, 190, 100, 0)); //DarkOrange
78+
using var brush2 = new SolidBrush(BookmarkMarkerTextColor);
7979
using var font = logPaintCtx.MonospacedFont;
8080
e.Graphics.DrawString("i", font, brush2, new RectangleF(r.Left, r.Top, r.Width, r.Height), _format);
8181
}
@@ -90,6 +90,12 @@ public static void CellPainting (ILogPaintContextUI logPaintCtx, bool focused, i
9090
// Matches what SystemColors.Window resolves to under Application.SetColorMode(Dark).
9191
private static readonly Color _darkModeWindowBackColor = Color.FromArgb(32, 32, 32);
9292

93+
/// <summary>
94+
/// Default foreground color on dark surfaces — a soft off-white that keeps contrast high
95+
/// (≈12:1 against the dark window background) but avoids the harsh glare of pure #FFFFFF.
96+
/// </summary>
97+
private static readonly Color _darkModeForeColor = Color.FromArgb(220, 220, 220);
98+
9399
public static Color GetBackColorFromHighlightEntry (HighlightEntry? entry, bool darkMode)
94100
{
95101
return entry?.BackgroundColor ?? (darkMode ? _darkModeWindowBackColor : Color.White);
@@ -102,9 +108,18 @@ public static Color GetForeColorFromHighlightEntry (HighlightEntry? entry, bool
102108
return entry.ForegroundColor;
103109
}
104110

105-
return darkMode ? Color.White : Color.Black;
111+
return darkMode ? _darkModeForeColor : Color.Black;
106112
}
107113

114+
/// <summary>
115+
/// Color of the "i" / "!" marker text drawn on bookmark / filter squares in column 0.
116+
/// Dark mode uses a brighter orange so the marker stays visible against the dark background.
117+
/// </summary>
118+
public static Color BookmarkMarkerTextColor =>
119+
Application.IsDarkModeEnabled
120+
? Color.FromArgb(255, 220, 130, 20) // brighter orange on dark
121+
: Color.FromArgb(255, 190, 100, 0); // DarkOrange baseline
122+
108123
[SupportedOSPlatform("windows")]
109124
public static Brush GetBrushForFocusedControl (bool focused, Color selectionColor, bool darkMode)
110125
{
@@ -250,7 +265,7 @@ public static DataGridViewCellStyle GetDataGridViewCellStyle (bool darkMode)
250265
Alignment = DataGridViewContentAlignment.MiddleLeft,
251266
BackColor = SystemColors.Window,
252267
Font = new Font("Courier New", 8.25F, FontStyle.Regular, GraphicsUnit.Point, 0),
253-
ForeColor = darkMode ? Color.White : Color.Black,
268+
ForeColor = darkMode ? _darkModeForeColor : Color.Black,
254269
SelectionBackColor = SystemColors.Highlight,
255270
SelectionForeColor = GetForeColorBasedOnBackColor(SystemColors.Highlight),
256271
WrapMode = DataGridViewTriState.False
@@ -265,7 +280,7 @@ public static DataGridViewCellStyle GetDataGridDefaultRowStyle (bool darkMode)
265280
{
266281
Alignment = DataGridViewContentAlignment.MiddleLeft,
267282
BackColor = SystemColors.Window,
268-
ForeColor = darkMode ? Color.White : Color.Black,
283+
ForeColor = darkMode ? _darkModeForeColor : Color.Black,
269284
SelectionBackColor = SystemColors.Highlight,
270285
SelectionForeColor = GetForeColorBasedOnBackColor(SystemColors.Highlight),
271286
WrapMode = DataGridViewTriState.False

‎src/LogExpert.UI/Services/LedService/LedIndicatorService.cs‎

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Drawing.Imaging;
33
using System.Globalization;
44
using System.Runtime.Versioning;
5+
using System.Windows.Forms;
56

67
using LogExpert.UI.Controls.LogWindow;
78
using LogExpert.UI.Interface;
@@ -254,6 +255,9 @@ public void Initialize (Color tailColor)
254255
/// </summary>
255256
private void CreateBrushes (Color tailColor)
256257
{
258+
var darkMode = Application.IsDarkModeEnabled;
259+
260+
// Signal-color LEDs (red/orange/yellow/green) are theme-independent — they carry semantic meaning.
257261
_ledBrushes = new Brush[5];
258262
_ledBrushes[0] = new SolidBrush(Color.FromArgb(255, 0, 0)); // Red (highest activity)
259263
_ledBrushes[1] = new SolidBrush(Color.FromArgb(255, 100, 0)); // Orange
@@ -263,12 +267,13 @@ private void CreateBrushes (Color tailColor)
263267

264268
_tailLedBrush = new Brush[3];
265269
_tailLedBrush[0] = new SolidBrush(tailColor); // Tail on
266-
_tailLedBrush[1] = new SolidBrush(Color.FromArgb(160, 160, 160)); // Tail off
270+
_tailLedBrush[1] = new SolidBrush(darkMode ? Color.FromArgb(175, 175, 175) : Color.FromArgb(160, 160, 160)); // Tail off
267271
_tailLedBrush[2] = new SolidBrush(Color.FromArgb(220, 220, 0)); // Tail trigger
268272

269-
_offLedBrush = new SolidBrush(Color.FromArgb(100, 100, 100));
270-
_dirtyLedBrush = new SolidBrush(Color.FromArgb(220, 0, 220)); // Magenta for dirty
271-
_syncLedBrush = new SolidBrush(Color.FromArgb(0, 100, 220)); // Blue for synced
273+
// Off / neutral LEDs must be visible against the surrounding UI background.
274+
_offLedBrush = new SolidBrush(darkMode ? Color.FromArgb(170, 170, 170) : Color.FromArgb(100, 100, 100));
275+
_dirtyLedBrush = new SolidBrush(Color.FromArgb(220, 0, 220)); // Magenta for dirty — semantic color, theme-independent
276+
_syncLedBrush = new SolidBrush(Color.FromArgb(0, 100, 220)); // Blue for synced — semantic color, theme-independent
272277
}
273278

274279
/// <summary>

0 commit comments

Comments
 (0)