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) { }