feat(message-list): wire ChunkRenderer ctor + AttachPayloadHandler setter

H integrates the chunk-render pipeline into MessageList:

- Extends ctor to 4 params (themes, resolver, fonts, chunkRenderer);
  TokenResolver preserved as load-bearing dep
- Adds private PayloadHandler? _handler field + internal
  AttachPayloadHandler(PayloadHandler) setter
- Switches DrawCompactRow/DrawCardRow render-path to
  _chunkRenderer.DrawChunks(message.Content, wrap, handler, 0f)
  instead of plain TextUnformatted

Setter-injection for PayloadHandler is the §6.2 cycle-resolution
(PayloadHandler → MainWindow → MessageList → PayloadHandler ctor-cycle
broken by post-construction wiring). G's HostedService.StartAsync will
call AttachPayloadHandler after both singletons resolve.

Also extends MessageList DI-reg in PluginHostFactory.cs with the
ChunkRenderer arg (4th GetRequiredService).
This commit is contained in:
2026-05-27 19:24:20 +02:00
parent 2e143686af
commit 4fb5ee6128
2 changed files with 32 additions and 23 deletions
+2 -1
View File
@@ -132,7 +132,8 @@ internal static class PluginHostFactory
services.AddSingleton(sp => new Ui.Components.MessageList(
sp.GetRequiredService<ThemeRegistry>(),
sp.GetRequiredService<Ui.StyleEngine.TokenResolver>(),
sp.GetRequiredService<FontManager>()
sp.GetRequiredService<FontManager>(),
sp.GetRequiredService<Ui.Components.ChunkRenderer>()
));
services.AddSingleton(_ => new Ui.Components.SymbolPicker());
services.AddSingleton(sp => new Ui.Components.InputBar(
+30 -22
View File
@@ -12,8 +12,7 @@ namespace HellionChat.Ui.Components;
// rows have a constant line height; card mode falls back to a linear
// render with a per-message height cache and an IsItemVisible skip path
// so off-screen rows place a Dummy of the cached height rather than
// running the full render again. Text-only rendering for now — full
// chunk/payload rendering re-attaches in a later cycle.
// running the full render again.
internal sealed class MessageList
{
private const float CompactRowHeight = 18f;
@@ -21,12 +20,28 @@ internal sealed class MessageList
private readonly ThemeRegistry _themes;
private readonly TokenResolver _resolver;
private readonly FontManager _fonts;
private readonly ChunkRenderer _chunkRenderer;
public MessageList(ThemeRegistry themes, TokenResolver resolver, FontManager fonts)
private PayloadHandler? _handler;
// §6.2: setter-injection breaks the PayloadHandler → MainWindow → MessageList → PayloadHandler 3-cycle.
// Wired by PayloadHandlerInitHostedService.StartAsync after both singletons exist.
internal void AttachPayloadHandler(PayloadHandler handler)
{
_handler = handler;
}
public MessageList(
ThemeRegistry themes,
TokenResolver resolver,
FontManager fonts,
ChunkRenderer chunkRenderer
)
{
_themes = themes;
_resolver = resolver;
_fonts = fonts;
_chunkRenderer = chunkRenderer;
}
public void Draw(Tab tab)
@@ -40,10 +55,6 @@ internal sealed class MessageList
// No own ImRaii.Child here — MainWindow already wraps the message
// area in one. Nesting would give the window two stacked scrolls
// and a runaway content-height computation.
var theme = _themes.Active;
var textAbgr = ColourUtil.RgbaToAbgr(theme.Colors.TextPrimary);
var mutedAbgr = ColourUtil.RgbaToAbgr(theme.Colors.TextMuted);
using var messages = tab.Messages.GetReadOnly(3);
var compact = Plugin.Config.UseCompactDensity;
@@ -53,15 +64,15 @@ internal sealed class MessageList
var pinnedToBottom = ImGui.GetScrollY() >= ImGui.GetScrollMaxY() - 1f;
if (compact)
DrawCompact(messages, textAbgr, mutedAbgr);
DrawCompact(messages);
else
DrawCard(tab, messages, textAbgr, mutedAbgr);
DrawCard(tab, messages);
if (pinnedToBottom)
ImGui.SetScrollHereY(1f);
}
private void DrawCompact(IReadOnlyList<Message> messages, uint textAbgr, uint mutedAbgr)
private void DrawCompact(IReadOnlyList<Message> messages)
{
unsafe
{
@@ -72,7 +83,7 @@ internal sealed class MessageList
while (clipper.Step())
{
for (var i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
DrawCompactRow(messages[i], textAbgr, mutedAbgr);
DrawCompactRow(messages[i]);
}
clipper.End();
}
@@ -83,18 +94,18 @@ internal sealed class MessageList
}
}
private void DrawCompactRow(Message message, uint textAbgr, uint mutedAbgr)
private void DrawCompactRow(Message message)
{
var timestamp = FormatTimestamp(message.Date);
var sender = message.SenderSource.TextValue;
var content = message.ContentSource.TextValue;
var line = string.IsNullOrEmpty(sender)
? $"{timestamp} {content}"
: $"{timestamp} {sender}: {content}";
ImGui.TextUnformatted(line);
ImGui.TextUnformatted(
string.IsNullOrEmpty(sender) ? timestamp : $"{timestamp} {sender}: "
);
ImGui.SameLine();
_chunkRenderer.DrawChunks(message.Content, wrap: true, handler: _handler, lineWidth: 0f);
}
private void DrawCard(Tab tab, IReadOnlyList<Message> messages, uint textAbgr, uint mutedAbgr)
private void DrawCard(Tab tab, IReadOnlyList<Message> messages)
{
var tabId = tab.Identifier;
for (var i = 0; i < messages.Count; i++)
@@ -127,11 +138,8 @@ internal sealed class MessageList
{
var timestamp = FormatTimestamp(message.Date);
var sender = message.SenderSource.TextValue;
var content = message.ContentSource.TextValue;
ImGui.TextUnformatted(string.IsNullOrEmpty(sender) ? timestamp : $"{timestamp} {sender}");
ImGui.PushTextWrapPos(0f);
ImGui.TextUnformatted(content);
ImGui.PopTextWrapPos();
_chunkRenderer.DrawChunks(message.Content, wrap: true, handler: _handler, lineWidth: 0f);
}
private static string FormatTimestamp(DateTimeOffset date)