From 63f5a28834b05060680580af68b7e72eaf774e65 Mon Sep 17 00:00:00 2001 From: Jon Kazama Date: Wed, 27 May 2026 12:19:50 +0200 Subject: [PATCH] feat(payload-handler): wire DrawPopups + Integrations/ContextFooter/StringifyMessage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit E2 fills the popup-dispatch layer of PayloadHandler: - DrawPopups: switch-dispatch over payload types, with TODO(E3)/(E4) markers at the deferred Draw{Player,Item,Status,Uri}Popup call sites - Integrations: invokes registered IPC integrations (LogWindow.Plugin.Ipc -> _ipc substitution per §4.2) - ContextFooter: ScreenshotMode + HideChat checkboxes (Plugin.Config static-bridge substitutions per §4.2) - StringifyMessage: pure helper, 1:1 from v1.5.6 Adds PopupSfx const (E1 polish — needed by E5's Click for UIGlobals.PlaySoundEffect). Removes #pragma CS0169 for _popup since DrawPopups now writes the field; the warning no longer triggers. E3 will fill DrawPlayerPopup + FindCharacterForPayload; E4 the Item/Status/Uri popups; E5 the Hover/Click bodies; E6 MoveTooltip. --- HellionChat/PayloadHandler.cs | 163 +++++++++++++++++++++++++++++++++- 1 file changed, 160 insertions(+), 3 deletions(-) diff --git a/HellionChat/PayloadHandler.cs b/HellionChat/PayloadHandler.cs index aebf615..72fe226 100644 --- a/HellionChat/PayloadHandler.cs +++ b/HellionChat/PayloadHandler.cs @@ -1,8 +1,15 @@ +using System.Linq; using Dalamud.Bindings.ImGui; using Dalamud.Game.Text.SeStringHandling; +using Dalamud.Game.Text.SeStringHandling.Payloads; +using Dalamud.Interface.ImGuiNotification; +using Dalamud.Interface.Utility.Raii; +using HellionChat.Code; +using HellionChat.Resources; using HellionChat.Themes; using HellionChat.Ui.Components; using HellionChat.Ui.Windows; +using HellionChat.Util; using Microsoft.Extensions.Logging; namespace HellionChat; @@ -10,6 +17,7 @@ namespace HellionChat; internal sealed class PayloadHandler { private const string PopupId = "hellionchat-context-popup"; + private const uint PopupSfx = 1; private readonly ThemeRegistry _themes; private readonly IpcManager _ipc; @@ -24,9 +32,7 @@ internal sealed class PayloadHandler public uint HoverCounter; public uint LastHoverCounter; -#pragma warning disable CS0169 // used in DrawPopups (E2) private (Chunk, Payload?)? _popup; -#pragma warning restore CS0169 public PayloadHandler( ThemeRegistry themes, @@ -49,7 +55,7 @@ internal sealed class PayloadHandler internal void Draw() { - // TODO(E2): DrawPopups(); + DrawPopups(); if (HandleTooltips && ++HoverCounter - LastHoverCounter > 1) { @@ -60,6 +66,157 @@ internal sealed class PayloadHandler } } + private void DrawPopups() + { + if (_popup == null) + return; + + var (chunk, payload) = _popup.Value; + + using var popup = ImRaii.Popup(PopupId); + if (!popup.Success) + { + _popup = null; + return; + } + + using var id = ImRaii.PushId(PopupId); + var drawn = false; + switch (payload) + { + case PlayerPayload player: + // TODO(E3): DrawPlayerPopup(chunk, player); + drawn = true; + break; + case ItemPayload item: + // TODO(E4): DrawItemPopup(item); + drawn = true; + break; + case UriPayload uri: + // TODO(E4): DrawUriPopup(uri); + drawn = true; + break; + case StatusPayload status: + // TODO(E4): DrawStatusPopup(status); + drawn = true; + break; + } + + ContextFooter(drawn, chunk); + Integrations(chunk, payload); + } + + private void Integrations(Chunk chunk, Payload? payload) + { + var registered = _ipc.Registered; + if (registered.Count == 0) + return; + + ImGui.Separator(); + + var contentId = chunk.Message?.ContentId ?? 0; + var sender = + chunk.Message?.Sender.Select(c => c.Link).FirstOrDefault(p => p is PlayerPayload) + as PlayerPayload; + + using var menu = ImRaii.Menu(Language.Context_Integrations); + if (!menu.Success) + return; + + var cursor = ImGui.GetCursorPos(); + foreach (var integrationId in registered) + { + try + { + _ipc.Invoke( + integrationId, + sender, + contentId, + payload, + chunk.Message?.SenderSource, + chunk.Message?.ContentSource + ); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error executing integration"); + } + } + + if (cursor == ImGui.GetCursorPos()) + { + using var pushedColor = ImRaii.PushColor( + ImGuiCol.Text, + ImGui.GetStyle().Colors[(int)ImGuiCol.TextDisabled] + ); + ImGui.Text("No integrations available"); + } + } + + private void ContextFooter(bool didCustomContext, Chunk chunk) + { + ImRaii.MenuDisposable menu = default; + if (didCustomContext) + { + ImGui.Separator(); + + // Only place these menu items in a submenu if we've already drawn + // custom context menu items based on the payload. + // + // It makes it much more convenient in the majority of cases to + // copy the message content without having to open a submenu. + menu = ImRaii.Menu(Plugin.PluginName); + if (!menu.Success) + return; + } + + ImGui.Checkbox(Language.Context_ScreenshotMode, ref Plugin.Config.ScreenshotMode); + + if (ImGui.Selectable(Language.Context_HideChat)) + Plugin.Config.HideChat = true; + + if (chunk.Message is { } message) + { + if (ImGui.Selectable(Language.Context_Copy)) + { + ImGui.SetClipboardText(StringifyMessage(message, true)); + WrapperUtil.AddNotification(Language.Context_CopySuccess, NotificationType.Info); + } + + // Only show a separate "Copy content" option if the message has + // Sender chunks, so it doesn't show for system messages. + if (message.Sender.Count > 0 && ImGui.Selectable(Language.Context_CopyContent)) + { + ImGui.SetClipboardText(StringifyMessage(message)); + WrapperUtil.AddNotification( + Language.Context_CopyContentSuccess, + NotificationType.Info + ); + } + + using var pushedColor = ImRaii.PushColor( + ImGuiCol.Text, + ImGui.GetStyle().Colors[(int)ImGuiCol.TextDisabled] + ); + ImGui.TextUnformatted(message.Code.Type.Name()); + } + + menu.Dispose(); + } + + private static string StringifyMessage(Message? message, bool withSender = false) + { + if (message == null) + return string.Empty; + + var chunks = withSender ? message.Sender.Concat(message.Content) : message.Content; + return chunks + .Where(chunk => chunk is TextChunk) + .Cast() + .Select(text => text.Content) + .Aggregate(string.Concat); + } + internal void Hover(Payload payload) { } internal unsafe void Click(Chunk chunk, Payload? payload, ImGuiMouseButton button) { }