fix(sidebar): scale the drawn width without moving the stored value

The sidebar constants were raw pixels. At 150% display scaling the text grows,
the column does not, and the row contents stop fitting.

GetWidth and IsExpanded stay unscaled on purpose. The stored width and the
switch threshold are user settings in design pixels, and
SidebarModeAutoSwitchStep compares GetWidth's return value against the raw
bounds with exact equality -- scaling there would fail the step at anything
other than 100%. Scaling happens once, at the single draw call site.

RowHeight and the two hit widths now come from Metrics. The row internals read
GetContentRegionAvail, so they follow automatically and the hit-area split
thresholds stay proportional.

The not-ready branch is scaled too: a scale change triggers a font rebuild, so
that branch really is hit while GlobalScale is moving, and an unscaled width
there makes the sidebar jump.

The width slider referenced the bounds as literals. It now uses the constants,
so it cannot drift away from the clamp.

Known remainder, deliberate: SidebarAutoSwitchThresholdPx is compared against
real screen pixels while the columns now scale, so the switch point drifts at
high scaling. Scaling it would fail the same SelfTest. Noted for v1.11.0.
This commit is contained in:
2026-08-17 23:48:36 +02:00
parent 10b345821f
commit 1e8a60ac80
2 changed files with 18 additions and 10 deletions
@@ -63,15 +63,16 @@ internal sealed class ChannelsTab
if (ImGui.CollapsingHeader("Sidebar"))
{
// Range matches Sidebar.MinSidebarWidth/MaxSidebarWidth (40-300). The
// lower bound sits just above the 38px icon-only threshold; the
// on-disk default (44) and the 150px expanded reference both fit.
// Bounds come from the constants rather than repeating the numbers,
// so the slider cannot drift away from the clamp in Sidebar.GetWidth.
// The stored value is unscaled; display scaling is applied where the
// sidebar is drawn.
DrawSliderInt(
"Sidebar width",
() => Plugin.Config.SidebarWidth,
v => Plugin.Config.SidebarWidth = v,
40,
300
(int)Sidebar.MinSidebarWidth,
(int)Sidebar.MaxSidebarWidth
);
}
}
+12 -5
View File
@@ -27,9 +27,9 @@ internal sealed class Sidebar
public const float MinSidebarWidth = 40f;
public const float MaxSidebarWidth = 300f;
private const float RowHeight = 32f;
private const float PopOutHitWidth = 22f;
private const float GreetedHitWidth = 22f;
private static float RowHeight => Metrics.SidebarRowHeight;
private static float PopOutHitWidth => Metrics.SidebarPopOutHitWidth;
private static float GreetedHitWidth => Metrics.SidebarGreetedHitWidth;
// B3-2 render observability: counts greeted glyphs actually drawn this frame.
// Incremented ONLY in the real glyph branch in DrawRow; reset at Draw start.
@@ -85,6 +85,10 @@ internal sealed class Sidebar
_pool = pool;
}
// Both stay unscaled. The stored width and the switch threshold are user
// settings in design pixels, and SidebarModeAutoSwitchStep compares this
// return value against the raw bounds. Display scaling is applied once, at
// the single draw call site below.
public bool IsExpanded(float windowWidth) =>
windowWidth >= Plugin.Config.SidebarAutoSwitchThresholdPx;
@@ -111,12 +115,15 @@ internal sealed class Sidebar
if (!_fonts.FontsReady)
{
ImGui.Dummy(new Vector2(IconOnlyWidth, 0));
// A scale change triggers a font rebuild, so this branch is really
// hit while GlobalScale is moving -- an unscaled width here makes
// the sidebar jump.
ImGui.Dummy(new Vector2(Metrics.SidebarIconOnlyWidth, 0));
return;
}
var expanded = IsExpanded(windowWidth);
var width = GetWidth(windowWidth);
var width = GetWidth(windowWidth) * Metrics.Scale;
using var child = ImRaii.Child("##hellion-sidebar", new Vector2(width, 0));
if (!child.Success)
return;