From ac67480defb85c7b7b23c6cffc8e0b1d685debe7 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Nov 2026 07:30:31 +0000 Subject: [PATCH] Give the selected tab its own fill on the tab bar [patch] ImGui fills a dock node's tab bar with TitleBgActive or TitleBg, and TabSelected and TabDimmedSelected resolved to those same colors, so the selected tab had no fill of its own while unselected tabs stood out. The selected tabs now sit one priority step above their bar, and TabHovered moves up a step so hovering still reads differently from being selected. Fixes ktsu-dev/ThemeProvider#161 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_017vfJW5kPDQWGc94xTwvzds --- ThemeProvider.ImGui/ImGuiPaletteMapper.cs | 8 ++++--- ThemeProvider.Test/ImGuiPaletteMapperTests.cs | 24 +++++++++++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/ThemeProvider.ImGui/ImGuiPaletteMapper.cs b/ThemeProvider.ImGui/ImGuiPaletteMapper.cs index 3195d36..3d86877 100644 --- a/ThemeProvider.ImGui/ImGuiPaletteMapper.cs +++ b/ThemeProvider.ImGui/ImGuiPaletteMapper.cs @@ -87,12 +87,14 @@ public IReadOnlyDictionary MapTheme(ISemanticTheme theme) { ImGuiCol.TreeLines, new(SemanticMeaning.Neutral, Priority.MediumHigh)}, { ImGuiCol.Tab, new(SemanticMeaning.Neutral, Priority.Low)}, - { ImGuiCol.TabSelected, new(SemanticMeaning.Primary, Priority.VeryLow)}, - { ImGuiCol.TabHovered, new(SemanticMeaning.Primary, Priority.Low)}, + // The tab bar is filled with the title bar's color, so the selected tab sits one step above it + // to read as the open tab, and hovering steps up once more. + { ImGuiCol.TabSelected, new(SemanticMeaning.Primary, Priority.Low)}, + { ImGuiCol.TabHovered, new(SemanticMeaning.Primary, Priority.MediumLow)}, { ImGuiCol.TabSelectedOverline, new(SemanticMeaning.Primary, Priority.High)}, // Tabs in an unfocused window drop to neutral so the focused window's tabs stand out. { ImGuiCol.TabDimmed, new(SemanticMeaning.Neutral, Priority.VeryLow)}, - { ImGuiCol.TabDimmedSelected, new(SemanticMeaning.Neutral, Priority.Low)}, + { ImGuiCol.TabDimmedSelected, new(SemanticMeaning.Neutral, Priority.MediumLow)}, { ImGuiCol.TabDimmedSelectedOverline, new(SemanticMeaning.Primary, Priority.High)}, // Alternate elements spread across the full 20-82% non-neutral range for better contrast diff --git a/ThemeProvider.Test/ImGuiPaletteMapperTests.cs b/ThemeProvider.Test/ImGuiPaletteMapperTests.cs index cff6d36..9b51bea 100644 --- a/ThemeProvider.Test/ImGuiPaletteMapperTests.cs +++ b/ThemeProvider.Test/ImGuiPaletteMapperTests.cs @@ -211,6 +211,30 @@ public void MapTheme_TextContrastsWithWindowBackground() } } + /// + /// ImGui fills a dock node's tab bar with TitleBgActive when focused and TitleBg otherwise, and draws + /// the selected tab with TabSelected or TabDimmedSelected. Each selected tab must differ from the bar + /// it sits on, or only the 1px overline shows which tab is open. Hovering must also read differently + /// from being selected. + /// + [TestMethod] + public void MapTheme_SelectedTabsDifferFromTheirTabBar() + { + ImGuiPaletteMapper mapper = new(); + + foreach (ThemeRegistry.ThemeInfo info in ThemeRegistry.AllThemes) + { + IReadOnlyDictionary palette = mapper.MapTheme(info.CreateInstance()); + + AssertDistinct(palette, ImGuiCol.TabSelected, ImGuiCol.TitleBgActive, info.Name); + AssertDistinct(palette, ImGuiCol.TabDimmedSelected, ImGuiCol.TitleBg, info.Name); + AssertDistinct(palette, ImGuiCol.TabHovered, ImGuiCol.TabSelected, info.Name); + } + } + + private static void AssertDistinct(IReadOnlyDictionary palette, ImGuiCol first, ImGuiCol second, string themeName) => + Assert.IsGreaterThan(1f / 255f, Vector4.Distance(palette[first], palette[second]), $"{themeName}: {first} is drawn in the {second} color"); + private sealed class NoNeutralTheme : ISemanticTheme { public Dictionary> SemanticMapping { get; } = new()