feat(style): add the setting-row and toggle-switch widgets

Two composites for the settings window, both built on the primitives from
v1.10.0 rather than beside them.

SettingRow puts the label left and the control right-aligned. ImGui does it the
other way round, control first and label trailing, which is a large part of why
the settings window reads as a form dump rather than a settings page. The
control arrives as a callback so one row covers toggles, sliders, combos and
buttons.

The cursor advance in SettingRow is the part that needed care. It has to happen
after the callback, because ItemSize overwrites CursorPos outright when the
control is submitted -- advancing first would simply be undone. But it must not
be an ImGui.Dummy: that submits an item and would replace the control as
g.LastItemData, silently disabling every IsItemDeactivatedAfterEdit save
throttle in the window. SetCursorScreenPos moves the cursor without touching
last-item state.

ToggleSwitch is a capsule with a gliding knob, driven by HoverState so it
animates instead of snapping. Unlike a slider there is no throttle to preserve:
a checkbox commits on the click itself. The caller owns the hit area, so a
settings row can make the whole row clickable, label included.

Named ToggleSwitch, not Toggle: Dalamud's Window base class already has a
Toggle() method, and the plain name collides in every window that uses the
widget.

Geometry lives in WidgetGeometry as usual, with nine new cases pinning the
right-alignment split, the narrow-row fallback (the label yields before the
control does), and that the knob stays inside its capsule at both ends.
ColourUtil gains a two-colour Lerp for the track crossfade.
This commit is contained in:
2026-08-18 11:37:11 +02:00
parent 5b51f7dcfd
commit 86992c70f2
5 changed files with 332 additions and 0 deletions
@@ -24,6 +24,8 @@ internal sealed class WidgetGalleryWindow : Window
private int _badgeCount = 3;
private bool _rowActive = true;
private bool _toggleA = true;
private bool _toggleB;
internal WidgetGalleryWindow(Plugin plugin, TokenResolver resolver)
: base("Widget Gallery###hellion-widget-gallery")
@@ -44,6 +46,8 @@ internal sealed class WidgetGalleryWindow : Window
ImGui.Separator();
DrawRowSection(c);
DrawToggleSection(c);
DrawSettingRowSection(c);
DrawBadgeSection(c);
DrawPillSection(c);
DrawIconButtonSection(c);
@@ -97,6 +101,79 @@ internal sealed class WidgetGalleryWindow : Window
ImGui.Spacing();
}
private void DrawToggleSection(ThemeColors c)
{
ImGui.TextUnformatted("Toggle");
var colors = new ToggleSwitchColors
{
TrackOffAbgr = _palette.Abgr(Token.SurfaceRaised, c),
TrackOnAbgr = _palette.Abgr(Token.AccentPrimary, c),
KnobAbgr = _palette.Abgr(Token.Text, c),
};
var size = ToggleSwitch.CalcSize();
var gap = 10f * Metrics.Scale;
for (var i = 0; i < 2; i++)
{
var origin = ImGui.GetCursorScreenPos();
var id = ImGui.GetID($"gallery.toggle.{i}");
if (ImGui.InvisibleButton($"##gallery-toggle-{i}", size))
{
if (i == 0)
_toggleA = !_toggleA;
else
_toggleB = !_toggleB;
}
ToggleSwitch.Draw(id, origin, i == 0 ? _toggleA : _toggleB, colors);
if (i == 0)
ImGui.SameLine(0f, gap);
}
ImGui.Spacing();
}
private void DrawSettingRowSection(ThemeColors c)
{
ImGui.TextUnformatted("SettingRow");
var colors = new SettingRowColors
{
LabelAbgr = _palette.Abgr(Token.Text, c),
DescriptionAbgr = _palette.Abgr(Token.TextMuted, c),
SurfaceHoverAbgr = _palette.Abgr(Token.SurfaceHover, c),
BorderAbgr = _palette.Abgr(Token.Border, c),
};
SettingRow.Draw(
ImGui.GetID("gallery.settingrow.plain"),
"A plain option",
null,
colors,
() => ImGui.Checkbox("##gallery-sr-a", ref _toggleA)
);
SettingRow.Draw(
ImGui.GetID("gallery.settingrow.described"),
"With a description",
"The second line explains what the control above actually does.",
colors,
() => ImGui.Checkbox("##gallery-sr-b", ref _toggleB)
);
SettingRow.Draw(
ImGui.GetID("gallery.settingrow.long"),
"A deliberately very long label that has to be clipped somewhere",
null,
colors,
() => ImGui.SliderInt("##gallery-sr-c", ref _badgeCount, 0, 150)
);
ImGui.Spacing();
}
private void DrawBadgeSection(ThemeColors c)
{
ImGui.TextUnformatted("Badge");