diff --git a/HellionChat/Ui/Components/Sidebar.cs b/HellionChat/Ui/Components/Sidebar.cs index 64bc5cc..4adad40 100644 --- a/HellionChat/Ui/Components/Sidebar.cs +++ b/HellionChat/Ui/Components/Sidebar.cs @@ -317,6 +317,13 @@ internal sealed class Sidebar ), AccentAbgr = _palette.Abgr(Token.AccentPrimary, colors), BorderAbgr = ColourUtil.ApplyAlpha(_palette.Abgr(Token.Border, colors), opacity), + }, + new RowStyle + { + // Across the gap between the sidebar group and the message + // area, so the selected tab touches the conversation it + // selects instead of ending at the seam. + ActiveBridgeWidth = ImGui.GetStyle().ItemSpacing.X, } ); diff --git a/HellionChat/Ui/StyleEngine/Widgets/Row.cs b/HellionChat/Ui/StyleEngine/Widgets/Row.cs index f80cd93..c2dba86 100644 --- a/HellionChat/Ui/StyleEngine/Widgets/Row.cs +++ b/HellionChat/Ui/StyleEngine/Widgets/Row.cs @@ -25,6 +25,12 @@ internal readonly record struct RowStyle public float AccentBarWidth { get; init; } = 2f; public bool DrawSeparator { get; init; } = true; + + // Extends the active fill past the right edge by this many pixels, so the + // selected tab reads as attached to the content it selects -- the classic + // tab metaphor -- instead of ending at a hard seam. Zero for rows that do + // not sit next to the thing they select. + public float ActiveBridgeWidth { get; init; } = 0f; } internal static class Row @@ -51,13 +57,19 @@ internal static class Row // default is 0.85 -- an opaque fill per row would make the sidebar a // solid block inside a translucent window. if (state.IsActive) - dl.AddRectFilled(origin, max, state.SurfaceActiveAbgr); + { + var fillMax = max; + if (style.ActiveBridgeWidth > 0f) + fillMax.X += style.ActiveBridgeWidth; + dl.AddRectFilled(origin, fillMax, state.SurfaceActiveAbgr); + } if (state.HoverAmount > 0f) dl.AddRectFilled( origin, max, - ColourUtil.ApplyAlpha(state.SurfaceHoverAbgr, state.HoverAmount) + ColourUtil.ApplyAlpha(state.SurfaceHoverAbgr, state.HoverAmount), + 3f * Metrics.Scale ); if (state.IsActive && style.AccentBarWidth > 0f) @@ -72,12 +84,17 @@ internal static class Row // path, so an unscaled 1px offset with a scaled stroke puts half the // line below max.Y -- and rows stack flush, so that half lands in the // first pixel row of the next one. + // A full-width line under every row reads as a ladder of hard + // cuts -- the tester's words were that the list felt too hard. + // The rule now starts past the icon column and fades to nothing + // before the right edge, the same shape the section headers use. var thickness = Metrics.Scale; var y = max.Y - thickness * 0.5f; - dl.AddLine( - new Vector2(origin.X, y), - new Vector2(max.X, y), - state.BorderAbgr, + var inset = 10f * Metrics.Scale; + dl.DrawFadeRule( + new Vector2(origin.X + inset, y), + size.X - inset, + ColourUtil.ApplyAlpha(state.BorderAbgr, 0.55f), thickness ); } diff --git a/HellionChat/Ui/Windows/MainWindow.cs b/HellionChat/Ui/Windows/MainWindow.cs index b496c3e..bd07ea3 100644 --- a/HellionChat/Ui/Windows/MainWindow.cs +++ b/HellionChat/Ui/Windows/MainWindow.cs @@ -367,6 +367,25 @@ internal sealed class MainWindow : Window, IFocusableChatWindow _sidebar.Draw(bodyWidth, tabs, ref _activeTab); } + // The gap between the tab list and the conversation used to be a bare + // strip of window background with a hard edge on both sides -- tester + // feedback called the transition too hard. A faint surface wash that + // fades toward the messages turns the cut into a seam. The active row + // bridges across it (RowStyle.ActiveBridgeWidth), so the selected tab + // stays attached to its content on top of the wash. + { + var seamMin = new Vector2(ImGui.GetItemRectMax().X, ImGui.GetItemRectMin().Y); + var seamMax = new Vector2( + seamMin.X + ImGui.GetStyle().ItemSpacing.X, + ImGui.GetItemRectMax().Y + ); + var wash = ColourUtil.ApplyAlpha( + ColourUtil.RgbaToAbgr(Plugin.Instance.ThemeRegistry.Active.Colors.Surface), + 0.35f + ); + ImGui.GetWindowDrawList().AddRectFilledMultiColor(seamMin, seamMax, wash, 0u, 0u, wash); + } + ImGui.SameLine(); using (ImRaii.Group())