refactor(ui): retire ChatLogWindow and the v1.5.6 chat-window layer
The legacy ChatLogWindow.cs and its tightly coupled neighbours are gone: PayloadHandler, Popout, ChatInputBar, AutoCompleteInfo, AutoTellTabTint, the three tab-icon helpers, the old Ui/StatusBar and Ui/SymbolPicker behind the components-layer replacements, HellionStyle + helpers, the CompactInputSubmitter test mirror and the QuickPickerSelfTestStep. The new component layer (MainWindow + the five components + GlobalStyleScope) now drives the whole chat surface. InputPreview, CommandHelpWindow and Debugger lose their ChatLogWindow backref. The first two are skeleton windows for now — DrawConditions always returns false until the new chat layer exposes equivalent state. Debugger keeps the current-tab and vanilla-chat blocks; the payload counters are explicitly marked offline. DbViewer renders Sender/Content columns as plain TextValue strings instead of the removed DrawChunks. GameFunctions.Chat and GameFunctions.KeybindManager keep the hook plumbing intact but mark every ChatLogWindow.Activated / ChangeTabDelta / TellSpecial site as offline so the FFXIV-side integration still compiles and runs without an Activated entry point. TypingIpc.BuildState reports the IPC state as not-typing / not-focused until the new chat layer surfaces real focus and buffer state again. Plugin.cs Draw uses StyleEngine.GlobalStyleScope.Push for the per-frame theme push and stops calling BeginFrame / FinalizeFrame / HideStateCheck / DefaultText through the dead window. ImGuiUtil drops PostPayload + WrapText + the surrounding word-wrap pipeline. PluginHostFactory and PluginLifecycle drop the legacy DI singletons and AddWindow entries. Build is clean and csharpier is clean across the trimmed 131-file tree.
This commit is contained in:
+12
-275
@@ -1,40 +1,20 @@
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Game.Text;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using Dalamud.Plugin.Services;
|
||||
using HellionChat.Code;
|
||||
using HellionChat.Resources;
|
||||
using HellionChat.Util;
|
||||
|
||||
namespace HellionChat.Ui;
|
||||
|
||||
public partial class InputPreview : Window
|
||||
// Pre-send chunk preview is offline while the chat input pipeline is
|
||||
// rebuilt. The window stays in the system so the DI graph keeps a single
|
||||
// shape across cycles, but DrawConditions always returns false until the
|
||||
// new preview lands on top of the components layer.
|
||||
public class InputPreview : Window
|
||||
{
|
||||
private ChatLogWindow LogWindow { get; }
|
||||
private readonly Plugin _plugin;
|
||||
|
||||
private bool Drawing;
|
||||
private bool HasEvaluation;
|
||||
internal float PreviewHeight;
|
||||
|
||||
private int LastLength;
|
||||
private Message? PreviewMessage;
|
||||
|
||||
private int CursorPosition;
|
||||
private bool NextChunkIsAutoTranslate;
|
||||
|
||||
internal int SelectedCursorPos = -1;
|
||||
|
||||
internal InputPreview(ChatLogWindow logWindow)
|
||||
internal InputPreview(Plugin plugin)
|
||||
: base("##chat2-inputpreview")
|
||||
{
|
||||
LogWindow = logWindow;
|
||||
|
||||
_plugin = plugin;
|
||||
Flags =
|
||||
ImGuiWindowFlags.NoSavedSettings
|
||||
| ImGuiWindowFlags.NoTitleBar
|
||||
@@ -42,257 +22,14 @@ public partial class InputPreview : Window
|
||||
| ImGuiWindowFlags.NoResize
|
||||
| ImGuiWindowFlags.NoFocusOnAppearing
|
||||
| ImGuiWindowFlags.NoScrollbar;
|
||||
|
||||
RespectCloseHotkey = false;
|
||||
DisableWindowSounds = true;
|
||||
IsOpen = true;
|
||||
|
||||
Plugin.Framework.Update += UpdateConditionCheck;
|
||||
IsOpen = false;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Plugin.Framework.Update -= UpdateConditionCheck;
|
||||
}
|
||||
public void Dispose() { }
|
||||
|
||||
private bool ValidDraw =>
|
||||
!string.IsNullOrEmpty(LogWindow.Chat)
|
||||
&& LogWindow.Chat.Length >= Plugin.Config.PreviewMinimum;
|
||||
public override bool DrawConditions() => false;
|
||||
|
||||
private void UpdateConditionCheck(IFramework framework)
|
||||
{
|
||||
Drawing = ValidDraw;
|
||||
if (!Drawing)
|
||||
{
|
||||
LastLength = 0;
|
||||
PreviewHeight = 0;
|
||||
PreviewMessage = null;
|
||||
HasEvaluation = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (PreviewMessage == null || LastLength != LogWindow.Chat.Length)
|
||||
{
|
||||
LastLength = LogWindow.Chat.Length;
|
||||
|
||||
var bytes = Encoding.UTF8.GetBytes(LogWindow.Chat.Trim());
|
||||
AutoTranslate.ReplaceWithPayload(ref bytes);
|
||||
|
||||
var chunks = ChunkUtil
|
||||
.ToChunks(SeString.Parse(bytes), ChunkSource.Content, ChatType.Say)
|
||||
.ToList();
|
||||
PreviewMessage = Message.FakeMessage(chunks, new ChatCode(XivChatType.Say, 0, 0));
|
||||
PreviewMessage.DecodeTextParam();
|
||||
}
|
||||
HasEvaluation = !Plugin.Config.OnlyPreviewIf || PreviewMessage.Content.Count > 1;
|
||||
}
|
||||
|
||||
internal bool IsDrawable => ValidDraw && HasEvaluation;
|
||||
|
||||
private static bool IsWindowMode =>
|
||||
Plugin.Config.PreviewPosition is PreviewPosition.Top or PreviewPosition.Bottom;
|
||||
|
||||
public override bool DrawConditions()
|
||||
{
|
||||
return IsWindowMode && IsDrawable;
|
||||
}
|
||||
|
||||
public override void PreDraw()
|
||||
{
|
||||
var pos = LogWindow.LastWindowPos;
|
||||
var size = LogWindow.LastWindowSize;
|
||||
|
||||
Size = size with { Y = PreviewHeight };
|
||||
|
||||
var y = Plugin.Config.PreviewPosition switch
|
||||
{
|
||||
PreviewPosition.Top => pos.Y - PreviewHeight,
|
||||
PreviewPosition.Bottom => pos.Y + size.Y,
|
||||
_ => throw new ArgumentOutOfRangeException(
|
||||
nameof(Plugin.Config.PreviewPosition),
|
||||
Plugin.Config.PreviewPosition,
|
||||
null
|
||||
),
|
||||
};
|
||||
|
||||
Position = pos with { Y = y };
|
||||
PositionCondition = ImGuiCond.Always;
|
||||
}
|
||||
|
||||
public override void Draw()
|
||||
{
|
||||
CalculatePreview();
|
||||
DrawPreview();
|
||||
}
|
||||
|
||||
internal void CalculatePreview()
|
||||
{
|
||||
// We Pre-draw this once to get the actual height :HideThePain:
|
||||
PreviewHeight = 0;
|
||||
|
||||
var pos = ImGui.GetCursorPos();
|
||||
ImGui.SetCursorPos(new Vector2(-500, -500));
|
||||
var before = ImGui.GetCursorPosY();
|
||||
using (ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero))
|
||||
{
|
||||
ImGui.TextUnformatted(Language.Options_Preview_Header);
|
||||
DrawChunksPreview(PreviewMessage!.Content);
|
||||
}
|
||||
var after = ImGui.GetCursorPosY();
|
||||
ImGui.SetCursorPos(pos);
|
||||
|
||||
PreviewHeight = after - before;
|
||||
PreviewHeight += IsWindowMode ? ImGui.GetStyle().WindowPadding.Y * 2 : 0;
|
||||
}
|
||||
|
||||
internal void DrawPreview()
|
||||
{
|
||||
using (ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero))
|
||||
{
|
||||
ImGui.TextUnformatted(Language.Options_Preview_Header);
|
||||
|
||||
var handler = LogWindow.HandlerLender.Borrow();
|
||||
DrawChunksPreview(PreviewMessage!.Content, handler, unique: 10000);
|
||||
handler.Draw();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawChunksPreview(
|
||||
IReadOnlyList<Chunk> chunks,
|
||||
PayloadHandler? handler = null,
|
||||
float lineWidth = 0f,
|
||||
int unique = 0
|
||||
)
|
||||
{
|
||||
CursorPosition = 0;
|
||||
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
|
||||
for (var i = 0; i < chunks.Count; i++)
|
||||
{
|
||||
if (chunks[i] is TextChunk text && string.IsNullOrEmpty(text.Content))
|
||||
continue;
|
||||
|
||||
DrawChunkPreview(chunks[i], handler, lineWidth, unique);
|
||||
|
||||
if (i < chunks.Count - 1)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
}
|
||||
else if (chunks[i].Link is EmotePayload && Plugin.Config.ShowEmotes)
|
||||
{
|
||||
// Emote payloads seem to not automatically put newlines, which
|
||||
// is an issue when modern mode is disabled.
|
||||
ImGui.SameLine();
|
||||
// Use default ImGui behavior for newlines.
|
||||
ImGui.TextUnformatted("");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawChunkPreview(
|
||||
Chunk chunk,
|
||||
PayloadHandler? handler = null,
|
||||
float lineWidth = 0f,
|
||||
int unique = 0
|
||||
)
|
||||
{
|
||||
if (chunk is IconChunk icon)
|
||||
{
|
||||
LogWindow.DrawIcon(chunk, icon, handler);
|
||||
if (icon.Icon != BitmapFontIcon.AutoTranslateBegin)
|
||||
return;
|
||||
|
||||
NextChunkIsAutoTranslate = true;
|
||||
// Malformed chunks could carry an AutoTranslateBegin icon without the matching
|
||||
// payload; bail out instead of dereferencing a null Link.
|
||||
if (chunk.Link is not AutoTranslatePayload payload)
|
||||
return;
|
||||
CursorPosition += $"<at:{payload.Group},{payload.Key}>".Length;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (chunk is not TextChunk text)
|
||||
return;
|
||||
|
||||
if (chunk.Link is EmotePayload emotePayload && Plugin.Config.ShowEmotes)
|
||||
{
|
||||
var emoteSize = ImGui.CalcTextSize("W");
|
||||
emoteSize = emoteSize with { Y = emoteSize.X } * 1.5f;
|
||||
|
||||
// TextWrap doesn't work for emotes, so we have to wrap them manually
|
||||
if (ImGui.GetContentRegionAvail().X < emoteSize.X)
|
||||
ImGui.NewLine();
|
||||
|
||||
// We only draw a dummy if it is still loading, in case it failed, we draw the actual name
|
||||
var image = EmoteCache.GetEmote(emotePayload.Code);
|
||||
if (image is { Failed: false })
|
||||
{
|
||||
if (image.IsLoaded)
|
||||
image.Draw(emoteSize);
|
||||
else
|
||||
ImGui.Dummy(emoteSize);
|
||||
|
||||
if (ImGui.IsItemHovered())
|
||||
ImGuiUtil.Tooltip(emotePayload.Code);
|
||||
|
||||
CursorPosition += emotePayload.Code.Length;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (NextChunkIsAutoTranslate)
|
||||
{
|
||||
NextChunkIsAutoTranslate = false;
|
||||
ImGuiUtil.WrapText(text.Content, chunk, handler, LogWindow.DefaultText, lineWidth);
|
||||
return;
|
||||
}
|
||||
|
||||
if (text.Link != null)
|
||||
{
|
||||
if (text.Link is ItemPayload)
|
||||
CursorPosition += "<item>".Length;
|
||||
else if (text.Link is MapLinkPayload)
|
||||
CursorPosition += "<flag>".Length;
|
||||
else if (text.Link is EmotePayload emote)
|
||||
CursorPosition += emote.Code.Length;
|
||||
else if (text.Link is UriPayload)
|
||||
CursorPosition += text.Content.Length;
|
||||
|
||||
ImGuiUtil.WrapText(text.Content, chunk, handler, LogWindow.DefaultText, lineWidth);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var word in WhitespaceRegex().Split(text.Content).Where(s => s != string.Empty))
|
||||
{
|
||||
var wordSize = ImGui.CalcTextSize(word);
|
||||
if (ImGui.GetContentRegionAvail().X < wordSize.X)
|
||||
ImGui.NewLine();
|
||||
|
||||
foreach (var letter in word)
|
||||
{
|
||||
var letterSize = ImGui.CalcTextSize(letter.ToString());
|
||||
|
||||
CursorPosition++;
|
||||
if (
|
||||
ImGui.Selectable(
|
||||
$"{letter}##{CursorPosition + unique}",
|
||||
false,
|
||||
ImGuiSelectableFlags.None,
|
||||
letterSize
|
||||
)
|
||||
)
|
||||
{
|
||||
SelectedCursorPos = CursorPosition;
|
||||
LogWindow.FocusedPreview = true;
|
||||
}
|
||||
ImGui.SameLine();
|
||||
}
|
||||
}
|
||||
ImGui.NewLine();
|
||||
}
|
||||
|
||||
[GeneratedRegex(@"(\s)")]
|
||||
private static partial Regex WhitespaceRegex();
|
||||
public override void Draw() { }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user