diff --git a/HellionChat/PluginHostFactory.cs b/HellionChat/PluginHostFactory.cs index f2ed0fd..83fba5f 100644 --- a/HellionChat/PluginHostFactory.cs +++ b/HellionChat/PluginHostFactory.cs @@ -162,7 +162,10 @@ internal static class PluginHostFactory services.AddSingleton(sp => new Ui.Components.Settings.TabSidebar( sp.GetRequiredService() )); - services.AddSingleton(sp => new Ui.Components.Settings.ContentArea()); + services.AddSingleton(sp => new Ui.Components.Settings.ContentArea( + sp.GetRequiredService(), + sp.GetRequiredService() + )); services.AddSingleton(sp => new Ui.Components.Settings.ThemePicker( sp.GetRequiredService(), sp.GetRequiredService() diff --git a/HellionChat/Ui/Components/Settings/ContentArea.cs b/HellionChat/Ui/Components/Settings/ContentArea.cs index 1e7da68..4021c0d 100644 --- a/HellionChat/Ui/Components/Settings/ContentArea.cs +++ b/HellionChat/Ui/Components/Settings/ContentArea.cs @@ -1,12 +1,39 @@ using System.Numerics; +using Dalamud.Bindings.ImGui; using Dalamud.Interface.Utility.Raii; +using HellionChat.Themes; +using HellionChat.Ui.StyleEngine; +using HellionChat.Util; namespace HellionChat.Ui.Components.Settings; internal sealed class ContentArea { + private readonly ThemeRegistry _themes; + private readonly TokenResolver _resolver; + + public ContentArea(ThemeRegistry themes, TokenResolver resolver) + { + _themes = themes; + _resolver = resolver; + } + public void Draw(string activeTab, Action renderTab) { + // ChildBg was never set, so the pane inherited whatever showed through + // the window -- and the window is translucent by default. Settings text + // ended up sitting on moving scenery, which is legible in a screenshot + // and not while the game is running. + // + // Pushed as a colour rather than painted into the draw list so ImGui + // fills it before the scrollbar and border, and the window's own opacity + // still applies on top. + var colors = _themes.Active.Colors; + using var bg = ImRaii.PushColor( + ImGuiCol.ChildBg, + ColourUtil.RgbaToVector4(_resolver.Resolve(Token.SurfaceBase, colors)) + ); + using var child = ImRaii.Child("##settings-content", new Vector2(0, 0), true); if (!child.Success) { diff --git a/HellionChat/Ui/Components/Settings/SettingsPalette.cs b/HellionChat/Ui/Components/Settings/SettingsPalette.cs index d4db594..b2eee56 100644 --- a/HellionChat/Ui/Components/Settings/SettingsPalette.cs +++ b/HellionChat/Ui/Components/Settings/SettingsPalette.cs @@ -23,7 +23,9 @@ internal sealed class SettingsPalette new() { LabelAbgr = _palette.Abgr(Token.Text, c), - DescriptionAbgr = _palette.Abgr(Token.TextMuted, c), + // A step below the label, not level with it: at TextMuted the two + // lines carried the same weight and the row read as two settings. + DescriptionAbgr = _palette.Abgr(Token.TextFaint, c), SurfaceHoverAbgr = _palette.Abgr(Token.SurfaceHover, c), BorderAbgr = _palette.Abgr(Token.Border, c), }; @@ -34,6 +36,7 @@ internal sealed class SettingsPalette TrackOffAbgr = _palette.Abgr(Token.SurfaceBase, c), TrackOnAbgr = _palette.Abgr(Token.AccentPrimary, c), KnobAbgr = _palette.Abgr(Token.Text, c), + BorderAbgr = _palette.Abgr(Token.Border, c), }; internal SectionHeaderColors Section(ThemeColors c) => @@ -44,6 +47,7 @@ internal sealed class SettingsPalette AccentAbgr = _palette.Abgr(Token.AccentPrimary, c), BorderAbgr = _palette.Abgr(Token.Border, c), HoverAbgr = _palette.Abgr(Token.SurfaceHover, c), + SurfaceAbgr = _palette.Abgr(Token.SurfaceRaised, c), }; internal SegmentedControlColors Segmented(ThemeColors c) => diff --git a/HellionChat/Ui/StyleEngine/Widgets/SectionHeader.cs b/HellionChat/Ui/StyleEngine/Widgets/SectionHeader.cs index 78c1ffd..1189b8b 100644 --- a/HellionChat/Ui/StyleEngine/Widgets/SectionHeader.cs +++ b/HellionChat/Ui/StyleEngine/Widgets/SectionHeader.cs @@ -13,6 +13,10 @@ internal readonly record struct SectionHeaderColors public uint AccentAbgr { get; init; } public uint BorderAbgr { get; init; } public uint HoverAbgr { get; init; } + + // Own ground. Without it the heading floats between the rows at roughly + // their own weight and stops separating anything. + public uint SurfaceAbgr { get; init; } } internal readonly record struct SectionHeaderStyle @@ -20,7 +24,7 @@ internal readonly record struct SectionHeaderStyle public SectionHeaderStyle() { } public float PadY { get; init; } = 6f; - public float AccentBarWidth { get; init; } = 2f; + public float AccentBarWidth { get; init; } = 3f; public float ChevronInset { get; init; } = 6f; } @@ -88,6 +92,8 @@ internal static class SectionHeader var dl = ImGui.GetWindowDrawList(); var max = origin + size; + dl.AddRectFilled(origin, max, ColourUtil.ApplyAlpha(colors.SurfaceAbgr, alpha)); + if (hoverAmount > 0f) dl.AddRectFilled( origin, @@ -95,10 +101,12 @@ internal static class SectionHeader ColourUtil.ApplyAlpha(colors.HoverAbgr, hoverAmount * alpha) ); + // Full height, not just the title line. Stopping at the title left the + // bar looking like a stray tick next to a two-line heading. var barWidth = style.AccentBarWidth * scale; dl.AddRectFilled( origin, - new Vector2(origin.X + barWidth, origin.Y + titleHeight + padY * 2f), + new Vector2(origin.X + barWidth, max.Y), ColourUtil.ApplyAlpha(colors.AccentAbgr, alpha) ); diff --git a/HellionChat/Ui/StyleEngine/Widgets/SegmentedControl.cs b/HellionChat/Ui/StyleEngine/Widgets/SegmentedControl.cs index 4fc5b63..acbd32f 100644 --- a/HellionChat/Ui/StyleEngine/Widgets/SegmentedControl.cs +++ b/HellionChat/Ui/StyleEngine/Widgets/SegmentedControl.cs @@ -117,7 +117,15 @@ internal static class SegmentedControl MetricsMath.Center(height, textSize.Y) ), ColourUtil.ApplyAlpha( - isSelected ? colors.SelectedLabelAbgr : colors.LabelAbgr, + isSelected + // On the accent fill, so the readable colour depends on + // how light that accent is in the active theme. + ? ColourUtil.OnColour( + colors.SelectedAbgr, + colors.SelectedLabelAbgr, + colors.TrackAbgr + ) + : colors.LabelAbgr, alpha ), label diff --git a/HellionChat/Ui/StyleEngine/Widgets/ToggleSwitch.cs b/HellionChat/Ui/StyleEngine/Widgets/ToggleSwitch.cs index 5575771..3d00ff2 100644 --- a/HellionChat/Ui/StyleEngine/Widgets/ToggleSwitch.cs +++ b/HellionChat/Ui/StyleEngine/Widgets/ToggleSwitch.cs @@ -11,6 +11,11 @@ internal readonly record struct ToggleSwitchColors public uint TrackOffAbgr { get; init; } public uint TrackOnAbgr { get; init; } public uint KnobAbgr { get; init; } + + // The off track needs an outline of its own. Filled with a surface tone it + // is the same colour as the row behind it, so an off switch read as empty + // space rather than as a control. + public uint BorderAbgr { get; init; } } internal readonly record struct ToggleSwitchStyle @@ -66,10 +71,25 @@ internal static class ToggleSwitch var track = ColourUtil.Lerp(colors.TrackOffAbgr, colors.TrackOnAbgr, amount); dl.AddRectFilled(origin, max, track, size.Y * 0.5f); + + // Fades out as the switch turns on, where the filled track carries the + // shape by itself. + if (amount < 1f) + dl.AddRect( + origin, + max, + ColourUtil.ApplyAlpha(colors.BorderAbgr, 1f - amount), + size.Y * 0.5f, + ImDrawFlags.None, + Metrics.Scale + ); + + // The knob picks its contrast from the track it sits on, so it stays + // visible on a pale accent and on a near-black surface alike. dl.AddCircleFilled( new Vector2(origin.X + knobX, origin.Y + size.Y * 0.5f), knobR, - colors.KnobAbgr, + ColourUtil.OnColour(track, colors.KnobAbgr, colors.TrackOffAbgr), 16 ); } diff --git a/HellionChat/Ui/Windows/WidgetGalleryWindow.cs b/HellionChat/Ui/Windows/WidgetGalleryWindow.cs index 35ad0f0..3d51571 100644 --- a/HellionChat/Ui/Windows/WidgetGalleryWindow.cs +++ b/HellionChat/Ui/Windows/WidgetGalleryWindow.cs @@ -160,6 +160,7 @@ internal sealed class WidgetGalleryWindow : Window TrackOffAbgr = _palette.Abgr(Token.SurfaceRaised, c), TrackOnAbgr = _palette.Abgr(Token.AccentPrimary, c), KnobAbgr = _palette.Abgr(Token.Text, c), + BorderAbgr = _palette.Abgr(Token.Border, c), }; var size = ToggleSwitch.CalcSize(); diff --git a/HellionChat/Util/ColourUtil.cs b/HellionChat/Util/ColourUtil.cs index 9dee87d..462ac5e 100755 --- a/HellionChat/Util/ColourUtil.cs +++ b/HellionChat/Util/ColourUtil.cs @@ -108,6 +108,23 @@ internal static class ColourUtil // going fully saturated (effect level stays "subtle"). RGB-only on // purpose -- DrawHoverSheen owns the alpha falloff. // TEST-MIRROR: ../../../Hellion Build test/Util/ColourUtilTintTests.cs + // Relative luminance of an ABGR colour, 0..1, using the sRGB coefficients. + // Alpha is ignored: this answers "is this surface light or dark", and a + // translucent light surface still reads light against the window behind it. + internal static float Luminance(uint abgr) + { + var r = (abgr & 0xFFu) / 255f; + var g = ((abgr >> 8) & 0xFFu) / 255f; + var b = ((abgr >> 16) & 0xFFu) / 255f; + return 0.2126f * r + 0.7152f * g + 0.0722f * b; + } + + // Picks whichever of two candidates stands further from the background. + // Themes range from near-black to pastel, so a fixed text colour on an accent + // fill is legible in some and invisible in others. + internal static uint OnColour(uint background, uint light, uint dark) => + Luminance(background) > 0.5f ? dark : light; + internal static uint LerpTowardWhite(uint abgr, float t) { t = Math.Clamp(t, 0f, 1f);