chore: initial commit
This commit is contained in:
Executable
+290
@@ -0,0 +1,290 @@
|
||||
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<string> _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<Chunk> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+185
@@ -0,0 +1,185 @@
|
||||
using System.Numerics;
|
||||
using ChatTwo.Code;
|
||||
using ChatTwo.Util;
|
||||
using Dalamud.Game.Command;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace ChatTwo.Ui;
|
||||
|
||||
internal sealed class Settings : IUiComponent {
|
||||
private PluginUi Ui { get; }
|
||||
|
||||
private bool _visible;
|
||||
|
||||
private bool _hideChat;
|
||||
private float _fontSize;
|
||||
private Dictionary<ChatType, uint> _chatColours = new();
|
||||
private List<Tab> _tabs = new();
|
||||
|
||||
internal Settings(PluginUi ui) {
|
||||
this.Ui = ui;
|
||||
this.Ui.Plugin.CommandManager.AddHandler("/chat2", new CommandInfo(this.Command) {
|
||||
HelpMessage = "Toggle the Chat 2 settings",
|
||||
});
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
this.Ui.Plugin.CommandManager.RemoveHandler("/chat2");
|
||||
}
|
||||
|
||||
private void Command(string command, string args) {
|
||||
this._visible ^= true;
|
||||
}
|
||||
|
||||
private void Initialise() {
|
||||
var config = this.Ui.Plugin.Config;
|
||||
this._hideChat = config.HideChat;
|
||||
this._fontSize = config.FontSize;
|
||||
this._chatColours = config.ChatColours.ToDictionary(entry => entry.Key, entry => entry.Value);
|
||||
this._tabs = config.Tabs.Select(tab => tab.Clone()).ToList();
|
||||
}
|
||||
|
||||
public void Draw() {
|
||||
if (!this._visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ImGui.Begin($"{this.Ui.Plugin.Name} settings", ref this._visible)) {
|
||||
ImGui.End();
|
||||
return;
|
||||
}
|
||||
|
||||
if (ImGui.IsWindowAppearing()) {
|
||||
this.Initialise();
|
||||
}
|
||||
|
||||
var height = ImGui.GetContentRegionAvail().Y
|
||||
- ImGui.GetStyle().FramePadding.Y * 2
|
||||
- ImGui.GetStyle().ItemSpacing.Y
|
||||
- ImGui.GetStyle().ItemInnerSpacing.Y * 2
|
||||
- ImGui.CalcTextSize("A").Y;
|
||||
if (ImGui.BeginChild("##chat2-settings", new Vector2(-1, height))) {
|
||||
ImGui.Checkbox("Hide chat", ref this._hideChat);
|
||||
ImGui.DragFloat("Font size", ref this._fontSize, .5f, 12f, 36f);
|
||||
|
||||
if (ImGui.TreeNodeEx("Chat colours")) {
|
||||
foreach (var type in Enum.GetValues<ChatType>()) {
|
||||
if (ImGui.Button($"Default##{type}")) {
|
||||
this._chatColours.Remove(type);
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
var vec = this._chatColours.TryGetValue(type, out var colour)
|
||||
? ColourUtil.RgbaToVector3(colour)
|
||||
: ColourUtil.RgbaToVector3(type.DefaultColour() ?? 0);
|
||||
if (ImGui.ColorEdit3(type.Name(), ref vec, ImGuiColorEditFlags.NoInputs)) {
|
||||
this._chatColours[type] = ColourUtil.Vector3ToRgba(vec);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.TreePop();
|
||||
}
|
||||
|
||||
if (ImGui.TreeNodeEx("Tabs")) {
|
||||
if (ImGui.Button("Add")) {
|
||||
this._tabs.Add(new Tab());
|
||||
}
|
||||
|
||||
for (var i = 0; i < this._tabs.Count; i++) {
|
||||
var tab = this._tabs[i];
|
||||
|
||||
if (ImGui.TreeNodeEx($"{tab.Name}###tab-{i}")) {
|
||||
ImGui.PushID($"tab-{i}");
|
||||
|
||||
ImGui.InputText("Name", ref tab.Name, 512, ImGuiInputTextFlags.EnterReturnsTrue);
|
||||
ImGui.Checkbox("Show unread count", ref tab.DisplayUnread);
|
||||
ImGui.Checkbox("Show timestamps", ref tab.DisplayTimestamp);
|
||||
|
||||
if (ImGui.TreeNodeEx("Channels")) {
|
||||
foreach (var type in Enum.GetValues<ChatType>()) {
|
||||
var enabled = tab.ChatCodes.ContainsKey(type);
|
||||
if (ImGui.Checkbox($"##{type.Name()}-{i}", ref enabled)) {
|
||||
if (enabled) {
|
||||
tab.ChatCodes[type] = ChatSourceExt.All;
|
||||
} else {
|
||||
tab.ChatCodes.Remove(type);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImGui.TreeNodeEx($"{type.Name()}##{i}")) {
|
||||
tab.ChatCodes.TryGetValue(type, out var sourcesEnum);
|
||||
var sources = (uint) sourcesEnum;
|
||||
|
||||
foreach (var source in Enum.GetValues<ChatSource>()) {
|
||||
if (ImGui.CheckboxFlags(source.ToString(), ref sources, (uint) source)) {
|
||||
tab.ChatCodes[type] = (ChatSource) sources;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.TreePop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ImGui.TreePop();
|
||||
}
|
||||
|
||||
ImGui.TreePop();
|
||||
|
||||
ImGui.PopID();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.EndChild();
|
||||
}
|
||||
|
||||
ImGui.Separator();
|
||||
|
||||
var save = ImGui.Button("Save");
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImGui.Button("Save and close")) {
|
||||
save = true;
|
||||
this._visible = false;
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImGui.Button("Discard")) {
|
||||
this._visible = false;
|
||||
}
|
||||
|
||||
ImGui.End();
|
||||
|
||||
if (save) {
|
||||
var config = this.Ui.Plugin.Config;
|
||||
|
||||
var hideChatChanged = this._hideChat != this.Ui.Plugin.Config.HideChat;
|
||||
var fontSizeChanged = Math.Abs(this._fontSize - this.Ui.Plugin.Config.FontSize) > float.Epsilon;
|
||||
|
||||
config.HideChat = this._hideChat;
|
||||
config.FontSize = this._fontSize;
|
||||
config.ChatColours = this._chatColours;
|
||||
config.Tabs = this._tabs;
|
||||
|
||||
this.Ui.Plugin.SaveConfig();
|
||||
|
||||
this.Ui.Plugin.Store.FilterAllTabs();
|
||||
|
||||
if (fontSizeChanged) {
|
||||
this.Ui.Plugin.Interface.UiBuilder.RebuildFonts();
|
||||
}
|
||||
|
||||
if (!this._hideChat && hideChatChanged) {
|
||||
this.Ui.Plugin.Functions.SetChatInteractable(true);
|
||||
}
|
||||
|
||||
this.Initialise();
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
namespace ChatTwo.Ui;
|
||||
|
||||
internal interface IUiComponent : IDisposable {
|
||||
void Draw();
|
||||
}
|
||||
Reference in New Issue
Block a user