c3c60e7e43
After not receiving a message for X seconds (configurable) in the current tab or any tab with unread mode enabled, the chat will be hidden. Focus can be returned with return or slash as usual. Having input focus or hovering the mouse over the chat window "bumps" it every frame. Also fixes a bug that prevented focus from being restored to tabs with input disabled. The chat window will be brought back but the activated event won't be fully processed. Co-authored-by: Auri <me@aurieh.me>
533 lines
17 KiB
C#
Executable File
533 lines
17 KiB
C#
Executable File
using System.Collections;
|
|
using ChatTwo.Code;
|
|
using ChatTwo.Resources;
|
|
using ChatTwo.Ui;
|
|
using Dalamud.Configuration;
|
|
using ImGuiNET;
|
|
|
|
namespace ChatTwo;
|
|
|
|
[Serializable]
|
|
internal class Configuration : IPluginConfiguration
|
|
{
|
|
private const int LatestVersion = 5;
|
|
|
|
public int Version { get; set; } = LatestVersion;
|
|
|
|
public bool HideChat = true;
|
|
public bool HideDuringCutscenes = true;
|
|
public bool HideWhenNotLoggedIn = true;
|
|
public bool HideWhenUiHidden = true;
|
|
public bool HideInLoadingScreens;
|
|
public bool HideInBattle;
|
|
public bool HideWhenInactive;
|
|
public int InactivityHideTimeout = 10;
|
|
public bool ShowHideButton = true;
|
|
public bool NativeItemTooltips = true;
|
|
public bool PrettierTimestamps = true;
|
|
public bool MoreCompactPretty;
|
|
public bool HideSameTimestamps;
|
|
public bool ShowNoviceNetwork;
|
|
public bool SidebarTabView;
|
|
public bool PrintChangelog = true;
|
|
public bool OnlyPreviewIf;
|
|
public int PreviewMinimum = 1;
|
|
public PreviewPosition PreviewPosition = PreviewPosition.Inside;
|
|
public CommandHelpSide CommandHelpSide = CommandHelpSide.None;
|
|
public KeybindMode KeybindMode = KeybindMode.Strict;
|
|
public LanguageOverride LanguageOverride = LanguageOverride.None;
|
|
public bool CanMove = true;
|
|
public bool CanResize = true;
|
|
public bool ShowTitleBar;
|
|
public bool ShowPopOutTitleBar = true;
|
|
public bool DatabaseBattleMessages;
|
|
public bool LoadPreviousSession;
|
|
public bool FilterIncludePreviousSessions;
|
|
public bool SortAutoTranslate;
|
|
public bool CollapseDuplicateMessages;
|
|
public bool PlaySounds = true;
|
|
public bool KeepInputFocus = true;
|
|
public int MaxLinesToRender = 10_000;
|
|
|
|
public bool ShowEmotes = true;
|
|
public HashSet<string> BlockedEmotes = [];
|
|
|
|
public bool FontsEnabled = true;
|
|
public ExtraGlyphRanges ExtraGlyphRanges = 0;
|
|
public float FontSize = 17f;
|
|
public float JapaneseFontSize = 17f;
|
|
public float SymbolsFontSize = 17f;
|
|
public string GlobalFont = Fonts.GlobalFonts[0].Name;
|
|
public string JapaneseFont = Fonts.JapaneseFonts[0].Item1;
|
|
|
|
public float TooltipOffset;
|
|
public float WindowAlpha = 100f;
|
|
public Dictionary<ChatType, uint> ChatColours = new();
|
|
public List<Tab> Tabs = new();
|
|
|
|
public bool OverrideStyle;
|
|
public string? ChosenStyle;
|
|
|
|
internal void UpdateFrom(Configuration other, bool backToOriginal)
|
|
{
|
|
if (backToOriginal)
|
|
foreach (var tab in Tabs.Where(t => t.PopOut))
|
|
tab.PopOut = false;
|
|
|
|
HideChat = other.HideChat;
|
|
HideDuringCutscenes = other.HideDuringCutscenes;
|
|
HideWhenNotLoggedIn = other.HideWhenNotLoggedIn;
|
|
HideWhenUiHidden = other.HideWhenUiHidden;
|
|
HideInLoadingScreens = other.HideInLoadingScreens;
|
|
HideInBattle = other.HideInBattle;
|
|
HideWhenInactive = other.HideWhenInactive;
|
|
InactivityHideTimeout = other.InactivityHideTimeout;
|
|
ShowHideButton = other.ShowHideButton;
|
|
NativeItemTooltips = other.NativeItemTooltips;
|
|
PrettierTimestamps = other.PrettierTimestamps;
|
|
MoreCompactPretty = other.MoreCompactPretty;
|
|
HideSameTimestamps = other.HideSameTimestamps;
|
|
ShowNoviceNetwork = other.ShowNoviceNetwork;
|
|
SidebarTabView = other.SidebarTabView;
|
|
PrintChangelog = other.PrintChangelog;
|
|
OnlyPreviewIf = other.OnlyPreviewIf;
|
|
PreviewMinimum = other.PreviewMinimum;
|
|
PreviewPosition = other.PreviewPosition;
|
|
CommandHelpSide = other.CommandHelpSide;
|
|
KeybindMode = other.KeybindMode;
|
|
LanguageOverride = other.LanguageOverride;
|
|
CanMove = other.CanMove;
|
|
CanResize = other.CanResize;
|
|
ShowTitleBar = other.ShowTitleBar;
|
|
ShowPopOutTitleBar = other.ShowPopOutTitleBar;
|
|
DatabaseBattleMessages = other.DatabaseBattleMessages;
|
|
LoadPreviousSession = other.LoadPreviousSession;
|
|
FilterIncludePreviousSessions = other.FilterIncludePreviousSessions;
|
|
SortAutoTranslate = other.SortAutoTranslate;
|
|
CollapseDuplicateMessages = other.CollapseDuplicateMessages;
|
|
PlaySounds = other.PlaySounds;
|
|
KeepInputFocus = other.KeepInputFocus;
|
|
MaxLinesToRender = other.MaxLinesToRender;
|
|
ShowEmotes = other.ShowEmotes;
|
|
BlockedEmotes = other.BlockedEmotes;
|
|
FontsEnabled = other.FontsEnabled;
|
|
ExtraGlyphRanges = other.ExtraGlyphRanges;
|
|
FontSize = other.FontSize;
|
|
JapaneseFontSize = other.JapaneseFontSize;
|
|
SymbolsFontSize = other.SymbolsFontSize;
|
|
GlobalFont = other.GlobalFont;
|
|
JapaneseFont = other.JapaneseFont;
|
|
TooltipOffset = other.TooltipOffset;
|
|
WindowAlpha = other.WindowAlpha;
|
|
ChatColours = other.ChatColours.ToDictionary(entry => entry.Key, entry => entry.Value);
|
|
Tabs = other.Tabs.Select(t => t.Clone()).ToList();
|
|
OverrideStyle = other.OverrideStyle;
|
|
ChosenStyle = other.ChosenStyle;
|
|
}
|
|
}
|
|
|
|
[Serializable]
|
|
internal enum UnreadMode
|
|
{
|
|
All,
|
|
Unseen,
|
|
None,
|
|
}
|
|
|
|
internal static class UnreadModeExt
|
|
{
|
|
internal static string Name(this UnreadMode mode) => mode switch
|
|
{
|
|
UnreadMode.All => Language.UnreadMode_All,
|
|
UnreadMode.Unseen => Language.UnreadMode_Unseen,
|
|
UnreadMode.None => Language.UnreadMode_None,
|
|
_ => throw new ArgumentOutOfRangeException(nameof(mode), mode, null),
|
|
};
|
|
|
|
internal static string? Tooltip(this UnreadMode mode) => mode switch
|
|
{
|
|
UnreadMode.All => Language.UnreadMode_All_Tooltip,
|
|
UnreadMode.Unseen => Language.UnreadMode_Unseen_Tooltip,
|
|
UnreadMode.None => Language.UnreadMode_None_Tooltip,
|
|
_ => null,
|
|
};
|
|
}
|
|
|
|
[Serializable]
|
|
internal class Tab
|
|
{
|
|
public string Name = Language.Tab_DefaultName;
|
|
public Dictionary<ChatType, ChatSource> ChatCodes = new();
|
|
public bool ExtraChatAll;
|
|
public HashSet<Guid> ExtraChatChannels = [];
|
|
|
|
public UnreadMode UnreadMode = UnreadMode.Unseen;
|
|
public bool DisplayTimestamp = true;
|
|
public InputChannel? Channel;
|
|
public bool PopOut;
|
|
public bool IndependentOpacity;
|
|
public float Opacity = 100f;
|
|
public bool InputDisabled;
|
|
|
|
[NonSerialized]
|
|
public uint Unread;
|
|
|
|
[NonSerialized]
|
|
public long LastMessageTime;
|
|
|
|
[NonSerialized]
|
|
public MessageList Messages = new();
|
|
|
|
[NonSerialized]
|
|
public InputChannel? PreviousChannel;
|
|
|
|
[NonSerialized]
|
|
public Guid Identifier = Guid.NewGuid();
|
|
|
|
internal bool Matches(Message message)
|
|
{
|
|
if (message.ExtraChatChannel != Guid.Empty)
|
|
return ExtraChatAll || ExtraChatChannels.Contains(message.ExtraChatChannel);
|
|
|
|
return message.Code.Type.IsGm()
|
|
|| ChatCodes.TryGetValue(message.Code.Type, out var sources)
|
|
&& (message.Code.Source is 0 or (ChatSource) 1
|
|
|| sources.HasFlag(message.Code.Source));
|
|
}
|
|
|
|
internal void AddMessage(Message message, bool unread = true)
|
|
{
|
|
Messages.AddPrune(message, MessageManager.MessageDisplayLimit);
|
|
if (unread)
|
|
{
|
|
Unread += 1;
|
|
LastMessageTime = Environment.TickCount64;
|
|
}
|
|
}
|
|
|
|
internal void Clear()
|
|
{
|
|
Messages.Clear();
|
|
}
|
|
|
|
internal Tab Clone()
|
|
{
|
|
return new Tab
|
|
{
|
|
Name = Name,
|
|
ChatCodes = ChatCodes.ToDictionary(entry => entry.Key, entry => entry.Value),
|
|
ExtraChatAll = ExtraChatAll,
|
|
ExtraChatChannels = ExtraChatChannels.ToHashSet(),
|
|
UnreadMode = UnreadMode,
|
|
DisplayTimestamp = DisplayTimestamp,
|
|
Channel = Channel,
|
|
PopOut = PopOut,
|
|
IndependentOpacity = IndependentOpacity,
|
|
Opacity = Opacity,
|
|
Identifier = Identifier,
|
|
InputDisabled = InputDisabled,
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// MessageList provides an ordered list of messages with duplicate ID
|
|
/// tracking, sorting and mutex protection.
|
|
/// </summary>
|
|
internal class MessageList
|
|
{
|
|
private ReaderWriterLock rwl = new();
|
|
|
|
private readonly List<Message> messages;
|
|
private readonly HashSet<Guid> trackedMessageIds;
|
|
|
|
public MessageList()
|
|
{
|
|
messages = new();
|
|
trackedMessageIds = new();
|
|
}
|
|
|
|
public MessageList(int initialCapacity)
|
|
{
|
|
messages = new(initialCapacity);
|
|
trackedMessageIds = new(initialCapacity);
|
|
}
|
|
|
|
public void AddPrune(Message message, int max)
|
|
{
|
|
rwl.AcquireWriterLock(-1);
|
|
try
|
|
{
|
|
AddLocked(message);
|
|
PruneMaxLocked(max);
|
|
}
|
|
finally
|
|
{
|
|
rwl.ReleaseWriterLock();
|
|
}
|
|
}
|
|
|
|
public void AddSortPrune(IEnumerable<Message> messages, int max)
|
|
{
|
|
rwl.AcquireWriterLock(-1);
|
|
try
|
|
{
|
|
foreach (var message in messages)
|
|
AddLocked(message);
|
|
|
|
SortLocked();
|
|
PruneMaxLocked(max);
|
|
}
|
|
finally
|
|
{
|
|
rwl.ReleaseWriterLock();
|
|
}
|
|
}
|
|
|
|
private void AddLocked(Message message)
|
|
{
|
|
if (trackedMessageIds.Contains(message.Id))
|
|
return;
|
|
|
|
messages.Add(message);
|
|
trackedMessageIds.Add(message.Id);
|
|
}
|
|
|
|
public void Clear()
|
|
{
|
|
rwl.AcquireWriterLock(-1);
|
|
try
|
|
{
|
|
messages.Clear();
|
|
trackedMessageIds.Clear();
|
|
}
|
|
finally
|
|
{
|
|
rwl.ReleaseWriterLock();
|
|
}
|
|
}
|
|
|
|
private void SortLocked()
|
|
{
|
|
messages.Sort((a, b) => a.Date.CompareTo(b.Date));
|
|
}
|
|
|
|
private void PruneMaxLocked(int max)
|
|
{
|
|
while (messages.Count > max)
|
|
{
|
|
trackedMessageIds.Remove(messages[0].Id);
|
|
messages.RemoveAt(0);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// GetReadOnly returns a read-only list of messages while holding a
|
|
/// reader lock. The list should be used with a using statement.
|
|
/// </summary>
|
|
public RLockedMessageList GetReadOnly(int millisecondsTimeout = -1)
|
|
{
|
|
rwl.AcquireReaderLock(millisecondsTimeout);
|
|
return new RLockedMessageList(rwl, messages);
|
|
}
|
|
|
|
internal class RLockedMessageList(ReaderWriterLock rwl, List<Message> messages) : IReadOnlyList<Message>, IDisposable
|
|
{
|
|
public IEnumerator<Message> GetEnumerator()
|
|
{
|
|
return messages.GetEnumerator();
|
|
}
|
|
|
|
IEnumerator IEnumerable.GetEnumerator()
|
|
{
|
|
return GetEnumerator();
|
|
}
|
|
|
|
public int Count => messages.Count;
|
|
|
|
public Message this[int index] => messages[index];
|
|
|
|
public void Dispose()
|
|
{
|
|
rwl.ReleaseReaderLock();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
[Serializable]
|
|
internal enum PreviewPosition
|
|
{
|
|
None,
|
|
Inside,
|
|
Top,
|
|
Bottom,
|
|
Tooltip,
|
|
}
|
|
|
|
internal static class PreviewPositionExt
|
|
{
|
|
internal static string Name(this PreviewPosition position) => position switch
|
|
{
|
|
PreviewPosition.None => Language.Options_Preview_None,
|
|
PreviewPosition.Inside => Language.Options_Preview_Inside,
|
|
PreviewPosition.Top => Language.Options_Preview_Top,
|
|
PreviewPosition.Bottom => Language.Options_Preview_Bottom,
|
|
PreviewPosition.Tooltip => Language.Options_Preview_Tooltip,
|
|
_ => throw new ArgumentOutOfRangeException(nameof(position), position, null),
|
|
};
|
|
}
|
|
|
|
[Serializable]
|
|
internal enum CommandHelpSide
|
|
{
|
|
None,
|
|
Left,
|
|
Right,
|
|
}
|
|
|
|
internal static class CommandHelpSideExt
|
|
{
|
|
internal static string Name(this CommandHelpSide side) => side switch
|
|
{
|
|
CommandHelpSide.None => Language.CommandHelpSide_None,
|
|
CommandHelpSide.Left => Language.CommandHelpSide_Left,
|
|
CommandHelpSide.Right => Language.CommandHelpSide_Right,
|
|
_ => throw new ArgumentOutOfRangeException(nameof(side), side, null),
|
|
};
|
|
}
|
|
|
|
[Serializable]
|
|
internal enum KeybindMode
|
|
{
|
|
Flexible,
|
|
Strict,
|
|
}
|
|
|
|
internal static class KeybindModeExt
|
|
{
|
|
internal static string Name(this KeybindMode mode) => mode switch
|
|
{
|
|
KeybindMode.Flexible => Language.KeybindMode_Flexible_Name,
|
|
KeybindMode.Strict => Language.KeybindMode_Strict_Name,
|
|
_ => throw new ArgumentOutOfRangeException(nameof(mode), mode, null),
|
|
};
|
|
|
|
internal static string? Tooltip(this KeybindMode mode) => mode switch
|
|
{
|
|
KeybindMode.Flexible => Language.KeybindMode_Flexible_Tooltip,
|
|
KeybindMode.Strict => Language.KeybindMode_Strict_Tooltip,
|
|
_ => null,
|
|
};
|
|
}
|
|
|
|
[Serializable]
|
|
internal enum LanguageOverride
|
|
{
|
|
None,
|
|
ChineseSimplified,
|
|
ChineseTraditional,
|
|
Dutch,
|
|
English,
|
|
French,
|
|
German,
|
|
Greek,
|
|
|
|
// Italian,
|
|
Japanese,
|
|
|
|
// Korean,
|
|
// Norwegian,
|
|
PortugueseBrazil,
|
|
Romanian,
|
|
Russian,
|
|
Spanish,
|
|
Swedish,
|
|
}
|
|
|
|
internal static class LanguageOverrideExt
|
|
{
|
|
internal static string Name(this LanguageOverride mode) => mode switch
|
|
{
|
|
LanguageOverride.None => Language.LanguageOverride_None,
|
|
LanguageOverride.ChineseSimplified => "简体中文",
|
|
LanguageOverride.ChineseTraditional => "繁體中文",
|
|
LanguageOverride.Dutch => "Nederlands",
|
|
LanguageOverride.English => "English",
|
|
LanguageOverride.French => "Français",
|
|
LanguageOverride.German => "Deutsch",
|
|
LanguageOverride.Greek => "Ελληνικά",
|
|
// LanguageOverride.Italian => "Italiano",
|
|
LanguageOverride.Japanese => "日本語",
|
|
// LanguageOverride.Korean => "한국어 (Korean)",
|
|
// LanguageOverride.Norwegian => "Norsk",
|
|
LanguageOverride.PortugueseBrazil => "Português do Brasil",
|
|
LanguageOverride.Romanian => "Română",
|
|
LanguageOverride.Russian => "Русский",
|
|
LanguageOverride.Spanish => "Español",
|
|
LanguageOverride.Swedish => "Svenska",
|
|
_ => throw new ArgumentOutOfRangeException(nameof(mode), mode, null),
|
|
};
|
|
|
|
internal static string Code(this LanguageOverride mode) => mode switch
|
|
{
|
|
LanguageOverride.None => "",
|
|
LanguageOverride.ChineseSimplified => "zh-hans",
|
|
LanguageOverride.ChineseTraditional => "zh-hant",
|
|
LanguageOverride.Dutch => "nl",
|
|
LanguageOverride.English => "en",
|
|
LanguageOverride.French => "fr",
|
|
LanguageOverride.German => "de",
|
|
LanguageOverride.Greek => "el",
|
|
// LanguageOverride.Italian => "it",
|
|
LanguageOverride.Japanese => "ja",
|
|
// LanguageOverride.Korean => "ko",
|
|
// LanguageOverride.Norwegian => "no",
|
|
LanguageOverride.PortugueseBrazil => "pt-br",
|
|
LanguageOverride.Romanian => "ro",
|
|
LanguageOverride.Russian => "ru",
|
|
LanguageOverride.Spanish => "es",
|
|
LanguageOverride.Swedish => "sv",
|
|
_ => throw new ArgumentOutOfRangeException(nameof(mode), mode, null),
|
|
};
|
|
}
|
|
|
|
[Serializable]
|
|
[Flags]
|
|
internal enum ExtraGlyphRanges
|
|
{
|
|
ChineseFull = 1 << 0,
|
|
ChineseSimplifiedCommon = 1 << 1,
|
|
Cyrillic = 1 << 2,
|
|
Japanese = 1 << 3,
|
|
Korean = 1 << 4,
|
|
Thai = 1 << 5,
|
|
Vietnamese = 1 << 6,
|
|
}
|
|
|
|
internal static class ExtraGlyphRangesExt
|
|
{
|
|
internal static string Name(this ExtraGlyphRanges ranges) => ranges switch
|
|
{
|
|
ExtraGlyphRanges.ChineseFull => Language.ExtraGlyphRanges_ChineseFull_Name,
|
|
ExtraGlyphRanges.ChineseSimplifiedCommon => Language.ExtraGlyphRanges_ChineseSimplifiedCommon_Name,
|
|
ExtraGlyphRanges.Cyrillic => Language.ExtraGlyphRanges_Cyrillic_Name,
|
|
ExtraGlyphRanges.Japanese => Language.ExtraGlyphRanges_Japanese_Name,
|
|
ExtraGlyphRanges.Korean => Language.ExtraGlyphRanges_Korean_Name,
|
|
ExtraGlyphRanges.Thai => Language.ExtraGlyphRanges_Thai_Name,
|
|
ExtraGlyphRanges.Vietnamese => Language.ExtraGlyphRanges_Vietnamese_Name,
|
|
_ => throw new ArgumentOutOfRangeException(nameof(ranges), ranges, null),
|
|
};
|
|
|
|
internal static nint Range(this ExtraGlyphRanges ranges) => ranges switch
|
|
{
|
|
ExtraGlyphRanges.ChineseFull => ImGui.GetIO().Fonts.GetGlyphRangesChineseFull(),
|
|
ExtraGlyphRanges.ChineseSimplifiedCommon => ImGui.GetIO().Fonts.GetGlyphRangesChineseSimplifiedCommon(),
|
|
ExtraGlyphRanges.Cyrillic => ImGui.GetIO().Fonts.GetGlyphRangesCyrillic(),
|
|
ExtraGlyphRanges.Japanese => ImGui.GetIO().Fonts.GetGlyphRangesJapanese(),
|
|
ExtraGlyphRanges.Korean => ImGui.GetIO().Fonts.GetGlyphRangesKorean(),
|
|
ExtraGlyphRanges.Thai => ImGui.GetIO().Fonts.GetGlyphRangesThai(),
|
|
ExtraGlyphRanges.Vietnamese => ImGui.GetIO().Fonts.GetGlyphRangesVietnamese(),
|
|
_ => throw new ArgumentOutOfRangeException(nameof(ranges), ranges, null),
|
|
};
|
|
}
|