feat(chunk-renderer): add DrawChunks + DrawChunk text-path (C3 stubs icon)
Resurrects v1.5.6 ChatLogWindow's DrawChunks/DrawChunk text-rendering pipeline into the new ChunkRenderer Components-Layer class. Text-chunk path is the full v1.5.6 migration (Plugin.Config.ScreenshotMode, _themes.Active.Colors.TextPrimary, _fonts.ItalicFont/_fonts.AxisItalic substitutions applied per §4.2/§4.5); icon-chunk dispatch in DrawChunk is stubbed pending C3 (EmoteCache + DrawIcon path). ImGuiUtil.WrapText is forward-stubbed in Util/ImGuiUtil.cs as a no-op TextUnformatted wrapper — Sub-Task D will replace the body with the full ~220-LOC word-wrap pipeline. ImGuiUtil.PostPayload is also forward-stubbed (payload hover/click routing belongs to Sub-Task E). Both stubs are the cleanest cut to keep DrawChunk's body faithful to v1.5.6 and avoid temporary fallback paths inside ChunkRenderer. PayloadHandler.cs is a minimal forward-stub class (Hover + Click stubs only) required by the DrawChunks/DrawChunk and PostPayload signatures. Sub-Task E will replace this stub with the full implementation. Discard pattern from C1 removed for _themes/_fonts (now genuinely consumed by DrawChunks/DrawChunk); _logger discard kept — not yet consumed in C2, deferred to E-task wiring.
This commit is contained in:
@@ -1,3 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using HellionChat.Code;
|
||||
using HellionChat.Themes;
|
||||
using HellionChat.Util;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -27,12 +33,146 @@ internal sealed class ChunkRenderer
|
||||
// names change every plugin reload to avoid stable cross-session linkage.
|
||||
_salt = new Random().Next().ToString();
|
||||
|
||||
// Field references kept for C2 consumption; remove no-ops when DrawChunks lands.
|
||||
_ = _themes;
|
||||
_ = _fonts;
|
||||
_ = _logger;
|
||||
}
|
||||
|
||||
public void DrawChunks(
|
||||
IReadOnlyList<Chunk> chunks,
|
||||
bool wrap = true,
|
||||
PayloadHandler? handler = null,
|
||||
float lineWidth = 0f
|
||||
)
|
||||
{
|
||||
// UI-7: render a copy with the sender name reformatted per the user's
|
||||
// display options. Skipped in screenshot mode so the name-anonymising
|
||||
// path in DrawChunk stays reliable (privacy wins). ForDisplay returns
|
||||
// the list unchanged when nothing applies, so non-sender lists and the
|
||||
// neutral default cost only a quick scan.
|
||||
if (!Plugin.Config.ScreenshotMode)
|
||||
chunks = SenderNameDisplay.ForDisplay(chunks);
|
||||
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
|
||||
|
||||
for (var i = 0; i < chunks.Count; i++)
|
||||
{
|
||||
if (chunks[i] is TextChunk text && string.IsNullOrEmpty(text.Content))
|
||||
continue;
|
||||
|
||||
DrawChunk(chunks[i], wrap, handler, lineWidth);
|
||||
|
||||
if (i < chunks.Count - 1)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
}
|
||||
else if (chunks[i].Link is EmotePayload && Plugin.Config.ShowEmotes)
|
||||
{
|
||||
// Emote payloads seem to not automatically put newlines, which
|
||||
// is an issue when modern mode is disabled.
|
||||
ImGui.SameLine();
|
||||
// Use default ImGui behavior for newlines.
|
||||
ImGui.TextUnformatted("");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawChunk(
|
||||
Chunk chunk,
|
||||
bool wrap = true,
|
||||
PayloadHandler? handler = null,
|
||||
float lineWidth = 0f
|
||||
)
|
||||
{
|
||||
if (chunk is IconChunk)
|
||||
{
|
||||
// TODO(C3): wire DrawIcon dispatch + EmotePayload image path here.
|
||||
return;
|
||||
}
|
||||
|
||||
if (chunk is not TextChunk text)
|
||||
return;
|
||||
|
||||
if (chunk.Link is EmotePayload emotePayload && Plugin.Config.ShowEmotes)
|
||||
{
|
||||
var emoteSize = ImGui.CalcTextSize("W");
|
||||
emoteSize = emoteSize with { Y = emoteSize.X } * 1.5f;
|
||||
|
||||
// TextWrap doesn't work for emotes, so we have to wrap them manually
|
||||
if (ImGui.GetContentRegionAvail().X < emoteSize.X)
|
||||
ImGui.NewLine();
|
||||
|
||||
// We only draw a dummy if it is still loading, in the case it failed we draw the actual name
|
||||
var image = EmoteCache.GetEmote(emotePayload.Code);
|
||||
if (image is { Failed: false })
|
||||
{
|
||||
if (image.IsLoaded)
|
||||
image.Draw(emoteSize);
|
||||
else
|
||||
ImGui.Dummy(emoteSize);
|
||||
|
||||
if (ImGui.IsItemHovered())
|
||||
ImGuiUtil.Tooltip(emotePayload.Code);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var colour = text.Foreground;
|
||||
if (colour == null && text.FallbackColour != null)
|
||||
{
|
||||
var type = text.FallbackColour.Value;
|
||||
colour = Plugin.Config.ChatColours.TryGetValue(type, out var col)
|
||||
? col
|
||||
: type.DefaultColor();
|
||||
}
|
||||
|
||||
var push = colour != null;
|
||||
var uColor = push ? ColourUtil.RgbaToAbgr(colour!.Value) : 0;
|
||||
using var pushedColor = ImRaii.PushColor(ImGuiCol.Text, uColor, push);
|
||||
|
||||
var useCustomItalicFont = Plugin.Config.FontsEnabled && _fonts.ItalicFont != null;
|
||||
if (text.Italic)
|
||||
(useCustomItalicFont ? _fonts.ItalicFont! : _fonts.AxisItalic).Push();
|
||||
|
||||
// Check for contains here as sometimes there are multiple
|
||||
// TextChunks with the same PlayerPayload but only one has the name.
|
||||
// E.g. party chat with cross world players adds extra chunks.
|
||||
//
|
||||
// Note: This has been null before, I'm guessing due to some issues with
|
||||
// other plugins. New TextChunks will now enforce empty string in ctor,
|
||||
// but old ones may still be null.
|
||||
// ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract
|
||||
var content = text.Content ?? "";
|
||||
if (Plugin.Config.ScreenshotMode)
|
||||
{
|
||||
if (chunk.Link is PlayerPayload playerPayload)
|
||||
content = HidePlayerInString(
|
||||
content,
|
||||
playerPayload.PlayerName,
|
||||
playerPayload.World.RowId
|
||||
);
|
||||
else if (Plugin.PlayerState.IsLoaded)
|
||||
content = HidePlayerInString(
|
||||
content,
|
||||
Plugin.PlayerState.CharacterName,
|
||||
Plugin.PlayerState.HomeWorld.RowId
|
||||
);
|
||||
}
|
||||
|
||||
var defaultText = ColourUtil.RgbaToVector4(_themes.Active.Colors.TextPrimary);
|
||||
if (wrap)
|
||||
{
|
||||
ImGuiUtil.WrapText(content, chunk, handler, defaultText, lineWidth);
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.TextUnformatted(content);
|
||||
ImGuiUtil.PostPayload(chunk, handler);
|
||||
}
|
||||
|
||||
if (text.Italic)
|
||||
(useCustomItalicFont ? _fonts.ItalicFont! : _fonts.AxisItalic).Pop();
|
||||
}
|
||||
|
||||
private string HidePlayerInString(string str, string playerName, uint worldId)
|
||||
{
|
||||
var expected = _gameFunctions.Chat.AbbreviatePlayerName(playerName);
|
||||
|
||||
Reference in New Issue
Block a user