Implement emotes part 2

This commit is contained in:
Infi
2024-05-09 19:09:36 +02:00
parent 53cf79003b
commit b7286b8010
15 changed files with 328 additions and 17 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Version>1.23.5</Version>
<Version>1.24.0</Version>
<TargetFramework>net8.0-windows</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
+5
View File
@@ -40,6 +40,9 @@ internal class Configuration : IPluginConfiguration
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;
@@ -88,6 +91,8 @@ internal class Configuration : IPluginConfiguration
PlaySounds = other.PlaySounds;
KeepInputFocus = other.KeepInputFocus;
MaxLinesToRender = other.MaxLinesToRender;
ShowEmotes = other.ShowEmotes;
BlockedEmotes = other.BlockedEmotes;
FontsEnabled = other.FontsEnabled;
ExtraGlyphRanges = other.ExtraGlyphRanges;
FontSize = other.FontSize;
+1 -1
View File
@@ -39,7 +39,7 @@ public static class EmoteCache
private static readonly Dictionary<string, Emote> Cache = new();
private static readonly string[] EmoteCodeArray = [];
public static readonly string[] EmoteCodeArray = [];
static EmoteCache()
{
+3 -2
View File
@@ -72,7 +72,8 @@ internal class Message
internal Dictionary<Guid, float?> Height { get; } = new();
internal Dictionary<Guid, bool> IsVisible { get; } = new();
internal Message(ulong receiver, ulong contentId, ChatCode code, List<Chunk> sender, List<Chunk> content, SeString senderSource, SeString contentSource) {
internal Message(ulong receiver, ulong contentId, ChatCode code, List<Chunk> sender, List<Chunk> content, SeString senderSource, SeString contentSource)
{
Receiver = receiver;
ContentId = contentId;
Date = DateTimeOffset.UtcNow;
@@ -191,7 +192,7 @@ internal class Message
var builder = new StringBuilder();
foreach (var word in text.Content.Split(" "))
{
if (EmoteCache.Exists(word))
if (Plugin.Config.ShowEmotes && EmoteCache.Exists(word) && !Plugin.Config.BlockedEmotes.Contains(word))
{
// We add all the previous collected text parts
AddContentAfterURLCheck(builder.ToString(), text, chunk);
+3 -3
View File
@@ -207,7 +207,7 @@ public sealed class PayloadHandler {
internal void Click(Chunk chunk, Payload? payload, ImGuiMouseButton button)
{
if (LogWindow.Plugin.Config.PlaySounds)
if (Plugin.Config.PlaySounds)
UIModule.PlaySound(PopupSfx);
switch (button)
@@ -230,7 +230,7 @@ public sealed class PayloadHandler {
DoHover(() => HoverStatus(status), hoverSize);
break;
case ItemPayload item:
if (LogWindow.Plugin.Config.NativeItemTooltips)
if (Plugin.Config.NativeItemTooltips)
{
if (!_handleTooltips || _hoveredItem != item.RawItemId)
{
@@ -302,7 +302,7 @@ public sealed class PayloadHandler {
var x = isLeft ? window.X : LogWindow.LastWindowPos.X - atkSize.X;
var y = Math.Clamp(window.Y - atkSize.Y, 0, float.MaxValue);
y -= isTop ? 0 : LogWindow.Plugin.Config.TooltipOffset; // offset to prevent cut-off on the bottom
y -= isTop ? 0 : Plugin.Config.TooltipOffset; // offset to prevent cut-off on the bottom
atk->SetPosition((short) x, (short) y);
}
+2 -1
View File
@@ -40,6 +40,8 @@ public sealed class Plugin : IDalamudPlugin
[PluginService] internal static INotificationManager Notification { get; private set; } = null!;
[PluginService] internal static IAddonLifecycle AddonLifecycle { get; private set; } = null!;
internal static Configuration Config = null!;
public readonly WindowSystem WindowSystem = new(PluginName);
public SettingsWindow SettingsWindow { get; }
public ChatLogWindow ChatLogWindow { get; }
@@ -48,7 +50,6 @@ public sealed class Plugin : IDalamudPlugin
public DebuggerWindow DebuggerWindow { get; }
internal LegacyMessageImporterWindow LegacyMessageImporterWindow { get; }
internal Configuration Config { get; }
internal Commands Commands { get; }
internal XivCommonBase Common { get; }
internal TextureCache TextureCache { get; }
+45
View File
@@ -1868,6 +1868,33 @@ namespace ChatTwo.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Blocked emotes.
/// </summary>
internal static string Options_Emote_BlockedEmotes {
get {
return ResourceManager.GetString("Options_Emote_BlockedEmotes", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Emote.
/// </summary>
internal static string Options_Emote_EmoteTable {
get {
return ResourceManager.GetString("Options_Emote_EmoteTable", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Emotes.
/// </summary>
internal static string Options_Emote_Tab {
get {
return ResourceManager.GetString("Options_Emote_Tab", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Extra glyphs can be added to {0}&apos;s global font by enabling the checkboxes below. This will likely require increasing Dalamud&apos;s font atlas size..
/// </summary>
@@ -2309,6 +2336,24 @@ namespace ChatTwo.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Replaces words with their emote version, currently supports BetterTTV.
/// </summary>
internal static string Options_ShowEmotes_Desc {
get {
return ResourceManager.GetString("Options_ShowEmotes_Desc", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Show emotes.
/// </summary>
internal static string Options_ShowEmotes_Name {
get {
return ResourceManager.GetString("Options_ShowEmotes_Name", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Show the Novice Network join button next to the settings button if logged in as a mentor..
/// </summary>
+15
View File
@@ -1018,4 +1018,19 @@
<data name="Options_KeepInputFocus_Description" xml:space="preserve">
<value>Keeps the input focus, even if you enter battle or do other actions</value>
</data>
<data name="Options_ShowEmotes_Name" xml:space="preserve">
<value>Show emotes</value>
</data>
<data name="Options_ShowEmotes_Desc" xml:space="preserve">
<value>Replaces words with their emote version, currently supports BetterTTV</value>
</data>
<data name="Options_Emote_Tab" xml:space="preserve">
<value>Emotes</value>
</data>
<data name="Options_Emote_BlockedEmotes" xml:space="preserve">
<value>Blocked emotes</value>
</data>
<data name="Options_Emote_EmoteTable" xml:space="preserve">
<value>Emote</value>
</data>
</root>
+1 -1
View File
@@ -31,7 +31,7 @@ public class CommandHelpWindow : Window {
var width = 350;
var scaledWidth = width * ImGuiHelpers.GlobalScale;
var pos = LogWindow.LastWindowPos;
switch (LogWindow.Plugin.Config.CommandHelpSide) {
switch (Plugin.Config.CommandHelpSide) {
case CommandHelpSide.Right:
pos.X += LogWindow.LastWindowSize.X;
break;
+9 -8
View File
@@ -39,15 +39,16 @@ internal class Popout : Window
public override void PreDraw()
{
if (ChatLogWindow.Plugin.Config is { OverrideStyle: true, ChosenStyle: not null })
StyleModel.GetConfiguredStyles()?.FirstOrDefault(style => style.Name == ChatLogWindow.Plugin.Config.ChosenStyle)?.Push();
if (Plugin.Config is { OverrideStyle: true, ChosenStyle: not null })
StyleModel.GetConfiguredStyles()?.FirstOrDefault(style => style.Name == Plugin.Config.ChosenStyle)?.Push();
Flags = ImGuiWindowFlags.None;
if (!ChatLogWindow.Plugin.Config.ShowPopOutTitleBar)
if (!Plugin.Config.ShowPopOutTitleBar)
Flags |= ImGuiWindowFlags.NoTitleBar;
if (!ChatLogWindow.PopOutDocked[Idx]) {
var alpha = Tab.IndependentOpacity ? Tab.Opacity : ChatLogWindow.Plugin.Config.WindowAlpha;
if (!ChatLogWindow.PopOutDocked[Idx])
{
var alpha = Tab.IndependentOpacity ? Tab.Opacity : Plugin.Config.WindowAlpha;
BgAlpha = alpha / 100f;
}
}
@@ -56,7 +57,7 @@ internal class Popout : Window
{
using var id = ImRaii.PushId($"popout-{Tab.Identifier}");
if (!ChatLogWindow.Plugin.Config.ShowPopOutTitleBar)
if (!Plugin.Config.ShowPopOutTitleBar)
{
ImGui.TextUnformatted(Tab.Name);
ImGui.Separator();
@@ -70,8 +71,8 @@ internal class Popout : Window
{
ChatLogWindow.PopOutDocked[Idx] = ImGui.IsWindowDocked();
if (ChatLogWindow.Plugin.Config is { OverrideStyle: true, ChosenStyle: not null })
StyleModel.GetConfiguredStyles()?.FirstOrDefault(style => style.Name == ChatLogWindow.Plugin.Config.ChosenStyle)?.Pop();
if (Plugin.Config is { OverrideStyle: true, ChosenStyle: not null })
StyleModel.GetConfiguredStyles()?.FirstOrDefault(style => style.Name == Plugin.Config.ChosenStyle)?.Pop();
}
public override void OnClose()
+1
View File
@@ -35,6 +35,7 @@ public sealed class SettingsWindow : Window
{
new Display(Mutable),
new ChatLog(Plugin, Mutable),
new Emote(Plugin, Mutable),
new Ui.SettingsTabs.Fonts(Mutable),
new ChatColours(Plugin, Mutable),
new Tabs(Plugin, Mutable),
+2
View File
@@ -49,5 +49,7 @@ internal sealed class Display : ISettingsTab
ImGuiUtil.OptionCheckbox(ref Mutable.CollapseDuplicateMessages, Language.Options_CollapseDuplicateMessages_Name, Language.Options_CollapseDuplicateMessages_Description);
ImGui.Spacing();
ImGui.PopTextWrapPos();
}
}
+69
View File
@@ -0,0 +1,69 @@
using System.Numerics;
using ChatTwo.Resources;
using ChatTwo.Util;
using Dalamud.Interface;
using Dalamud.Interface.Utility.Raii;
using ImGuiNET;
namespace ChatTwo.Ui.SettingsTabs;
internal sealed class Emote : ISettingsTab
{
private readonly Plugin Plugin;
private Configuration Mutable { get; }
public string Name => Language.Options_Emote_Tab + "###tabs-emote";
private static SearchSelector.SelectorPopupOptions WordPopupOptions = null!;
internal Emote(Plugin plugin, Configuration mutable)
{
Plugin = plugin;
Mutable = mutable;
WordPopupOptions = new SearchSelector.SelectorPopupOptions
{
FilteredSheet = EmoteCache.EmoteCodeArray.Where(w => !Mutable.BlockedEmotes.Contains(w))
};
}
public void Draw(bool changed)
{
ImGui.PushTextWrapPos();
ImGuiUtil.OptionCheckbox(ref Mutable.ShowEmotes, Language.Options_ShowEmotes_Name, Language.Options_ShowEmotes_Desc);
ImGui.Spacing();
ImGui.TextUnformatted(Language.Options_Emote_BlockedEmotes);
ImGui.Spacing();
var buttonWidth = ImGui.GetContentRegionAvail().X / 3;
using (Plugin.FontManager.FontAwesome.Push())
ImGui.Button(FontAwesomeIcon.Plus.ToIconString(), new Vector2(buttonWidth, 0));
if (SearchSelector.SelectorPopup("WordAddPopup", out var newWord, WordPopupOptions))
Mutable.BlockedEmotes.Add(newWord);
using var table = ImRaii.Table("##BlockedWords", 2, ImGuiTableFlags.RowBg | ImGuiTableFlags.BordersInner);
if (table)
{
ImGui.TableSetupColumn(Language.Options_Emote_EmoteTable);
ImGui.TableSetupColumn("##Del", 0, 0.07f);
ImGui.TableHeadersRow();
var copiedList = Mutable.BlockedEmotes.ToArray();
foreach (var word in copiedList)
{
ImGui.TableNextColumn();
ImGui.TextUnformatted(word);
ImGui.TableNextColumn();
if (ImGuiUtil.Button($"##{word}Del", FontAwesomeIcon.Trash, !ImGui.GetIO().KeyCtrl))
Mutable.BlockedEmotes.Remove(word);
}
}
ImGui.PopTextWrapPos();
}
}
+10
View File
@@ -3,6 +3,7 @@ using System.Text;
using Dalamud.Game.ClientState.Keys;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Interface;
using Dalamud.Interface.Components;
using Dalamud.Interface.Style;
using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii;
@@ -260,6 +261,15 @@ internal static class ImGuiUtil
return r;
}
public static bool Button(string id, FontAwesomeIcon icon, bool disabled)
{
var clicked = false;
using (ImRaii.Disabled(disabled))
clicked = ImGuiComponents.IconButton(id, icon);
return clicked;
}
internal static bool CtrlShiftButton(string label, string tooltip = "")
{
var ctrlShiftHeld = ImGui.GetIO() is { KeyCtrl: true, KeyShift: true };
+161
View File
@@ -0,0 +1,161 @@
using System.Numerics;
using Dalamud.Interface.Utility;
using ImGuiNET;
using System.Collections;
namespace ChatTwo.Util;
// Modified from: https://github.com/UnknownX7/Hypostasis/blob/master/ImGui/ExcelSheet.cs
public static class SearchSelector
{
private static string[]? FilteredSearchSheet;
private static string SheetSearchText = null!;
private static string PrevSearchId = null!;
private static Type PrevSearchType = null!;
public record SelectorOptions
{
public Func<string, string> FormatRow { get; init; } = row => row.ToString();
public Func<string, string, bool>? SearchPredicate { get; init; } = null;
public Func<string, bool, bool>? DrawSelectable { get; init; } = null;
public IEnumerable<string> FilteredSheet { get; init; } = [];
public Vector2? Size { get; init; } = null;
}
public record SelectorPopupOptions: SelectorOptions
{
public ImGuiPopupFlags PopupFlags { get; init; } = ImGuiPopupFlags.None;
public bool CloseOnSelection { get; init; } = false;
public Func<string, bool> IsSelected { get; init; } = _ => false;
}
private static void SearchInput(string id, IEnumerable<string> filteredSheet, Func<string, string, bool> searchPredicate)
{
if (ImGui.IsWindowAppearing() && ImGui.IsWindowFocused() && !ImGui.IsAnyItemActive())
{
if (id != PrevSearchId)
{
if (typeof(string) != PrevSearchType)
{
SheetSearchText = string.Empty;
PrevSearchType = typeof(string);
}
FilteredSearchSheet = null;
PrevSearchId = id;
}
ImGui.SetKeyboardFocusHere(0);
}
if (ImGui.InputTextWithHint("##ExcelSheetSearch", "Search", ref SheetSearchText, 128, ImGuiInputTextFlags.AutoSelectAll))
FilteredSearchSheet = null;
FilteredSearchSheet ??= filteredSheet.Where(s => searchPredicate(s, SheetSearchText)).ToArray();
}
public static bool SelectorPopup(string id, out string selected, SelectorPopupOptions? options = null, bool close = false)
{
options ??= new SelectorPopupOptions();
var sheet = options.FilteredSheet;
selected = string.Empty;
if (close)
return false;
ImGui.SetNextWindowSize(options.Size ?? new Vector2(0, 250 * ImGuiHelpers.GlobalScale));
if (!ImGui.BeginPopupContextItem(id, options.PopupFlags))
return false;
SearchInput(id, sheet, options.SearchPredicate ?? ((row, s) => options.FormatRow(row).Contains(s, StringComparison.CurrentCultureIgnoreCase)));
ImGui.BeginChild("SearchList", Vector2.Zero, true);
var ret = false;
var drawSelectable = options.DrawSelectable ?? ((row, selected) => ImGui.Selectable(options.FormatRow(row), selected));
using (var clipper = new ListClipper(FilteredSearchSheet!.Length))
{
foreach (var i in clipper.Rows)
{
var searched = FilteredSearchSheet[i];
ImGui.PushID(id);
if (!drawSelectable(searched, options.IsSelected(searched))) continue;
selected = searched;
ret = true;
ImGui.PopID();
}
}
// ImGui issue #273849, children keep popups from closing automatically
if (ret && options.CloseOnSelection)
ImGui.CloseCurrentPopup();
ImGui.EndChild();
ImGui.EndPopup();
return ret;
}
}
public unsafe class ListClipper : IEnumerable<(int, int)>, IDisposable
{
private ImGuiListClipperPtr Clipper;
private readonly int CurrentRows;
private readonly int CurrentColumns;
private readonly bool TwoDimensional;
private readonly int ItemRemainder;
public int FirstRow { get; private set; } = -1;
public int CurrentRow { get; private set; }
public int DisplayEnd => Clipper.DisplayEnd;
public IEnumerable<int> Rows
{
get
{
while (Clipper.Step()) // Supposedly this calls End()
{
if (Clipper.ItemsHeight > 0 && FirstRow < 0)
FirstRow = (int)(ImGui.GetScrollY() / Clipper.ItemsHeight);
for (var i = Clipper.DisplayStart; i < Clipper.DisplayEnd; i++)
{
CurrentRow = i;
yield return TwoDimensional ? i : i * CurrentColumns;
}
}
}
}
private IEnumerable<int> Columns
{
get
{
var cols = (ItemRemainder == 0 || CurrentRows != DisplayEnd || CurrentRow != DisplayEnd - 1) ? CurrentColumns : ItemRemainder;
for (var j = 0; j < cols; j++)
yield return j;
}
}
public ListClipper(int items, int cols = 1, bool twoD = false, float itemHeight = 0)
{
TwoDimensional = twoD;
CurrentColumns = cols;
CurrentRows = TwoDimensional ? items : (int)MathF.Ceiling((float)items / CurrentColumns);
ItemRemainder = !TwoDimensional ? items % CurrentColumns : 0;
Clipper = new ImGuiListClipperPtr(ImGuiNative.ImGuiListClipper_ImGuiListClipper());
Clipper.Begin(CurrentRows, itemHeight);
}
public IEnumerator<(int, int)> GetEnumerator() => (from i in Rows from j in Columns select (i, j)).GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public void Dispose()
{
Clipper.Destroy(); // This also calls End() but I'm calling it anyway just in case
GC.SuppressFinalize(this);
}
}