feat(settings): render six mock elements in live preview

This commit is contained in:
2026-05-26 18:20:57 +02:00
parent 304e1aab20
commit 13ca241ac7
@@ -4,6 +4,7 @@ using Dalamud.Bindings.ImGui;
using Dalamud.Interface.Utility.Raii;
using HellionChat.Themes;
using HellionChat.Ui.StyleEngine;
using HellionChat.Util;
namespace HellionChat.Ui.Components.Settings;
@@ -14,6 +15,20 @@ internal sealed class LivePreviewPanel : IDisposable
// higher signals a Dispose skip and a subscriber leak against ThemeRegistry.
internal static int InstanceCount;
// Plan-mandated mock strings — international tester-ready, do not localise.
private const string MockSystem = "System: Connection established";
private const string MockSay = "Say: Hello, world!";
private const string MockTell = "Tell → Player: Hey, want to party?";
private const string MockFc = "FC: Welcome aboard.";
// FontAwesome is intentionally not pulled in — crown/cog render as Unicode
// glyphs in the default font so this panel stays DI-light (Step 2 scope).
private const string CrownGlyph = "♛";
private const string CogGlyph = "⚙";
private const float MiddleBandHeight = 220f;
private const float SidebarWidth = 70f;
private readonly ThemeRegistry _themes;
private readonly TokenResolver _resolver;
@@ -49,40 +64,256 @@ internal sealed class LivePreviewPanel : IDisposable
DrawBrandBar(theme);
DrawHonorificHeader(theme);
// Sidebar paints the left strip without advancing the cursor; the
// MessageList paints the right strip and reserves the full band.
DrawSidebar(theme);
DrawMessageList(theme);
DrawInputBar(theme);
DrawStatusBar(theme);
}
private void DrawBrandBar(Theme theme)
private static void DrawBrandBar(Theme theme)
{
// Mini gradient: PrimaryDark → Primary → PrimaryLight → PrimaryGlow.
// Implementation lands in next sub-step.
const float height = 24f;
var draw = ImGui.GetWindowDrawList();
var origin = ImGui.GetCursorScreenPos();
var width = ImGui.GetContentRegionAvail().X;
var max = new Vector2(origin.X + width, origin.Y + height);
// Horizontal gradient split into three rects so all four primary
// slots (PrimaryDark/Primary/PrimaryLight/PrimaryGlow) drive the bar.
var pdAbgr = ColourUtil.RgbaToAbgr(theme.Colors.PrimaryDark);
var pAbgr = ColourUtil.RgbaToAbgr(theme.Colors.Primary);
var plAbgr = ColourUtil.RgbaToAbgr(theme.Colors.PrimaryLight);
var pgAbgr = ColourUtil.RgbaToAbgr(theme.Colors.PrimaryGlow);
var third = width / 3f;
draw.AddRectFilledMultiColor(
origin,
new Vector2(origin.X + third, max.Y),
pdAbgr,
pAbgr,
pAbgr,
pdAbgr
);
draw.AddRectFilledMultiColor(
new Vector2(origin.X + third, origin.Y),
new Vector2(origin.X + 2 * third, max.Y),
pAbgr,
plAbgr,
plAbgr,
pAbgr
);
draw.AddRectFilledMultiColor(
new Vector2(origin.X + 2 * third, origin.Y),
max,
plAbgr,
pgAbgr,
pgAbgr,
plAbgr
);
var label = "HellionChat";
var textSize = ImGui.CalcTextSize(label);
var textPos = new Vector2(
origin.X + (width - textSize.X) * 0.5f,
origin.Y + (height - textSize.Y) * 0.5f
);
draw.AddText(textPos, ColourUtil.RgbaToAbgr(theme.Colors.TextPrimary), label);
ImGui.Dummy(new Vector2(width, height));
}
private void DrawHonorificHeader(Theme theme)
private static void DrawHonorificHeader(Theme theme)
{
// Crown + Brackets-Title in Identity. Implementation in next sub-step.
const float height = 32f;
var draw = ImGui.GetWindowDrawList();
var origin = ImGui.GetCursorScreenPos();
var width = ImGui.GetContentRegionAvail().X;
draw.AddLine(
origin,
new Vector2(origin.X + width, origin.Y),
ColourUtil.RgbaToAbgr(theme.Colors.Border),
1f
);
var crownAbgr = ColourUtil.RgbaToAbgr(theme.Colors.Identity);
var textAbgr = ColourUtil.RgbaToAbgr(theme.Colors.TextPrimary);
var title = "«Champion» Preview";
var crownSize = ImGui.CalcTextSize(CrownGlyph);
var titleSize = ImGui.CalcTextSize(title);
var totalWidth = crownSize.X + 4f + titleSize.X;
var startX = origin.X + (width - totalWidth) * 0.5f;
var y = origin.Y + (height - titleSize.Y) * 0.5f;
draw.AddText(new Vector2(startX, y), crownAbgr, CrownGlyph);
draw.AddText(new Vector2(startX + crownSize.X + 4f, y), textAbgr, title);
ImGui.Dummy(new Vector2(width, height));
}
private void DrawSidebar(Theme theme)
private static void DrawSidebar(Theme theme)
{
// 3 channel rows. Implementation in next sub-step.
// Paints the left strip of the horizontal middle band. MessageList
// consumes the cursor reservation for the full band height.
var draw = ImGui.GetWindowDrawList();
var origin = ImGui.GetCursorScreenPos();
var rowHeight = MiddleBandHeight / 3f;
var surface = ColourUtil.RgbaToAbgr(theme.Colors.Surface);
var surfaceHover = ColourUtil.RgbaToAbgr(theme.Colors.SurfaceHover);
var textAbgr = ColourUtil.RgbaToAbgr(theme.Colors.TextPrimary);
var primaryAbgr = ColourUtil.RgbaToAbgr(theme.Colors.Primary);
var accentAbgr = ColourUtil.RgbaToAbgr(theme.Colors.Accent);
ReadOnlySpan<string> labels = ["Linkshell", "Tell", "FC"];
for (var i = 0; i < 3; i++)
{
var rowMin = new Vector2(origin.X, origin.Y + i * rowHeight);
var rowMax = new Vector2(origin.X + SidebarWidth, rowMin.Y + rowHeight);
var bg = i == 1 ? surfaceHover : surface;
draw.AddRectFilled(rowMin, rowMax, bg);
if (i == 0)
{
draw.AddRectFilled(rowMin, new Vector2(rowMin.X + 2f, rowMax.Y), primaryAbgr);
}
var labelSize = ImGui.CalcTextSize(labels[i]);
var textPos = new Vector2(rowMin.X + 6f, rowMin.Y + (rowHeight - labelSize.Y) * 0.5f);
draw.AddText(textPos, textAbgr, labels[i]);
if (i == 1)
{
// Tell row carries an unread-dot in Accent on the right.
var dotCenter = new Vector2(rowMax.X - 8f, rowMin.Y + rowHeight * 0.5f);
draw.AddRectFilled(
new Vector2(dotCenter.X - 2f, dotCenter.Y - 2f),
new Vector2(dotCenter.X + 2f, dotCenter.Y + 2f),
accentAbgr
);
}
}
}
private void DrawMessageList(Theme theme)
private static void DrawMessageList(Theme theme)
{
// 4 mock messages. Implementation in next sub-step.
var draw = ImGui.GetWindowDrawList();
var origin = ImGui.GetCursorScreenPos();
var totalWidth = ImGui.GetContentRegionAvail().X;
var listOrigin = new Vector2(origin.X + SidebarWidth, origin.Y);
var listWidth = totalWidth - SidebarWidth;
var max = new Vector2(listOrigin.X + listWidth, listOrigin.Y + MiddleBandHeight);
draw.AddRectFilled(listOrigin, max, ColourUtil.RgbaToAbgr(theme.Colors.WindowBg));
var padMin = new Vector2(listOrigin.X + 2f, max.Y - 6f);
draw.AddRectFilled(
padMin,
new Vector2(max.X - 2f, max.Y - 2f),
ColourUtil.RgbaToAbgr(theme.Colors.FrameBg)
);
ReadOnlySpan<(string Text, uint Rgba)> rows =
[
(MockSystem, theme.Colors.TextMuted),
(MockSay, theme.Colors.TextPrimary),
(MockTell, theme.Colors.StatusInfo),
(MockFc, theme.Colors.StatusSuccess),
];
var lineHeight = ImGui.GetTextLineHeightWithSpacing();
for (var i = 0; i < rows.Length; i++)
{
var pos = new Vector2(listOrigin.X + 6f, listOrigin.Y + 6f + i * lineHeight);
draw.AddText(pos, ColourUtil.RgbaToAbgr(rows[i].Rgba), rows[i].Text);
}
draw.AddLine(
new Vector2(origin.X, max.Y - 1f),
new Vector2(origin.X + totalWidth, max.Y - 1f),
ColourUtil.RgbaToAbgr(theme.Colors.Border),
1f
);
// Reserve the full middle-band height (sidebar overlays into the
// same vertical span and does not advance the cursor itself).
ImGui.Dummy(new Vector2(totalWidth, MiddleBandHeight));
}
private void DrawInputBar(Theme theme)
private static void DrawInputBar(Theme theme)
{
// Pill + text + cog. Implementation in next sub-step.
const float height = 24f;
const float pillWidth = 50f;
var draw = ImGui.GetWindowDrawList();
var origin = ImGui.GetCursorScreenPos();
var width = ImGui.GetContentRegionAvail().X;
var max = new Vector2(origin.X + width, origin.Y + height);
draw.AddRectFilled(origin, max, ColourUtil.RgbaToAbgr(theme.Colors.FrameBg));
var pillMin = new Vector2(origin.X + 4f, origin.Y + 4f);
var pillMax = new Vector2(origin.X + pillWidth, max.Y - 4f);
draw.AddRectFilled(pillMin, pillMax, ColourUtil.RgbaToAbgr(theme.Colors.Primary), 6f);
var pillLabel = "Say";
var pillLabelSize = ImGui.CalcTextSize(pillLabel);
var pillTextPos = new Vector2(
pillMin.X + ((pillMax.X - pillMin.X) - pillLabelSize.X) * 0.5f,
pillMin.Y + ((pillMax.Y - pillMin.Y) - pillLabelSize.Y) * 0.5f
);
draw.AddText(pillTextPos, ColourUtil.RgbaToAbgr(theme.Colors.TextPrimary), pillLabel);
var placeholder = "Type a message...";
var phSize = ImGui.CalcTextSize(placeholder);
var phPos = new Vector2(pillMax.X + 6f, origin.Y + (height - phSize.Y) * 0.5f);
draw.AddText(phPos, ColourUtil.RgbaToAbgr(theme.Colors.TextPrimary), placeholder);
var cogSize = ImGui.CalcTextSize(CogGlyph);
var cogPos = new Vector2(max.X - cogSize.X - 6f, origin.Y + (height - cogSize.Y) * 0.5f);
draw.AddText(cogPos, ColourUtil.RgbaToAbgr(theme.Colors.TextMuted), CogGlyph);
ImGui.Dummy(new Vector2(width, height));
}
private void DrawStatusBar(Theme theme)
private static void DrawStatusBar(Theme theme)
{
// Status icons. Implementation in next sub-step.
const float height = 20f;
const float iconSize = 8f;
const float iconGap = 6f;
var draw = ImGui.GetWindowDrawList();
var origin = ImGui.GetCursorScreenPos();
var width = ImGui.GetContentRegionAvail().X;
var max = new Vector2(origin.X + width, origin.Y + height);
draw.AddRectFilled(origin, max, ColourUtil.RgbaToAbgr(theme.Colors.ChildBg));
ReadOnlySpan<uint> statusRgba =
[
theme.Colors.StatusSuccess,
theme.Colors.StatusDanger,
theme.Colors.StatusWarning,
];
var iconY = origin.Y + (height - iconSize) * 0.5f;
for (var i = 0; i < statusRgba.Length; i++)
{
var iconX = origin.X + 6f + i * (iconSize + iconGap);
draw.AddRectFilled(
new Vector2(iconX, iconY),
new Vector2(iconX + iconSize, iconY + iconSize),
ColourUtil.RgbaToAbgr(statusRgba[i]),
2f
);
}
var label = "preview";
var labelSize = ImGui.CalcTextSize(label);
var labelPos = new Vector2(
max.X - labelSize.X - 6f,
origin.Y + (height - labelSize.Y) * 0.5f
);
draw.AddText(labelPos, ColourUtil.RgbaToAbgr(theme.Colors.TextDim), label);
ImGui.Dummy(new Vector2(width, height));
}
}