Right-clicking a name or an item inside a pop-out did nothing at all. One payload handler is shared by the main window, every pop-out and the input preview, and it holds a single popup state. The main window is registered first, so it draws first, finds no open popup in its own scope, reads that as "closed" and clears the state -- before the window that actually opened the popup gets its turn. The popup now belongs to the surface that opened it. The others leave its state alone instead of dropping it. The rule itself sits in its own helper because the handler pulls in Dalamud and cannot be loaded from a test.
189 lines
6.2 KiB
C#
189 lines
6.2 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();
|
|
handler.ActiveSurface = this;
|
|
_chunkRenderer.DrawChunks(
|
|
_previewMessage!.Content,
|
|
wrap: true,
|
|
handler: handler,
|
|
lineWidth: 0f
|
|
);
|
|
handler.Draw();
|
|
}
|
|
}
|
|
}
|