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:
@@ -0,0 +1,15 @@
|
|||||||
|
using Dalamud.Bindings.ImGui;
|
||||||
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
|
|
||||||
|
namespace HellionChat;
|
||||||
|
|
||||||
|
// TODO(E): full PayloadHandler implementation — click/hover routing, tooltip
|
||||||
|
// positioning, party-finder and URI handling. This forward-stub exists only to
|
||||||
|
// satisfy the DrawChunks/DrawChunk and ImGuiUtil.PostPayload signatures while
|
||||||
|
// Sub-Task E is pending.
|
||||||
|
public sealed class PayloadHandler
|
||||||
|
{
|
||||||
|
internal void Hover(Payload payload) { }
|
||||||
|
|
||||||
|
internal void Click(Chunk chunk, Payload? payload, ImGuiMouseButton button) { }
|
||||||
|
}
|
||||||
@@ -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.Themes;
|
||||||
using HellionChat.Util;
|
using HellionChat.Util;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
@@ -27,12 +33,146 @@ internal sealed class ChunkRenderer
|
|||||||
// names change every plugin reload to avoid stable cross-session linkage.
|
// names change every plugin reload to avoid stable cross-session linkage.
|
||||||
_salt = new Random().Next().ToString();
|
_salt = new Random().Next().ToString();
|
||||||
|
|
||||||
// Field references kept for C2 consumption; remove no-ops when DrawChunks lands.
|
|
||||||
_ = _themes;
|
|
||||||
_ = _fonts;
|
|
||||||
_ = _logger;
|
_ = _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)
|
private string HidePlayerInString(string str, string playerName, uint worldId)
|
||||||
{
|
{
|
||||||
var expected = _gameFunctions.Chat.AbbreviatePlayerName(playerName);
|
var expected = _gameFunctions.Chat.AbbreviatePlayerName(playerName);
|
||||||
|
|||||||
@@ -615,4 +615,51 @@ internal static class ImGuiUtil
|
|||||||
extraChatChannels.Remove(id);
|
extraChatChannels.Remove(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Payload interaction state shared between PostPayload and WrapText.
|
||||||
|
// Tracks the last hovered payload so hover-leave events can fire correctly.
|
||||||
|
private static readonly ImGuiMouseButton[] Buttons =
|
||||||
|
[
|
||||||
|
ImGuiMouseButton.Left,
|
||||||
|
ImGuiMouseButton.Middle,
|
||||||
|
ImGuiMouseButton.Right,
|
||||||
|
];
|
||||||
|
|
||||||
|
private static Payload? Hovered;
|
||||||
|
|
||||||
|
internal static void PostPayload(Chunk chunk, PayloadHandler? handler)
|
||||||
|
{
|
||||||
|
var payload = chunk.Link;
|
||||||
|
if (payload != null && ImGui.IsItemHovered())
|
||||||
|
{
|
||||||
|
Hovered = payload;
|
||||||
|
ImGui.SetMouseCursor(ImGuiMouseCursor.Hand);
|
||||||
|
handler?.Hover(payload);
|
||||||
|
}
|
||||||
|
else if (!ReferenceEquals(Hovered, payload))
|
||||||
|
{
|
||||||
|
Hovered = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (handler == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var button in Buttons)
|
||||||
|
if (ImGui.IsItemClicked(button))
|
||||||
|
handler.Click(chunk, payload, button);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(D): real word-wrap pipeline (~220 LOC) lands in Sub-Task D.
|
||||||
|
// This forward-stub lets DrawChunk compile while keeping its body
|
||||||
|
// faithful to v1.5.6 without temporary fallback paths inside ChunkRenderer.
|
||||||
|
internal static void WrapText(
|
||||||
|
string csText,
|
||||||
|
Chunk chunk,
|
||||||
|
PayloadHandler? handler,
|
||||||
|
Vector4 defaultText,
|
||||||
|
float lineWidth
|
||||||
|
)
|
||||||
|
{
|
||||||
|
ImGui.TextUnformatted(csText);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user