diff --git a/HellionChat/Ui/StyleEngine/Widgets/Row.cs b/HellionChat/Ui/StyleEngine/Widgets/Row.cs index 42cebde..f80cd93 100644 --- a/HellionChat/Ui/StyleEngine/Widgets/Row.cs +++ b/HellionChat/Ui/StyleEngine/Widgets/Row.cs @@ -67,11 +67,19 @@ internal static class Row } if (style.DrawSeparator) + { + // Offset scales with the thickness. ImGui strokes centred on the + // 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. + var thickness = Metrics.Scale; + var y = max.Y - thickness * 0.5f; dl.AddLine( - new Vector2(origin.X, max.Y - 1f), - new Vector2(max.X, max.Y - 1f), + new Vector2(origin.X, y), + new Vector2(max.X, y), state.BorderAbgr, - Metrics.Scale + thickness ); + } } } diff --git a/HellionChat/Ui/StyleEngine/Widgets/SectionHeader.cs b/HellionChat/Ui/StyleEngine/Widgets/SectionHeader.cs index f456543..78c1ffd 100644 --- a/HellionChat/Ui/StyleEngine/Widgets/SectionHeader.cs +++ b/HellionChat/Ui/StyleEngine/Widgets/SectionHeader.cs @@ -35,8 +35,6 @@ internal static class SectionHeader { private static readonly Dictionary Open = []; - internal static void Reset() => Open.Clear(); - internal static bool Draw( uint key, string title, @@ -105,11 +103,13 @@ internal static class SectionHeader ); var textX = origin.X + leadWidth; + dl.PushClipRect(origin, max, true); dl.AddText( new Vector2(textX, origin.Y + padY), ColourUtil.ApplyAlpha(colors.TitleAbgr, alpha), title ); + dl.PopClipRect(); DrawChevron( dl, diff --git a/HellionChat/Ui/StyleEngine/Widgets/SegmentedControl.cs b/HellionChat/Ui/StyleEngine/Widgets/SegmentedControl.cs index 1735b62..4fc5b63 100644 --- a/HellionChat/Ui/StyleEngine/Widgets/SegmentedControl.cs +++ b/HellionChat/Ui/StyleEngine/Widgets/SegmentedControl.cs @@ -38,9 +38,13 @@ internal static class SegmentedControl private static uint AnimKey(uint id, int index) => (id ^ (uint)(index + 1) * 0x85EBCA6Bu) * 2654435761u + 0x9E3779B9u; + // Reads the cursor rather than taking a position, like SettingRow and + // SectionHeader. It cannot avoid reserving space -- every segment submits an + // InvisibleButton, and those always advance -- so taking an origin would let + // a caller place it somewhere the reservation does not match. + // To right-align it inside a setting row, set the cursor before calling. internal static int Draw( uint id, - Vector2 origin, float width, ReadOnlySpan labels, int selected, @@ -52,6 +56,7 @@ internal static class SegmentedControl if (labels.Length == 0) return selected; + var origin = ImGui.GetCursorScreenPos(); var style = styleOverride ?? new SegmentedControlStyle(); var scale = Metrics.Scale; var rounding = style.Rounding * scale; @@ -128,11 +133,12 @@ internal static class SegmentedControl ); // Explicit, rather than inheriting whatever the last InvisibleButton left - // behind. The loop re-pins the cursor before every segment, so the run - // happens to end in the right place today -- but that is a side effect, - // and a caller cannot tell from here whether it still needs to reserve - // the row itself. Same contract as SettingRow and SectionHeader now: - // the widget owns its own advance. + // behind: the loop re-pins the cursor before every segment, so the run + // only ends in the right place as a side effect. + // + // The full height, unlike SettingRow and SectionHeader, which subtract + // ItemSpacing.Y. Those two stack flush on purpose because they are list + // entries. This is a single control and takes the normal gap. ImGui.SetCursorScreenPos(origin); ImGuiP.ItemSize(new Vector2(width, height)); diff --git a/HellionChat/Ui/StyleEngine/Widgets/SettingRow.cs b/HellionChat/Ui/StyleEngine/Widgets/SettingRow.cs index 71cdf75..6f4d1b3 100644 --- a/HellionChat/Ui/StyleEngine/Widgets/SettingRow.cs +++ b/HellionChat/Ui/StyleEngine/Widgets/SettingRow.cs @@ -124,8 +124,15 @@ internal static class SettingRow // control. The description wraps instead of being cut. var dl = ImGui.GetWindowDrawList(); dl.PushClipRect(origin, new Vector2(origin.X + labelWidth, origin.Y + size.Y), true); + // Without a description the label is alone next to the control, so it + // centres against the control band. A slider draws its text at + // FramePadding.Y, so a top-aligned label sits visibly high next to it. + // With a description the pair is top-aligned and this must not apply. + var labelY = description is null + ? origin.Y + padY + MetricsMath.Center(lineHeight, ImGui.GetTextLineHeight()) + : origin.Y + padY; dl.AddText( - new Vector2(origin.X, origin.Y + padY), + new Vector2(origin.X, labelY), ColourUtil.ApplyAlpha(colors.LabelAbgr, alpha), label ); diff --git a/HellionChat/Ui/Windows/WidgetGalleryWindow.cs b/HellionChat/Ui/Windows/WidgetGalleryWindow.cs index 7695600..35ad0f0 100644 --- a/HellionChat/Ui/Windows/WidgetGalleryWindow.cs +++ b/HellionChat/Ui/Windows/WidgetGalleryWindow.cs @@ -282,6 +282,16 @@ internal sealed class WidgetGalleryWindow : Window private int _segmentTwo; private int _segmentThree = 1; + // Held, not rebuilt per frame: a collection expression inside Draw + // allocates a fresh array on every frame the window is open. + private readonly int[] _badgeSamples = [1, 9, 0, 120]; + private static readonly FontAwesomeIcon[] GalleryGlyphs = + [ + FontAwesomeIcon.ArrowUpRightFromSquare, + FontAwesomeIcon.Check, + FontAwesomeIcon.CheckCircle, + ]; + private void DrawSegmentedSection(ThemeColors c) { var colors = new SegmentedControlColors @@ -298,7 +308,6 @@ internal sealed class WidgetGalleryWindow : Window _segmentTwo = SegmentedControl.Draw( ImGui.GetID("gallery.segmented.two"), - ImGui.GetCursorScreenPos(), width, SegmentLabelsTwo, _segmentTwo, @@ -307,7 +316,6 @@ internal sealed class WidgetGalleryWindow : Window _segmentThree = SegmentedControl.Draw( ImGui.GetID("gallery.segmented.three"), - ImGui.GetCursorScreenPos(), width, SegmentLabelsThree, _segmentThree, @@ -318,7 +326,6 @@ internal sealed class WidgetGalleryWindow : Window // and no overhang on the right. SegmentedControl.Draw( ImGui.GetID("gallery.segmented.odd"), - ImGui.GetCursorScreenPos(), 201f, SegmentLabelsThree, 0, @@ -334,7 +341,8 @@ internal sealed class WidgetGalleryWindow : Window ImGui.TextUnformatted("Badge"); ImGui.SliderInt("count##badge", ref _badgeCount, 0, 150); - int[] samples = [1, 9, _badgeCount, 120]; + _badgeSamples[2] = _badgeCount; + var samples = _badgeSamples; var origin = ImGui.GetCursorScreenPos(); var x = origin.X; foreach (var n in samples) @@ -393,12 +401,7 @@ internal sealed class WidgetGalleryWindow : Window ImGui.TextUnformatted("IconButton"); var size = new Vector2(Metrics.SidebarPopOutHitWidth, Metrics.SidebarRowHeight); - FontAwesomeIcon[] glyphs = - [ - FontAwesomeIcon.ArrowUpRightFromSquare, - FontAwesomeIcon.Check, - FontAwesomeIcon.CheckCircle, - ]; + var glyphs = GalleryGlyphs; for (var i = 0; i < glyphs.Length; i++) {