InputPreview was only rendered for PreviewPosition.Top/Bottom (the DrawConditions IsWindowMode gate). Inside-mode (the default) and Tooltip-mode had no caller at all because v1.5.6's inline-render path lived on the deleted ChatLogWindow and was not migrated to the v1.7.0 Components-Layer. Wire Inside-mode by calling CalculatePreviewHeight + DrawPreview inline from MainWindow.DrawMainArea between the message-list child and the input bar, with the message-list height reserved for the preview block. Wire Tooltip-mode by sampling IsItemHovered() on the input text widget inside InputBar.DrawInputField (analog to the existing _isFocused = ImGui.IsItemFocused() idiom on the same line) and exposing it as WasInputTextHovered; MainWindow opens the tooltip after _input.Draw when both the hover-flag and PreviewPosition.Tooltip are active. Plan-drift acknowledged: the plan stated Plugin.InputPreview is statically reachable, but the property was declared as an instance member on Plugin.cs:101. Hoisted to internal static to match the plan's intention (analog to Plugin.Config); updated the single external instance-access site in PluginLifecycle.RegisterWindows to the type-qualified form. Verified in-game: Inside-mode preview block appears between message list and input bar on first keystroke; tooltip-mode shows preview on text-field hover only; Top/Bottom-mode unchanged; empty buffer hides the preview in all modes. dotnet build clean, dotnet csharpier check clean.
190 lines
6.4 KiB
C#
190 lines
6.4 KiB
C#
using System.Numerics;
|
|
using Dalamud.Bindings.ImGui;
|
|
using Dalamud.Interface.Utility;
|
|
using Dalamud.Interface.Utility.Raii;
|
|
using Dalamud.Interface.Windowing;
|
|
using HellionChat.Util;
|
|
|
|
namespace HellionChat.Ui.Windows;
|
|
|
|
// Top-level chat window assembled from the components layer. Layout from
|
|
// top to bottom: honorific header, horizontal body with sidebar + main
|
|
// area (messages + input bar), and the status strip pinned to the
|
|
// bottom. The window-level theme push stays on the global plugin draw
|
|
// path for now — this window only composes content.
|
|
//
|
|
// Components are fully qualified through the Ui.Components prefix so the
|
|
// old Ui.StatusBar type (still alive until the cleanup block removes it)
|
|
// cannot shadow the new layer through parent-namespace resolution.
|
|
internal sealed class MainWindow : Window
|
|
{
|
|
private const float DefaultWidth = 620f;
|
|
private const float DefaultHeight = 340f;
|
|
private const float MinWidth = 480f;
|
|
private const float MinHeight = 260f;
|
|
|
|
private readonly Components.HonorificHeader _honorific;
|
|
private readonly Components.Sidebar _sidebar;
|
|
private readonly Components.MessageList _messages;
|
|
private readonly Components.InputBar _input;
|
|
private readonly Components.StatusBar _status;
|
|
private readonly Lender<PayloadHandler> _handlerLender;
|
|
|
|
private Tab? _activeTab;
|
|
|
|
public Vector2 LastWindowPos { get; private set; } = Vector2.Zero;
|
|
public Vector2 LastWindowSize { get; private set; } = Vector2.Zero;
|
|
internal unsafe ImGuiViewport* LastViewport;
|
|
|
|
public MainWindow(
|
|
Components.HonorificHeader honorific,
|
|
Components.Sidebar sidebar,
|
|
Components.MessageList messages,
|
|
Components.InputBar input,
|
|
Components.StatusBar status,
|
|
Lender<PayloadHandler> handlerLender
|
|
)
|
|
: base($"{Plugin.PluginName}###hellion-main")
|
|
{
|
|
_honorific = honorific;
|
|
_sidebar = sidebar;
|
|
_messages = messages;
|
|
_input = input;
|
|
_status = status;
|
|
_handlerLender = handlerLender;
|
|
|
|
Size = new Vector2(DefaultWidth, DefaultHeight);
|
|
SizeCondition = ImGuiCond.FirstUseEver;
|
|
SizeConstraints = new WindowSizeConstraints
|
|
{
|
|
MinimumSize = new Vector2(MinWidth, MinHeight),
|
|
MaximumSize = new Vector2(float.MaxValue, float.MaxValue),
|
|
};
|
|
// The message list owns its own scroll inside the body child;
|
|
// the outer window must not show a second scrollbar.
|
|
Flags = ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse;
|
|
IsOpen = Plugin.Config.MainWindowOpen;
|
|
RespectCloseHotkey = false;
|
|
}
|
|
|
|
public Tab? ActiveTab => _activeTab;
|
|
|
|
// Internal accessors for self-tests so the probes can reach the live
|
|
// component without exposing them as public surface.
|
|
internal Components.Sidebar GetSidebarForSelfTest() => _sidebar;
|
|
|
|
internal Components.HonorificHeader GetHonorificHeaderForSelfTest() => _honorific;
|
|
|
|
// new-shadow on Window.Toggle so the open path also writes Config —
|
|
// OnClose already covers the close path through the base behaviour.
|
|
public new void Toggle()
|
|
{
|
|
IsOpen = !IsOpen;
|
|
Plugin.Config.MainWindowOpen = IsOpen;
|
|
}
|
|
|
|
public override void OnClose()
|
|
{
|
|
Plugin.Config.MainWindowOpen = false;
|
|
}
|
|
|
|
public override void Draw()
|
|
{
|
|
LastWindowPos = ImGui.GetWindowPos();
|
|
LastWindowSize = ImGui.GetWindowSize();
|
|
unsafe
|
|
{
|
|
LastViewport = ImGui.GetWindowViewport().Handle;
|
|
}
|
|
|
|
// Primary pool-reset path; InputPreview has a defensive fallback for the MainWindow-closed edge case.
|
|
_handlerLender.ResetCounter();
|
|
|
|
// First-frame seed: the active tab defaults to the first persisted
|
|
// tab so the message list isn't empty on a clean session.
|
|
if (_activeTab is null && Plugin.Config.Tabs.Count > 0)
|
|
_activeTab = Plugin.Config.Tabs[0];
|
|
|
|
var statusHeight = Components.StatusBar.Height;
|
|
|
|
using (var body = ImRaii.Child("##hellion-body", new Vector2(-1f, -statusHeight)))
|
|
{
|
|
if (body.Success)
|
|
DrawBody();
|
|
}
|
|
|
|
_messages.DrawHandlerPopups();
|
|
_status.Draw(_activeTab);
|
|
}
|
|
|
|
private void DrawBody()
|
|
{
|
|
var bodyWidth = ImGui.GetContentRegionAvail().X;
|
|
_honorific.Draw(bodyWidth);
|
|
|
|
using (ImRaii.Group())
|
|
{
|
|
_sidebar.Draw(bodyWidth, Plugin.Config.Tabs, ref _activeTab);
|
|
}
|
|
|
|
ImGui.SameLine();
|
|
|
|
using (ImRaii.Group())
|
|
{
|
|
DrawMainArea();
|
|
}
|
|
}
|
|
|
|
private void DrawMainArea()
|
|
{
|
|
var inputHeight = Components.InputBar.Height;
|
|
|
|
// Shrink the message child when Inside-mode preview is active so the
|
|
// inline preview block does not overlap the message list. PreviewHeight
|
|
// lags one frame behind on the very first keystroke (same as v1.5.6).
|
|
var previewHeight =
|
|
Plugin.Config.PreviewPosition is PreviewPosition.Inside
|
|
&& Plugin.InputPreview.IsDrawable
|
|
? Plugin.InputPreview.PreviewHeight
|
|
: 0f;
|
|
|
|
using (
|
|
var messages = ImRaii.Child(
|
|
"##hellion-main-area",
|
|
new Vector2(-1f, -(inputHeight + previewHeight))
|
|
)
|
|
)
|
|
{
|
|
if (messages.Success)
|
|
_messages.Draw(_activeTab!);
|
|
}
|
|
|
|
// Inside-mode inline render: measure first so PreviewHeight is fresh
|
|
// for the next frame's reservation, then draw between messages and input.
|
|
if (
|
|
Plugin.Config.PreviewPosition is PreviewPosition.Inside
|
|
&& Plugin.InputPreview.IsDrawable
|
|
)
|
|
{
|
|
Plugin.InputPreview.CalculatePreviewHeight();
|
|
Plugin.InputPreview.DrawPreview();
|
|
}
|
|
|
|
_input.Draw(_activeTab);
|
|
|
|
// Tooltip-mode: sampled hover-state from InputBar reflects the actual
|
|
// InputText widget (after-Draw IsItemHovered would target a QuickButton).
|
|
// ImRaii.Tooltip has no Success guard — BeginTooltip always runs in ctor.
|
|
if (
|
|
Plugin.Config.PreviewPosition is PreviewPosition.Tooltip
|
|
&& Plugin.InputPreview.IsDrawable
|
|
&& _input.WasInputTextHovered
|
|
)
|
|
{
|
|
ImGui.SetNextWindowSize(new Vector2(500 * ImGuiHelpers.GlobalScale, -1));
|
|
using var tooltip = ImRaii.Tooltip();
|
|
Plugin.InputPreview.DrawPreview();
|
|
}
|
|
}
|
|
}
|