ChatTwo/ChatTwo/Ui/ChatLog.cs

1017 lines
36 KiB
C#
Executable File

using System.Diagnostics;
using System.Numerics;
using System.Text;
using ChatTwo.Code;
using ChatTwo.GameFunctions.Types;
using ChatTwo.Resources;
using ChatTwo.Util;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.ClientState.Keys;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Game.Text.SeStringHandling.Payloads;
using Dalamud.Interface;
using Dalamud.Logging;
using Dalamud.Memory;
using ImGuiNET;
using ImGuiScene;
using Lumina.Excel.GeneratedSheets;
namespace ChatTwo.Ui;
internal sealed class ChatLog : IUiComponent {
private const string ChatChannelPicker = "chat-channel-picker";
internal PluginUi Ui { get; }
internal bool Activate;
internal string Chat = string.Empty;
private readonly TextureWrap? _fontIcon;
private readonly List<string> _inputBacklog = new();
private int _inputBacklogIdx = -1;
internal int LastTab { get; private set; }
private InputChannel? _tempChannel;
private TellTarget? _tellTarget;
private readonly Stopwatch _lastResize = new();
private CommandHelp? _commandHelp;
internal Vector2 LastWindowSize { get; private set; } = Vector2.Zero;
internal Vector2 LastWindowPos { get; private set; } = Vector2.Zero;
private PayloadHandler PayloadHandler { get; }
private Dictionary<string, ChatType> TextCommandChannels { get; } = new();
internal ChatLog(PluginUi ui) {
this.Ui = ui;
this.PayloadHandler = new PayloadHandler(this.Ui, this);
this.SetUpTextCommandChannels();
this.Ui.Plugin.Commands.Register("/clearlog2", "Clear the Chat 2 chat log").Execute += this.ClearLog;
this.Ui.Plugin.Commands.Register("/chat2").Execute += this.ToggleChat;
this._fontIcon = this.Ui.Plugin.DataManager.GetImGuiTexture("common/font/fonticon_ps5.tex");
this.Ui.Plugin.Functions.Chat.Activated += this.Activated;
this.Ui.Plugin.ClientState.Login += this.Login;
this.Ui.Plugin.ClientState.Logout += this.Logout;
}
public void Dispose() {
this.Ui.Plugin.ClientState.Logout -= this.Logout;
this.Ui.Plugin.ClientState.Login -= this.Login;
this.Ui.Plugin.Functions.Chat.Activated -= this.Activated;
this._fontIcon?.Dispose();
this.Ui.Plugin.Commands.Register("/chat2").Execute -= this.ToggleChat;
this.Ui.Plugin.Commands.Register("/clearlog2").Execute -= this.ClearLog;
}
private void Logout(object? sender, EventArgs e) {
foreach (var tab in this.Ui.Plugin.Config.Tabs) {
tab.Clear();
}
}
private void Login(object? sender, EventArgs e) {
this.Ui.Plugin.Store.FilterAllTabs(false);
}
private void Activated(ChatActivatedArgs args) {
this.Activate = true;
if (args.AddIfNotPresent != null && !this.Chat.Contains(args.AddIfNotPresent)) {
this.Chat += args.AddIfNotPresent;
}
if (args.Input != null) {
this.Chat += args.Input;
}
var (info, reason, target) = (args.ChannelSwitchInfo, args.TellReason, args.TellTarget);
if (info.Channel != null) {
var prevTemp = this._tempChannel;
if (info.Permanent) {
this.Ui.Plugin.Functions.Chat.SetChannel(info.Channel.Value);
} else {
this._tempChannel = info.Channel.Value;
}
if (info.Channel is InputChannel.Tell) {
if (info.Rotate != RotateMode.None) {
var idx = prevTemp != InputChannel.Tell
? 0
: info.Rotate == RotateMode.Reverse
? -1
: 1;
var tellInfo = this.Ui.Plugin.Functions.Chat.GetTellHistoryInfo(idx);
if (tellInfo != null && reason != null) {
this._tellTarget = new TellTarget(tellInfo.Name, (ushort) tellInfo.World, tellInfo.ContentId, reason.Value);
}
} else {
this._tellTarget = null;
if (target != null) {
this._tellTarget = target;
}
}
} else {
this._tellTarget = null;
}
var mode = prevTemp == null
? RotateMode.None
: info.Rotate;
if (info.Channel is InputChannel.Linkshell1 && info.Rotate != RotateMode.None) {
var idx = this.Ui.Plugin.Functions.Chat.RotateLinkshellHistory(mode);
this._tempChannel = info.Channel.Value + (uint) idx;
} else if (info.Channel is InputChannel.CrossLinkshell1 && info.Rotate != RotateMode.None) {
var idx = this.Ui.Plugin.Functions.Chat.RotateCrossLinkshellHistory(mode);
this._tempChannel = info.Channel.Value + (uint) idx;
}
}
if (info.Text != null && this.Chat.Length == 0) {
this.Chat = info.Text;
}
}
private void ClearLog(string command, string arguments) {
switch (arguments) {
case "all":
foreach (var tab in this.Ui.Plugin.Config.Tabs) {
tab.Clear();
}
break;
case "help":
this.Ui.Plugin.ChatGui.Print("- /clearlog2: clears the active tab's log");
this.Ui.Plugin.ChatGui.Print("- /clearlog2 all: clears all tabs' logs and the global history");
this.Ui.Plugin.ChatGui.Print("- /clearlog2 help: shows this help");
break;
default:
if (this.LastTab > -1 && this.LastTab < this.Ui.Plugin.Config.Tabs.Count) {
this.Ui.Plugin.Config.Tabs[this.LastTab].Clear();
}
break;
}
}
private void ToggleChat(string command, string arguments) {
var parts = arguments.Split(' ');
if (parts.Length < 2 || parts[0] != "chat") {
return;
}
switch (parts[1]) {
case "hide":
this._hideState = HideState.User;
break;
case "show":
this._hideState = HideState.None;
break;
case "toggle":
this._hideState = this._hideState switch {
HideState.User or HideState.CutsceneOverride => HideState.None,
HideState.Cutscene => HideState.CutsceneOverride,
HideState.None => HideState.User,
_ => this._hideState,
};
break;
}
}
private void SetUpTextCommandChannels() {
this.TextCommandChannels.Clear();
foreach (var input in Enum.GetValues<InputChannel>()) {
var commands = input.TextCommands(this.Ui.Plugin.DataManager);
if (commands == null) {
continue;
}
var type = input.ToChatType();
foreach (var command in commands) {
this.AddTextCommandChannel(command, type);
}
}
var echo = this.Ui.Plugin.DataManager.GetExcelSheet<TextCommand>()?.GetRow(116);
if (echo != null) {
this.AddTextCommandChannel(echo, ChatType.Echo);
}
}
private void AddTextCommandChannel(TextCommand command, ChatType type) {
this.TextCommandChannels[command.Command] = type;
this.TextCommandChannels[command.ShortCommand] = type;
this.TextCommandChannels[command.Alias] = type;
this.TextCommandChannels[command.ShortAlias] = type;
}
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);
}
private static float GetRemainingHeightForMessageLog() {
var lineHeight = ImGui.CalcTextSize("A").Y;
return ImGui.GetContentRegionAvail().Y
- lineHeight * 2
- ImGui.GetStyle().ItemSpacing.Y
- ImGui.GetStyle().FramePadding.Y * 2;
}
private unsafe ImGuiViewport* _lastViewport;
private void HandleKeybinds(bool modifiersOnly = false) {
var modifierState = (ModifierFlag) 0;
if (ImGui.GetIO().KeyAlt) {
modifierState |= ModifierFlag.Alt;
}
if (ImGui.GetIO().KeyCtrl) {
modifierState |= ModifierFlag.Ctrl;
}
if (ImGui.GetIO().KeyShift) {
modifierState |= ModifierFlag.Shift;
}
var turnedOff = new Dictionary<VirtualKey, (uint, string)>();
foreach (var (toIntercept, keybind) in this.Ui.Plugin.Functions.Chat.Keybinds) {
if (toIntercept is "CMD_CHAT" or "CMD_COMMAND") {
continue;
}
void Intercept(VirtualKey key, ModifierFlag modifier) {
var modifierPressed = this.Ui.Plugin.Config.KeybindMode switch {
KeybindMode.Strict => modifier == modifierState,
KeybindMode.Flexible => modifierState.HasFlag(modifier),
_ => false,
};
if (!ImGui.IsKeyPressed((int) key) || !modifierPressed || modifier == 0 && modifiersOnly) {
return;
}
var bits = NumUtil.NumberOfSetBits((uint) modifier);
if (!turnedOff.TryGetValue(key, out var previousBits) || previousBits.Item1 < bits) {
turnedOff[key] = (bits, toIntercept);
}
}
Intercept(keybind.Key1, keybind.Modifier1);
Intercept(keybind.Key2, keybind.Modifier2);
}
foreach (var (_, (_, keybind)) in turnedOff) {
if (!GameFunctions.Chat.KeybindsToIntercept.TryGetValue(keybind, out var info)) {
continue;
}
try {
TellReason? reason = info.Channel == InputChannel.Tell ? TellReason.Reply : null;
this.Activated(new ChatActivatedArgs(info) {
TellReason = reason,
});
} catch (Exception ex) {
PluginLog.LogError(ex, "Error in chat Activated event");
}
}
}
private bool CutsceneActive {
get {
var condition = this.Ui.Plugin.Condition;
return condition[ConditionFlag.OccupiedInCutSceneEvent]
|| condition[ConditionFlag.WatchingCutscene78];
}
}
private bool GposeActive {
get {
var condition = this.Ui.Plugin.Condition;
return condition[ConditionFlag.WatchingCutscene];
}
}
private enum HideState {
None,
Cutscene,
CutsceneOverride,
User,
}
private HideState _hideState = HideState.None;
public void Draw() {
if (this.DrawChatLog()) {
this._commandHelp?.Draw();
}
}
/// <returns>true if window was rendered</returns>
private unsafe bool DrawChatLog() {
// if the chat has no hide state and in a cutscene, set the hide state to cutscene
if (this.Ui.Plugin.Config.HideDuringCutscenes && this._hideState == HideState.None && (this.CutsceneActive || this.GposeActive)) {
this._hideState = HideState.Cutscene;
}
// if the chat is hidden because of a cutscene and no longer in a cutscene, set the hide state to none
if (this._hideState is HideState.Cutscene or HideState.CutsceneOverride && !this.CutsceneActive && !this.GposeActive) {
this._hideState = HideState.None;
}
// if the chat is hidden because of a cutscene and the chat has been activated, show chat
if (this._hideState == HideState.Cutscene && this.Activate) {
this._hideState = HideState.CutsceneOverride;
}
// if the user hid the chat and is now activating chat, reset the hide state
if (this._hideState == HideState.User && this.Activate) {
this._hideState = HideState.None;
}
if (this._hideState is HideState.Cutscene or HideState.User) {
return false;
}
if (this.Ui.Plugin.Config.HideWhenNotLoggedIn && !this.Ui.Plugin.ClientState.IsLoggedIn) {
return false;
}
var flags = ImGuiWindowFlags.None;
if (!this.Ui.Plugin.Config.CanMove) {
flags |= ImGuiWindowFlags.NoMove;
}
if (!this.Ui.Plugin.Config.CanResize) {
flags |= ImGuiWindowFlags.NoResize;
}
if (!this.Ui.Plugin.Config.ShowTitleBar) {
flags |= ImGuiWindowFlags.NoTitleBar;
}
if (this._lastViewport == ImGuiHelpers.MainViewport.NativePtr) {
ImGui.SetNextWindowBgAlpha(this.Ui.Plugin.Config.WindowAlpha);
}
ImGui.SetNextWindowSize(new Vector2(500, 250) * ImGuiHelpers.GlobalScale, ImGuiCond.FirstUseEver);
if (!ImGui.Begin($"{this.Ui.Plugin.Name}###chat2", flags)) {
this._lastViewport = ImGui.GetWindowViewport().NativePtr;
ImGui.End();
return false;
}
var resized = this.LastWindowSize != ImGui.GetWindowSize();
this.LastWindowSize = ImGui.GetWindowSize();
this.LastWindowPos = ImGui.GetWindowPos();
if (resized) {
this._lastResize.Restart();
}
this._lastViewport = ImGui.GetWindowViewport().NativePtr;
var currentTab = this.Ui.Plugin.Config.SidebarTabView
? this.DrawTabSidebar()
: this.DrawTabBar();
if (this.Activate) {
ImGui.SetKeyboardFocusHere();
}
Tab? activeTab = null;
if (currentTab > -1 && currentTab < this.Ui.Plugin.Config.Tabs.Count) {
activeTab = this.Ui.Plugin.Config.Tabs[currentTab];
}
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
try {
if (this._tellTarget != null) {
var world = this.Ui.Plugin.DataManager.GetExcelSheet<World>()
?.GetRow(this._tellTarget.World)
?.Name
?.RawString ?? "???";
this.DrawChunks(new Chunk[] {
new TextChunk(ChunkSource.None, null, "Tell "),
new TextChunk(ChunkSource.None, null, this._tellTarget.Name),
new IconChunk(ChunkSource.None, null, BitmapFontIcon.CrossWorld),
new TextChunk(ChunkSource.None, null, world),
});
} else if (this._tempChannel != null) {
if (this._tempChannel.Value.IsLinkshell()) {
var idx = (uint) this._tempChannel.Value - (uint) InputChannel.Linkshell1;
var lsName = this.Ui.Plugin.Functions.Chat.GetLinkshellName(idx);
ImGui.TextUnformatted($"LS #{idx + 1}: {lsName}");
} else if (this._tempChannel.Value.IsCrossLinkshell()) {
var idx = (uint) this._tempChannel.Value - (uint) InputChannel.CrossLinkshell1;
var cwlsName = this.Ui.Plugin.Functions.Chat.GetCrossLinkshellName(idx);
ImGui.TextUnformatted($"CWLS [{idx + 1}]: {cwlsName}");
} else {
ImGui.TextUnformatted(this._tempChannel.Value.ToChatType().Name());
}
} else if (activeTab is { Channel: { } channel }) {
ImGui.TextUnformatted(channel.ToChatType().Name());
} else {
this.DrawChunks(this.Ui.Plugin.Functions.Chat.Channel.name);
}
} finally {
ImGui.PopStyleVar();
}
var beforeIcon = ImGui.GetCursorPos();
if (ImGuiUtil.IconButton(FontAwesomeIcon.Comment) && activeTab is not { Channel: { } }) {
ImGui.OpenPopup(ChatChannelPicker);
}
if (activeTab is { Channel: { } } && ImGui.IsItemHovered()) {
ImGui.BeginTooltip();
ImGui.TextUnformatted(Language.ChatLog_SwitcherDisabled);
ImGui.EndTooltip();
}
if (ImGui.BeginPopup(ChatChannelPicker)) {
foreach (var channel in Enum.GetValues<InputChannel>()) {
var name = this.Ui.Plugin.DataManager.GetExcelSheet<LogFilter>()!
.FirstOrDefault(row => row.LogKind == (byte) channel.ToChatType())
?.Name
?.RawString ?? channel.ToString();
if (ImGui.Selectable(name)) {
this.Ui.Plugin.Functions.Chat.SetChannel(channel);
this._tellTarget = null;
}
}
ImGui.EndPopup();
}
ImGui.SameLine();
var afterIcon = ImGui.GetCursorPos();
var buttonWidth = afterIcon.X - beforeIcon.X;
var showNovice = this.Ui.Plugin.Config.ShowNoviceNetwork && this.Ui.Plugin.Functions.IsMentor();
var inputWidth = ImGui.GetContentRegionAvail().X - buttonWidth * (showNovice ? 2 : 1);
var inputType = this._tempChannel?.ToChatType() ?? activeTab?.Channel?.ToChatType() ?? this.Ui.Plugin.Functions.Chat.Channel.channel.ToChatType();
if (this.Chat.Trim().StartsWith('/')) {
var command = this.Chat.Split(' ')[0];
if (this.TextCommandChannels.TryGetValue(command, out var channel)) {
inputType = channel;
}
}
var normalColour = *ImGui.GetStyleColorVec4(ImGuiCol.Text);
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(inputWidth);
const ImGuiInputTextFlags inputFlags = ImGuiInputTextFlags.EnterReturnsTrue
| ImGuiInputTextFlags.CallbackAlways
| ImGuiInputTextFlags.CallbackCharFilter
| 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;
if (!trimmed.StartsWith('/')) {
if (this._tellTarget != null) {
var target = this._tellTarget;
var reason = target.Reason;
var world = this.Ui.Plugin.DataManager.GetExcelSheet<World>()?.GetRow(target.World);
if (world is { IsPublic: true }) {
if (reason == TellReason.Reply && this.Ui.Plugin.Common.Functions.FriendList.List.Any(friend => friend.ContentId == target.ContentId)) {
reason = TellReason.Friend;
}
this.Ui.Plugin.Functions.Chat.SendTell(reason, target.ContentId, target.Name, (ushort) world.RowId, trimmed);
}
if (this._tempChannel is InputChannel.Tell) {
this._tellTarget = null;
}
goto Skip;
}
if (this._tempChannel != null) {
trimmed = $"{this._tempChannel.Value.Prefix()} {trimmed}";
} else if (activeTab is { Channel: { } channel }) {
trimmed = $"{channel.Prefix()} {trimmed}";
}
}
this.Ui.Plugin.Common.Functions.Chat.SendMessageUnsafe(Encoding.UTF8.GetBytes(trimmed));
}
Skip:
this.Chat = string.Empty;
}
if (ImGui.IsItemActive()) {
this.HandleKeybinds(true);
}
if (!this.Activate && !ImGui.IsItemActive()) {
if (this._tempChannel is InputChannel.Tell) {
this._tellTarget = null;
}
this._tempChannel = null;
}
if (ImGui.BeginPopupContextItem()) {
ImGui.PushStyleColor(ImGuiCol.Text, normalColour);
try {
if (ImGui.Selectable(Language.ChatLog_HideChat)) {
this.UserHide();
}
} finally {
ImGui.PopStyleColor();
}
ImGui.EndPopup();
}
if (inputColour != null) {
ImGui.PopStyleColor();
}
ImGui.SameLine();
if (ImGuiUtil.IconButton(FontAwesomeIcon.Cog)) {
this.Ui.SettingsVisible ^= true;
}
if (showNovice) {
ImGui.SameLine();
if (ImGuiUtil.IconButton(FontAwesomeIcon.Leaf)) {
this.Ui.Plugin.Functions.ClickNoviceNetworkButton();
}
}
ImGui.End();
return true;
}
internal void UserHide() {
this._hideState = HideState.User;
}
private void DrawMessageLog(Tab tab, float childHeight, bool switchedTab) {
if (ImGui.BeginChild("##chat2-messages", new Vector2(-1, childHeight))) {
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
var table = tab.DisplayTimestamp && this.Ui.Plugin.Config.PrettierTimestamps;
var oldCellPaddingY = ImGui.GetStyle().CellPadding.Y;
if (this.Ui.Plugin.Config.PrettierTimestamps && this.Ui.Plugin.Config.MoreCompactPretty) {
var padding = ImGui.GetStyle().CellPadding;
padding.Y = 0;
ImGui.PushStyleVar(ImGuiStyleVar.CellPadding, padding);
}
if (table) {
if (!ImGui.BeginTable("timestamp-table", 2, ImGuiTableFlags.PreciseWidths)) {
goto EndChild;
}
ImGui.TableSetupColumn("timestamps", ImGuiTableColumnFlags.WidthFixed);
ImGui.TableSetupColumn("messages", ImGuiTableColumnFlags.WidthStretch);
}
try {
tab.MessagesMutex.WaitOne();
var reset = false;
if (this._lastResize.IsRunning && this._lastResize.Elapsed.TotalSeconds > 0.25) {
this._lastResize.Stop();
this._lastResize.Reset();
reset = true;
}
var lastPos = ImGui.GetCursorPosY();
foreach (var message in tab.Messages) {
if (reset) {
message.Height = null;
message.IsVisible = false;
}
// go to next row
if (table) {
ImGui.TableNextColumn();
}
// message has rendered once
if (message.Height.HasValue) {
// message isn't visible, so render dummy
if (!message.IsVisible) {
var beforeDummy = ImGui.GetCursorPos();
if (table) {
// skip to the message column for vis test
ImGui.TableNextColumn();
}
ImGui.Dummy(new Vector2(10f, message.Height.Value));
message.IsVisible = ImGui.IsItemVisible();
if (message.IsVisible) {
if (table) {
ImGui.TableSetColumnIndex(0);
}
ImGui.SetCursorPos(beforeDummy);
} else {
goto UpdateMessage;
}
}
}
if (tab.DisplayTimestamp) {
var timestamp = message.Date.ToLocalTime().ToString("t");
if (table) {
ImGui.TextUnformatted(timestamp);
} else {
this.DrawChunk(new TextChunk(ChunkSource.None, null, $"[{timestamp}]") {
Foreground = 0xFFFFFFFF,
});
ImGui.SameLine();
}
}
if (table) {
ImGui.TableNextColumn();
}
var lineWidth = ImGui.GetContentRegionAvail().X;
var beforeDraw = ImGui.GetCursorScreenPos();
if (message.Sender.Count > 0) {
this.DrawChunks(message.Sender, true, this.PayloadHandler, lineWidth);
ImGui.SameLine();
}
this.DrawChunks(message.Content, true, this.PayloadHandler, lineWidth);
var afterDraw = ImGui.GetCursorScreenPos();
message.Height = ImGui.GetCursorPosY() - lastPos;
if (this.Ui.Plugin.Config.PrettierTimestamps && !this.Ui.Plugin.Config.MoreCompactPretty) {
message.Height -= oldCellPaddingY * 2;
beforeDraw.Y += oldCellPaddingY;
afterDraw.Y -= oldCellPaddingY;
}
message.IsVisible = ImGui.IsRectVisible(beforeDraw, afterDraw);
UpdateMessage:
lastPos = ImGui.GetCursorPosY();
}
} finally {
tab.MessagesMutex.ReleaseMutex();
ImGui.PopStyleVar(this.Ui.Plugin.Config.PrettierTimestamps && this.Ui.Plugin.Config.MoreCompactPretty ? 2 : 1);
}
if (switchedTab || ImGui.GetScrollY() >= ImGui.GetScrollMaxY()) {
ImGui.SetScrollHereY(1f);
}
this.PayloadHandler.Draw();
if (table) {
ImGui.EndTable();
}
}
EndChild:
ImGui.EndChild();
}
private int DrawTabBar() {
var currentTab = -1;
if (!ImGui.BeginTabBar("##chat2-tabs")) {
return currentTab;
}
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.UnreadMode == UnreadMode.None || tab.Unread == 0 ? "" : $" ({tab.Unread})";
var draw = ImGui.BeginTabItem($"{tab.Name}{unread}###log-tab-{tabI}");
this.DrawTabContextMenu(tab, tabI);
if (!draw) {
continue;
}
currentTab = tabI;
var switchedTab = this.LastTab != tabI;
this.LastTab = tabI;
tab.Unread = 0;
this.DrawMessageLog(tab, GetRemainingHeightForMessageLog(), switchedTab);
ImGui.EndTabItem();
}
ImGui.EndTabBar();
return currentTab;
}
private int DrawTabSidebar() {
var currentTab = -1;
if (!ImGui.BeginTable("tabs-table", 2, ImGuiTableFlags.BordersInnerV | ImGuiTableFlags.SizingStretchProp | ImGuiTableFlags.Resizable)) {
return -1;
}
ImGui.TableSetupColumn("tabs", ImGuiTableColumnFlags.None, 1);
ImGui.TableSetupColumn("chat", ImGuiTableColumnFlags.None, 4);
ImGui.TableNextColumn();
var switchedTab = false;
var childHeight = GetRemainingHeightForMessageLog();
if (ImGui.BeginChild("##chat2-tab-sidebar", new Vector2(-1, childHeight))) {
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.UnreadMode == UnreadMode.None || tab.Unread == 0 ? "" : $" ({tab.Unread})";
var clicked = ImGui.Selectable($"{tab.Name}{unread}###log-tab-{tabI}", this.LastTab == tabI);
this.DrawTabContextMenu(tab, tabI);
if (!clicked) {
continue;
}
currentTab = tabI;
switchedTab = this.LastTab != tabI;
this.LastTab = tabI;
}
}
ImGui.EndChild();
ImGui.TableNextColumn();
if (currentTab == -1 && this.LastTab < this.Ui.Plugin.Config.Tabs.Count) {
currentTab = this.LastTab;
this.Ui.Plugin.Config.Tabs[currentTab].Unread = 0;
}
if (currentTab > -1) {
this.DrawMessageLog(this.Ui.Plugin.Config.Tabs[currentTab], childHeight, switchedTab);
}
ImGui.EndTable();
return currentTab;
}
private void DrawTabContextMenu(Tab tab, int i) {
if (!ImGui.BeginPopupContextItem()) {
return;
}
var tabs = this.Ui.Plugin.Config.Tabs;
var anyChanged = false;
ImGui.PushID($"tab-context-menu-{i}");
ImGui.SetNextItemWidth(250f * ImGuiHelpers.GlobalScale);
if (ImGui.InputText("##tab-name", ref tab.Name, 128)) {
anyChanged = true;
}
if (ImGuiUtil.IconButton(FontAwesomeIcon.TrashAlt, tooltip: Language.ChatLog_Tabs_Delete)) {
tabs.RemoveAt(i);
anyChanged = true;
}
ImGui.SameLine();
var (leftIcon, leftTooltip) = this.Ui.Plugin.Config.SidebarTabView
? (FontAwesomeIcon.ArrowUp, Language.ChatLog_Tabs_MoveUp)
: (FontAwesomeIcon.ArrowLeft, Language.ChatLog_Tabs_MoveLeft);
if (ImGuiUtil.IconButton(leftIcon, tooltip: leftTooltip) && i > 0) {
(tabs[i - 1], tabs[i]) = (tabs[i], tabs[i - 1]);
ImGui.CloseCurrentPopup();
anyChanged = true;
}
ImGui.SameLine();
var (rightIcon, rightTooltip) = this.Ui.Plugin.Config.SidebarTabView
? (FontAwesomeIcon.ArrowDown, Language.ChatLog_Tabs_MoveDown)
: (FontAwesomeIcon.ArrowRight, Language.ChatLog_Tabs_MoveRight);
if (ImGuiUtil.IconButton(rightIcon, tooltip: rightTooltip) && i < tabs.Count - 1) {
(tabs[i + 1], tabs[i]) = (tabs[i], tabs[i + 1]);
ImGui.CloseCurrentPopup();
anyChanged = true;
}
if (anyChanged) {
this.Ui.Plugin.SaveConfig();
}
ImGui.PopID();
ImGui.EndPopup();
}
private unsafe int Callback(ImGuiInputTextCallbackData* data) {
var ptr = new ImGuiInputTextCallbackDataPtr(data);
if (data->EventFlag == ImGuiInputTextFlags.CallbackCharFilter) {
var valid = this.Ui.Plugin.Functions.Chat.IsCharValid((char) ptr.EventChar);
if (!valid) {
return 1;
}
}
if (this.Activate) {
this.Activate = false;
data->CursorPos = this.Chat.Length;
data->SelectionStart = data->SelectionEnd = data->CursorPos;
}
var text = MemoryHelper.ReadString((IntPtr) data->Buf, data->BufTextLen);
if (text.StartsWith('/')) {
var command = text.Split(' ')[0];
var cmd = this.Ui.Plugin.DataManager.GetExcelSheet<TextCommand>()?.FirstOrDefault(cmd => cmd.Command.RawString == command
|| cmd.Alias.RawString == command
|| cmd.ShortCommand.RawString == command
|| cmd.ShortAlias.RawString == command);
if (cmd != null) {
this._commandHelp = new CommandHelp(this, cmd);
goto PostCommandHelp;
}
}
this._commandHelp = null;
PostCommandHelp:
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;
}
internal void DrawChunks(IReadOnlyList<Chunk> chunks, bool wrap = true, PayloadHandler? handler = null, float lineWidth = 0f) {
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
try {
for (var i = 0; i < chunks.Count; i++) {
if (chunks[i] is TextChunk text && string.IsNullOrEmpty(text.Content)) {
continue;
}
this.DrawChunk(chunks[i], wrap, handler, lineWidth);
if (i < chunks.Count - 1) {
ImGui.SameLine();
}
}
} finally {
ImGui.PopStyleVar();
}
}
private void DrawChunk(Chunk chunk, bool wrap = true, PayloadHandler? handler = null, float lineWidth = 0f) {
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 * ImGuiHelpers.GlobalScale;
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);
ImGuiUtil.PostPayload(chunk, handler);
}
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);
}
var content = text.Content;
if (this.Ui.ScreenshotMode) {
if (chunk.Link is PlayerPayload playerPayload) {
var hashCode = $"{this.Ui.Salt}{playerPayload.PlayerName}{playerPayload.World.RowId}".GetHashCode();
content = $"Player {hashCode:X8}";
} else if (this.Ui.Plugin.ClientState.LocalPlayer is { } player && content.Contains(player.Name.TextValue)) {
var hashCode = $"{this.Ui.Salt}{player.Name.TextValue}{player.HomeWorld.Id}".GetHashCode();
content = content.Replace(player.Name.TextValue, $"Player {hashCode:X8}");
}
}
if (wrap) {
ImGuiUtil.WrapText(content, chunk, handler, this.Ui.DefaultText, lineWidth);
} else {
ImGui.TextUnformatted(content);
ImGuiUtil.PostPayload(chunk, handler);
}
if (text.Italic && this.Ui.ItalicFont.HasValue) {
ImGui.PopFont();
}
if (colour != null) {
ImGui.PopStyleColor();
}
}
}