The first sweep matched a character class that swallowed the digit, so a bare B1 slipped through while B1-2 was caught. Searching the whole A-Z space instead of guessing prefixes turned up 130-odd more: B0 through B6, C2, C3, D1, H2, M6, P7, P8, T2, W2, plus GP-04, KB-01, OD-1, PM-1, PM-3, SEC-01, TR-4, TR-7, UI-11, UI-12, XC-8 and API-3. Kept deliberately: 41 B4 01 is a byte signature, "N0" a format string, #L119-L128 a source anchor, LS4/LS6 are linkshells, and A=FF B=0C G=41 R=C2 explains a colour-channel order. Those look like codes and are not. Also translated the eight German comments left in the theme files and ImGuiUtil. Seven of them described what a palette does to which channel, which is worth reading -- just not in a second language in an otherwise English codebase.
188 lines
6.1 KiB
C#
188 lines
6.1 KiB
C#
using System.Numerics;
|
|
using System.Text;
|
|
using Dalamud.Bindings.ImGui;
|
|
using Dalamud.Game.Text;
|
|
using Dalamud.Game.Text.SeStringHandling;
|
|
using Dalamud.Interface.Utility.Raii;
|
|
using Dalamud.Interface.Windowing;
|
|
using HellionChat.Code;
|
|
using HellionChat.Resources;
|
|
using HellionChat.Util;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace HellionChat.Ui;
|
|
|
|
internal sealed class InputPreview : Window
|
|
{
|
|
private readonly Components.ChunkRenderer _chunkRenderer;
|
|
private readonly Lender<PayloadHandler> _handlerLender;
|
|
private readonly Windows.MainWindow _mainWindow;
|
|
private readonly Components.InputBar _inputBar;
|
|
private readonly ILogger<InputPreview> _logger;
|
|
|
|
private bool _drawing;
|
|
private bool _hasEvaluation;
|
|
internal float PreviewHeight;
|
|
|
|
private int _lastLength;
|
|
private Message? _previewMessage;
|
|
|
|
internal InputPreview(
|
|
Components.ChunkRenderer chunkRenderer,
|
|
Lender<PayloadHandler> handlerLender,
|
|
Windows.MainWindow mainWindow,
|
|
Components.InputBar inputBar,
|
|
ILogger<InputPreview> logger
|
|
)
|
|
: base("##chat2-inputpreview")
|
|
{
|
|
_chunkRenderer = chunkRenderer;
|
|
_handlerLender = handlerLender;
|
|
_mainWindow = mainWindow;
|
|
_inputBar = inputBar;
|
|
_logger = logger;
|
|
|
|
Flags =
|
|
ImGuiWindowFlags.NoSavedSettings
|
|
| ImGuiWindowFlags.NoTitleBar
|
|
| ImGuiWindowFlags.NoMove
|
|
| ImGuiWindowFlags.NoResize
|
|
| ImGuiWindowFlags.NoFocusOnAppearing
|
|
| ImGuiWindowFlags.NoScrollbar;
|
|
|
|
RespectCloseHotkey = false;
|
|
DisableWindowSounds = true;
|
|
IsOpen = true;
|
|
|
|
// TODO: remove discard once logging call sites exist
|
|
_ = _logger;
|
|
}
|
|
|
|
public void Dispose() { }
|
|
|
|
private bool ValidDraw =>
|
|
!string.IsNullOrEmpty(_inputBar.PendingMessage)
|
|
&& _inputBar.PendingMessage.Length >= Plugin.Config.PreviewMinimum;
|
|
|
|
// IsDrawable gates DrawConditions; it is also consumed externally by
|
|
// any component that needs to know whether the preview popup is visible.
|
|
internal bool IsDrawable => ValidDraw && _hasEvaluation;
|
|
|
|
private static bool IsWindowMode =>
|
|
Plugin.Config.PreviewPosition is PreviewPosition.Top or PreviewPosition.Bottom;
|
|
|
|
// PreOpenCheck owns state: it runs once per frame before the visibility
|
|
// gate so Drawing/PreviewMessage/HasEvaluation stay fresh even when the
|
|
// window is not ultimately drawn. PreDraw owns position/size to avoid
|
|
// wasted computation on frames where DrawConditions returns false
|
|
// (position math only matters when the window is about to render).
|
|
// This matches the v1.5.6 UpdateConditionCheck/PreDraw split — the
|
|
// Framework.Update subscribe is removed; PreOpenCheck runs at the same
|
|
// cadence via Dalamud's WindowSystem.
|
|
public override void PreOpenCheck()
|
|
{
|
|
_drawing = ValidDraw;
|
|
if (!_drawing)
|
|
{
|
|
_lastLength = 0;
|
|
PreviewHeight = 0;
|
|
_previewMessage = null;
|
|
_hasEvaluation = false;
|
|
return;
|
|
}
|
|
|
|
if (_previewMessage == null || _lastLength != _inputBar.PendingMessage.Length)
|
|
{
|
|
_lastLength = _inputBar.PendingMessage.Length;
|
|
|
|
var bytes = Encoding.UTF8.GetBytes(_inputBar.PendingMessage.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;
|
|
}
|
|
|
|
public override bool DrawConditions()
|
|
{
|
|
return IsWindowMode && IsDrawable;
|
|
}
|
|
|
|
public override void PreDraw()
|
|
{
|
|
var pos = _mainWindow.LastWindowPos;
|
|
var size = _mainWindow.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()
|
|
{
|
|
CalculatePreviewHeight();
|
|
DrawPreview();
|
|
}
|
|
|
|
internal void CalculatePreviewHeight()
|
|
{
|
|
// Pre-draw offscreen once to measure actual rendered height; value is
|
|
// consumed next frame by PreDraw() for window sizing.
|
|
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);
|
|
_chunkRenderer.DrawChunks(_previewMessage!.Content, wrap: true, lineWidth: 0f);
|
|
}
|
|
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);
|
|
|
|
// Primary path resets the Lender counter in MainWindow.Draw();
|
|
// this fallback covers the edge-case where MainWindow is closed but
|
|
// InputPreview is still open, preventing handler pool growth.
|
|
if (!_mainWindow.IsOpen)
|
|
_handlerLender.ResetCounter();
|
|
|
|
var handler = _handlerLender.Borrow();
|
|
_chunkRenderer.DrawChunks(
|
|
_previewMessage!.Content,
|
|
wrap: true,
|
|
handler: handler,
|
|
lineWidth: 0f
|
|
);
|
|
handler.Draw();
|
|
}
|
|
}
|
|
}
|