using System.Numerics; using ChatTwo.Code; using ChatTwo.Util; using ImGuiNET; using ImGuiScene; namespace ChatTwo.Ui; internal sealed class ChatLog : IUiComponent { private PluginUi Ui { get; } private bool _activate; private string _chat = string.Empty; private readonly TextureWrap? _fontIcon; private readonly List _inputBacklog = new(); private int _inputBacklogIdx = -1; private int _lastTab; internal ChatLog(PluginUi ui) { this.Ui = ui; this._fontIcon = this.Ui.Plugin.DataManager.GetImGuiTexture("common/font/fonticon_ps5.tex"); this.Ui.Plugin.Functions.ChatActivated += this.ChatActivated; } public void Dispose() { this.Ui.Plugin.Functions.ChatActivated -= this.ChatActivated; this._fontIcon?.Dispose(); } private void ChatActivated(string? input) { this._activate = true; if (input != null && !this._chat.Contains(input)) { this._chat += input; } } private void AddBacklog(string message) { for (var i = 0; i < this._inputBacklog.Count; i++) { if (this._inputBacklog[i] != message) { continue; } this._inputBacklog.RemoveAt(i); break; } this._inputBacklog.Add(message); } public unsafe void Draw() { if (!ImGui.Begin($"{this.Ui.Plugin.Name}##chat", ImGuiWindowFlags.NoTitleBar)) { ImGui.End(); return; } var lineHeight = ImGui.CalcTextSize("A").Y; if (ImGui.BeginTabBar("##chat2-tabs")) { for (var tabI = 0; tabI < this.Ui.Plugin.Config.Tabs.Count; tabI++) { var tab = this.Ui.Plugin.Config.Tabs[tabI]; var unread = tabI == this._lastTab || !tab.DisplayUnread || tab.Unread == 0 ? "" : $" ({tab.Unread})"; if (ImGui.BeginTabItem($"{tab.Name}{unread}###log-tab-{tabI}")) { var switchedTab = this._lastTab != tabI; this._lastTab = tabI; tab.Unread = 0; // var drawnHeight = 0f; // var numDrawn = 0; // var lastPos = ImGui.GetCursorPosY(); var height = ImGui.GetContentRegionAvail().Y - lineHeight * 2 - ImGui.GetStyle().ItemSpacing.Y * 4; if (ImGui.BeginChild("##chat2-messages", new Vector2(-1, height))) { ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero); // var clipper = new ImGuiListClipperPtr(ImGuiNative.ImGuiListClipper_ImGuiListClipper()); // int numMessages; try { tab.MessagesMutex.WaitOne(); for (var i = 0; i < tab.Messages.Count; i++) { // numDrawn += 1; var message = tab.Messages[i]; if (tab.DisplayTimestamp) { var timestamp = message.Date.ToLocalTime().ToString("t"); this.DrawChunk(new TextChunk($"[{timestamp}]") { Foreground = 0xFFFFFFFF, }); ImGui.SameLine(); } if (message.Sender.Count > 0) { this.DrawChunks(message.Sender); ImGui.SameLine(); } this.DrawChunks(message.Content); // drawnHeight += ImGui.GetCursorPosY() - lastPos; // lastPos = ImGui.GetCursorPosY(); } // numMessages = tab.Messages.Count; // may render too many items, but this is easier // clipper.Begin(numMessages, lineHeight + ImGui.GetStyle().ItemSpacing.Y); // while (clipper.Step()) { // } } finally { tab.MessagesMutex.ReleaseMutex(); ImGui.PopStyleVar(); } // PluginLog.Log($"numDrawn: {numDrawn}"); if (switchedTab || ImGui.GetScrollY() >= ImGui.GetScrollMaxY()) { // PluginLog.Log($"drawnHeight: {drawnHeight}"); // var itemPosY = clipper.StartPosY + drawnHeight; // PluginLog.Log($"itemPosY: {itemPosY}"); // ImGui.SetScrollFromPosY(itemPosY - ImGui.GetWindowPos().Y); ImGui.SetScrollHereY(1f); } } ImGui.EndChild(); ImGui.EndTabItem(); } } ImGui.EndTabBar(); } if (this._activate) { ImGui.SetKeyboardFocusHere(); } ImGui.TextUnformatted(this.Ui.Plugin.Functions.ChatChannel.name); var inputType = this.Ui.Plugin.Functions.ChatChannel.channel.ToChatType(); var inputColour = this.Ui.Plugin.Config.ChatColours.TryGetValue(inputType, out var inputCol) ? inputCol : inputType.DefaultColour(); if (inputColour != null) { ImGui.PushStyleColor(ImGuiCol.Text, ColourUtil.RgbaToAbgr(inputColour.Value)); } ImGui.SetNextItemWidth(-1); const ImGuiInputTextFlags inputFlags = ImGuiInputTextFlags.EnterReturnsTrue | ImGuiInputTextFlags.CallbackAlways | ImGuiInputTextFlags.CallbackHistory; if (ImGui.InputText("##chat2-input", ref this._chat, 500, inputFlags, this.Callback)) { if (!string.IsNullOrWhiteSpace(this._chat)) { var trimmed = this._chat.Trim(); this.AddBacklog(trimmed); this._inputBacklogIdx = -1; this.Ui.Plugin.Common.Functions.Chat.SendMessage(trimmed); } this._chat = string.Empty; } if (inputColour != null) { ImGui.PopStyleColor(); } ImGui.End(); } private unsafe int Callback(ImGuiInputTextCallbackData* data) { var ptr = new ImGuiInputTextCallbackDataPtr(data); if (this._activate) { this._activate = false; data->CursorPos = this._chat.Length; data->SelectionStart = data->SelectionEnd = data->CursorPos; } if (data->EventFlag != ImGuiInputTextFlags.CallbackHistory) { return 0; } var prevPos = this._inputBacklogIdx; switch (data->EventKey) { case ImGuiKey.UpArrow: switch (this._inputBacklogIdx) { case -1: var offset = 0; if (!string.IsNullOrWhiteSpace(this._chat)) { this.AddBacklog(this._chat); offset = 1; } this._inputBacklogIdx = this._inputBacklog.Count - 1 - offset; break; case > 0: this._inputBacklogIdx--; break; } break; case ImGuiKey.DownArrow: { if (this._inputBacklogIdx != -1) { if (++this._inputBacklogIdx >= this._inputBacklog.Count) { this._inputBacklogIdx = -1; } } break; } } if (prevPos == this._inputBacklogIdx) { return 0; } var historyStr = this._inputBacklogIdx >= 0 ? this._inputBacklog[this._inputBacklogIdx] : string.Empty; ptr.DeleteChars(0, ptr.BufTextLen); ptr.InsertChars(0, historyStr); return 0; } private void DrawChunks(IReadOnlyList chunks) { for (var i = 0; i < chunks.Count; i++) { this.DrawChunk(chunks[i]); if (i < chunks.Count - 1) { ImGui.SameLine(); } } } private void DrawChunk(Chunk chunk) { if (chunk is IconChunk icon && this._fontIcon != null) { var bounds = IconUtil.GetBounds((byte) icon.Icon); if (bounds != null) { var texSize = new Vector2(this._fontIcon.Width, this._fontIcon.Height); var sizeRatio = this.Ui.Plugin.Config.FontSize / bounds.Value.W; var size = new Vector2(bounds.Value.Z, bounds.Value.W) * sizeRatio; var uv0 = new Vector2(bounds.Value.X, bounds.Value.Y - 2) / texSize; var uv1 = new Vector2(bounds.Value.X + bounds.Value.Z, bounds.Value.Y - 2 + bounds.Value.W) / texSize; ImGui.Image(this._fontIcon.ImGuiHandle, size, uv0, uv1); } return; } if (chunk is not TextChunk text) { return; } var colour = text.Foreground; if (colour == null && text.FallbackColour != null) { var type = text.FallbackColour.Value; colour = this.Ui.Plugin.Config.ChatColours.TryGetValue(type, out var col) ? col : type.DefaultColour(); } if (colour != null) { colour = ColourUtil.RgbaToAbgr(colour.Value); ImGui.PushStyleColor(ImGuiCol.Text, colour.Value); } if (text.Italic && this.Ui.ItalicFont.HasValue) { ImGui.PushFont(this.Ui.ItalicFont.Value); } ImGuiUtil.WrapText(text.Content); if (text.Italic && this.Ui.ItalicFont.HasValue) { ImGui.PopFont(); } if (colour != null) { ImGui.PopStyleColor(); } } }