feat(themes): restore the per-card theme preview mockup in the picker
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
using System.Numerics;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using HellionChat.Themes;
|
||||
using HellionChat.Util;
|
||||
|
||||
namespace HellionChat.Ui.Components.Settings;
|
||||
|
||||
// Mini chat-window mockup drawn straight into the WindowDrawList (restored from
|
||||
// 1.5.6 ThemeMockup). No textures, no per-frame allocations — pure rect/text.
|
||||
internal static class ThemeMockup
|
||||
{
|
||||
public static void Draw(Vector2 origin, Vector2 size, Theme theme)
|
||||
{
|
||||
var draw = ImGui.GetWindowDrawList();
|
||||
var c = theme.Colors;
|
||||
|
||||
draw.AddRectFilled(
|
||||
origin,
|
||||
origin + size,
|
||||
ColourUtil.RgbaToAbgr(c.WindowBg | 0xFFu),
|
||||
theme.Layout.WindowRounding
|
||||
);
|
||||
|
||||
var titleHeight = 14f;
|
||||
draw.AddRectFilled(
|
||||
origin,
|
||||
new Vector2(origin.X + size.X, origin.Y + titleHeight),
|
||||
ColourUtil.RgbaToAbgr(c.Identity),
|
||||
theme.Layout.WindowRounding
|
||||
);
|
||||
|
||||
var tabY = origin.Y + titleHeight + 4f;
|
||||
var tabHeight = 12f;
|
||||
for (var i = 0; i < 3; i++)
|
||||
{
|
||||
var tabX = origin.X + 6f + i * 28f;
|
||||
var color = i == 0 ? c.FrameBg : c.ChildBg;
|
||||
draw.AddRectFilled(
|
||||
new Vector2(tabX, tabY),
|
||||
new Vector2(tabX + 26f, tabY + tabHeight),
|
||||
ColourUtil.RgbaToAbgr(color),
|
||||
theme.Layout.TabRounding
|
||||
);
|
||||
|
||||
if (i == 0)
|
||||
{
|
||||
draw.AddRectFilled(
|
||||
new Vector2(tabX, tabY + tabHeight - 2f),
|
||||
new Vector2(tabX + 26f, tabY + tabHeight),
|
||||
ColourUtil.RgbaToAbgr(c.Primary)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
var rowY = tabY + tabHeight + 6f;
|
||||
var rowHeight = 18f;
|
||||
draw.AddRectFilled(
|
||||
new Vector2(origin.X + 6f, rowY),
|
||||
new Vector2(origin.X + size.X - 6f, rowY + rowHeight),
|
||||
ColourUtil.RgbaToAbgr(c.Surface),
|
||||
2f
|
||||
);
|
||||
|
||||
var btnW = 28f;
|
||||
var btnH = 10f;
|
||||
var btnX = origin.X + size.X - btnW - 6f;
|
||||
var btnY = origin.Y + size.Y - btnH - 6f;
|
||||
draw.AddRectFilled(
|
||||
new Vector2(btnX, btnY),
|
||||
new Vector2(btnX + btnW, btnY + btnH),
|
||||
ColourUtil.RgbaToAbgr(c.Accent),
|
||||
theme.Layout.FrameRounding
|
||||
);
|
||||
|
||||
draw.AddRect(
|
||||
origin,
|
||||
origin + size,
|
||||
ColourUtil.RgbaToAbgr(c.Border),
|
||||
theme.Layout.WindowRounding
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,8 @@ internal sealed class ThemePicker
|
||||
// to enforce coverage. Kept on the static map so the test does not pierce instance state.
|
||||
internal static IEnumerable<string> CategoryMapSlugs => CategoryMap.SelectMany(c => c.Slugs);
|
||||
|
||||
private const float CardHeight = 132f;
|
||||
|
||||
private readonly ThemeRegistry _themes;
|
||||
private readonly Plugin _plugin;
|
||||
|
||||
@@ -51,10 +53,7 @@ internal sealed class ThemePicker
|
||||
: ImGuiTreeNodeFlags.None;
|
||||
if (ImGui.CollapsingHeader(category, flags))
|
||||
{
|
||||
foreach (var slug in slugs)
|
||||
{
|
||||
DrawCard(slug);
|
||||
}
|
||||
DrawThemeGrid(Resolve(slugs));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,10 +69,7 @@ internal sealed class ThemePicker
|
||||
)
|
||||
)
|
||||
{
|
||||
foreach (var theme in customs)
|
||||
{
|
||||
DrawCard(theme.Slug);
|
||||
}
|
||||
DrawThemeGrid(customs);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -84,61 +80,97 @@ internal sealed class ThemePicker
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawCard(string slug)
|
||||
private IEnumerable<Theme> Resolve(IEnumerable<string> slugs)
|
||||
{
|
||||
if (!_themes.TryGet(slug, out var theme))
|
||||
{
|
||||
foreach (var slug in slugs)
|
||||
if (_themes.TryGet(slug, out var theme))
|
||||
yield return theme;
|
||||
}
|
||||
|
||||
// Grid of theme cards, each carrying a mini chat mockup (restored from 1.5.6
|
||||
// DrawThemeGrid + ThemeMockup). Column count adapts to the available width.
|
||||
private void DrawThemeGrid(IEnumerable<Theme> themes)
|
||||
{
|
||||
var list = themes.ToList();
|
||||
if (list.Count == 0)
|
||||
return;
|
||||
|
||||
var avail = ImGui.GetContentRegionAvail().X;
|
||||
var columns = avail >= 460f ? 2 : 1;
|
||||
var cardWidth = columns > 1 ? (avail - (columns - 1) * 8f) / columns : avail;
|
||||
|
||||
for (var i = 0; i < list.Count; i++)
|
||||
{
|
||||
DrawThemeCard(list[i], cardWidth, CardHeight);
|
||||
if ((i + 1) % columns != 0 && i != list.Count - 1)
|
||||
ImGui.SameLine();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawThemeCard(Theme theme, float w, float h)
|
||||
{
|
||||
ImGui.BeginGroup();
|
||||
|
||||
var isActive = string.Equals(
|
||||
theme.Slug,
|
||||
_themes.Active.Slug,
|
||||
StringComparison.OrdinalIgnoreCase
|
||||
);
|
||||
var origin = ImGui.GetCursorScreenPos();
|
||||
var clicked = ImGui.InvisibleButton($"##theme-card-{theme.Slug}", new Vector2(w, h));
|
||||
var hovered = ImGui.IsItemHovered();
|
||||
|
||||
var draw = ImGui.GetWindowDrawList();
|
||||
draw.AddRectFilled(
|
||||
origin,
|
||||
origin + new Vector2(w, h),
|
||||
ColourUtil.RgbaToAbgr(theme.Colors.WindowBg | 0xFFu),
|
||||
4f
|
||||
);
|
||||
|
||||
if (isActive)
|
||||
{
|
||||
draw.AddRect(
|
||||
origin,
|
||||
origin + new Vector2(w, h),
|
||||
ColourUtil.RgbaToAbgr(theme.Colors.Primary),
|
||||
4f,
|
||||
ImDrawFlags.None,
|
||||
2f
|
||||
);
|
||||
}
|
||||
else if (hovered)
|
||||
{
|
||||
draw.AddRect(
|
||||
origin,
|
||||
origin + new Vector2(w, h),
|
||||
ColourUtil.RgbaToAbgr(theme.Colors.PrimaryLight & 0xFFFFFF99u),
|
||||
4f,
|
||||
ImDrawFlags.None,
|
||||
1f
|
||||
);
|
||||
}
|
||||
|
||||
var active = _themes.Active.Slug == slug;
|
||||
var label = $"{theme.Name} — {theme.Author}##theme-card-{slug}";
|
||||
ThemeMockup.Draw(origin + new Vector2(12f, 12f), new Vector2(w - 24f, 60f), theme);
|
||||
|
||||
// Selectable uses ImGui's default Header colour, not the theme's Surface.
|
||||
// Swatch overlay below carries the theme cue; v1.7.x-polish if testers flag the mismatch.
|
||||
if (ImGui.Selectable(label, active, ImGuiSelectableFlags.None, new Vector2(0, 40)))
|
||||
draw.AddText(
|
||||
origin + new Vector2(12f, 80f),
|
||||
ColourUtil.RgbaToAbgr(theme.Colors.TextPrimary),
|
||||
theme.Name
|
||||
);
|
||||
draw.AddText(
|
||||
origin + new Vector2(12f, 100f),
|
||||
ColourUtil.RgbaToAbgr(theme.Colors.TextMuted),
|
||||
theme.Author
|
||||
);
|
||||
|
||||
ImGui.EndGroup();
|
||||
|
||||
if (clicked)
|
||||
{
|
||||
_themes.Switch(slug);
|
||||
Plugin.Config.Theme = slug;
|
||||
_themes.Switch(theme.Slug);
|
||||
Plugin.Config.Theme = theme.Slug;
|
||||
_plugin.SaveConfig();
|
||||
}
|
||||
|
||||
// Mini-Preview-Swatch overlay (3 ABGR boxes on the right side of the card).
|
||||
// Selectable owns the hit-box; the DrawList overlay is decorative — full card area
|
||||
// remains the click target, not just the swatch.
|
||||
//
|
||||
// RGBA-vs-ABGR-Disziplin: ThemeColors slots hold uint values in 0xRRGGBBAA layout
|
||||
// (see ThemeColors.cs header comment). ImGui draw calls expect ABGR (native byte order)
|
||||
// — pass theme colours through ColourUtil.RgbaToAbgr before any AddRectFilled / AddText
|
||||
// / AddLine. The repo-wide pattern is "swap at the boundary" (see InputBar swap-at-
|
||||
// boundary pattern). Forgetting the swap renders Red and Blue channels swapped and
|
||||
// shifts the alpha byte into the green slot.
|
||||
var draw = ImGui.GetWindowDrawList();
|
||||
var max = ImGui.GetItemRectMax();
|
||||
var min = ImGui.GetItemRectMin();
|
||||
var swatchY = min.Y + 12;
|
||||
DrawSwatch(
|
||||
draw,
|
||||
new Vector2(max.X - 60, swatchY),
|
||||
ColourUtil.RgbaToAbgr(theme.Colors.Surface)
|
||||
);
|
||||
DrawSwatch(
|
||||
draw,
|
||||
new Vector2(max.X - 42, swatchY),
|
||||
ColourUtil.RgbaToAbgr(theme.Colors.Primary)
|
||||
);
|
||||
DrawSwatch(
|
||||
draw,
|
||||
new Vector2(max.X - 24, swatchY),
|
||||
ColourUtil.RgbaToAbgr(theme.Colors.Accent)
|
||||
);
|
||||
}
|
||||
|
||||
// Caller contract: `colorAbgr` is already byte-swapped from the ThemeColors RGBA backing
|
||||
// field via ColourUtil.RgbaToAbgr. Passing a raw RGBA value here renders with the wrong
|
||||
// channel order.
|
||||
private static void DrawSwatch(ImDrawListPtr draw, Vector2 topLeft, uint colorAbgr)
|
||||
{
|
||||
draw.AddRectFilled(topLeft, topLeft + new Vector2(14, 14), colorAbgr, 2f);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user