fix(popouts): restore a way to close a pop-out

Last commit hid the header row when the title bar was on, to stop the tab name
appearing twice. The close button was in that row, and the title bar carries
none of its own -- ShowCloseButton is off because closing has to go through the
pool to release the slot. So a pop-out with its title bar on could not be closed
at all.

Pop-in moves into the input row, alongside the settings and hide buttons that
are already there. It is red, contrast-checked against the button plate it sits
on, and carries a tooltip, since a bare X next to an emoji picker does not say
where the tab is going.

Wired by setter after construction rather than through the constructor: the
window is what the button has to call, and it does not exist yet while its own
input row is being built. Routing it through the graph would close a
factory-callsite cycle MS.DI cannot detect.

The header row is now just the tab name, drawn only when there is no title bar
to carry it.

InputBar_PopIn_Tooltip ships in all 25 languages, including the Designer entry.
New strings get translated with the change from here on, not batched.
This commit is contained in:
2026-08-18 19:21:24 +02:00
parent 051fd64857
commit d1e12ce866
28 changed files with 541 additions and 475 deletions
+38 -1
View File
@@ -51,6 +51,13 @@ internal sealed class InputBar
// Null in pop-outs (those have their own close button). Hides the main window.
private readonly Action? _onHideWindow;
// Set only for pop-outs, and after construction: the window does not exist
// yet while its own input row is being built, and routing it through the DI
// graph would close a factory-callsite cycle MS.DI cannot see. Its presence
// is what puts the pop-in button in the row, so the main window cannot grow
// one by accident.
internal Action? OnPopIn { get; set; }
private string _pendingMessage = string.Empty;
private bool _isFocused;
private bool _wasInputTextHovered;
@@ -541,7 +548,7 @@ internal sealed class InputBar
tooltip = HellionStrings.InputBar_Settings_Tooltip;
// Hides the window (1.5.6 UserHide). One-way — Enter brings it back.
// Main window only (pop-outs have their own close); last in the row.
// Main window only; last in the row there.
if (Plugin.Config.ShowHideButton && _onHideWindow is not null)
{
ImGui.SameLine();
@@ -550,6 +557,36 @@ internal sealed class InputBar
if (ImGui.IsItemHovered())
tooltip = HellionStrings.InputBar_HideChat_Tooltip;
}
// Pop-in, in the pop-out windows only. It sits here rather than in a
// header row because a pop-out with its title bar on had no header at
// all -- and the title bar carries no close button, since closing has
// to go through the pool to release the slot.
if (OnPopIn is not null)
{
ImGui.SameLine();
// Red, and contrast-checked against the button plate it sits on
// rather than taken raw: several themes ship a danger colour that
// is nearly invisible on their own button fill.
var danger = ColourUtil.RgbaToAbgr(
_resolver.Resolve(Token.StatusDanger, _themes.Active.Colors)
);
var plate = ColourUtil.Vector4ToAbgr(ImGui.GetStyle().Colors[(int)ImGuiCol.Button]);
using (
ImRaii.PushColor(
ImGuiCol.Text,
ColourUtil.RgbaToVector4(
ColourUtil.RgbaToAbgr(ColourUtil.EnsureContrast(danger, plate, 3f))
)
)
)
{
if (ImGui.Button(FontAwesomeIcon.Times.ToIconString()))
OnPopIn();
}
if (ImGui.IsItemHovered())
tooltip = HellionStrings.InputBar_PopIn_Tooltip;
}
}
if (tooltip is not null)