feat(sidebar): soften the seam and let the active tab reach its content
Tester feedback, relayed by Flo: the transition from the tab list to the chat field is too hard, and the tabs could present themselves better. Three causes, three changes, no structural touch -- tabs stay tabs, per the standing decision. The full-width border line under every row was a ladder of hard cuts. It is a fading rule now, starting past the icon column and dissolving before the right edge -- the same shape the section headers have used since v1.11.0, at about half the opacity. The gap between the sidebar group and the message area was a bare strip of window background with a hard edge on both sides. A faint surface wash fades across it toward the messages, turning the cut into a seam. And the selected tab bridges that gap: its active fill extends across the spacing so it touches the conversation it selects -- the classic tab metaphor, attached instead of adjacent. The bridge is a RowStyle knob (default zero), so sidebar rows opt in and nothing else inherits it. Hover fills picked up the standard three-pixel rounding on the way.
This commit is contained in:
@@ -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,
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
|
||||
Reference in New Issue
Block a user