feat(popouts): bring the pop-out windows up to the rest

Three things, all visible in one screenshot.

The tab name was drawn twice. The pop-out has a title bar carrying the tab name
and a header row underneath repeating it -- the same string, one line apart. The
header now only draws when the title bar is off, which is the case it was
written for.

The close button was ImGui.Button, so it took the theme's button colours. On
several themes that is a bright magenta plate sitting next to plain text, making
the way out of the window the loudest thing in it. It uses the plugin's own icon
button now: glyph contrast-checked against the surface, danger colour on hover
only.

And the message area gets the same floor as the main log, with the same
restraint -- no accent wash, motes at a tenth. Pop-outs were the last surface
still sitting on flat background while every other window had depth.
This commit is contained in:
2026-08-18 19:09:40 +02:00
parent d30922af8c
commit 9c7be4106d
2 changed files with 57 additions and 13 deletions
+4 -1
View File
@@ -324,7 +324,10 @@ internal static class PluginHostFactory
sp.GetRequiredService<Ui.CommandHelpWindow>()
),
sp.GetRequiredService<ILogger<Ui.Windows.ChannelPopoutWindow>>(),
sp.GetRequiredService<FontManager>()
sp.GetRequiredService<FontManager>(),
sp.GetRequiredService<Ui.StyleEngine.SurfaceBackdrop>(),
sp.GetRequiredService<ThemeRegistry>(),
sp.GetRequiredService<Ui.StyleEngine.TokenResolver>()
)
);
services.AddSingleton(sp => new Ui.Windows.ChannelPopoutPool(
+53 -12
View File
@@ -3,7 +3,10 @@ using Dalamud.Bindings.ImGui;
using Dalamud.Interface;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Interface.Windowing;
using HellionChat.Themes;
using HellionChat.Ui.Components;
using HellionChat.Ui.StyleEngine.Widgets;
using HellionChat.Util;
using Microsoft.Extensions.Logging;
namespace HellionChat.Ui.Windows;
@@ -20,13 +23,19 @@ internal sealed class ChannelPopoutWindow : Window, IFocusableChatWindow
private readonly InputBar _input;
private readonly ILogger<ChannelPopoutWindow> _logger;
private readonly FontManager _fonts;
private readonly Ui.StyleEngine.SurfaceBackdrop _backdrop;
private readonly ThemeRegistry _themes;
private readonly Ui.StyleEngine.TokenResolver _resolver;
public ChannelPopoutWindow(
int slotIndex,
MessageList messages,
InputBar input,
ILogger<ChannelPopoutWindow> logger,
FontManager fonts
FontManager fonts,
Ui.StyleEngine.SurfaceBackdrop backdrop,
ThemeRegistry themes,
Ui.StyleEngine.TokenResolver resolver
)
: base($"{Plugin.PluginName}###hellion_popout_{slotIndex}")
{
@@ -35,6 +44,9 @@ internal sealed class ChannelPopoutWindow : Window, IFocusableChatWindow
_input = input;
_logger = logger;
_fonts = fonts;
_backdrop = backdrop;
_themes = themes;
_resolver = resolver;
IsOpen = false;
RespectCloseHotkey = false;
ShowCloseButton = false;
@@ -103,7 +115,11 @@ internal sealed class ChannelPopoutWindow : Window, IFocusableChatWindow
if (Bound is null)
return;
DrawHeader(Bound);
// Only when the window has no title bar of its own. With the bar on,
// this row repeated the tab name directly underneath it -- the same
// string twice, one line apart.
if (!Plugin.Config.ShowPopOutTitleBar)
DrawHeader(Bound);
// The header close button can unbind us mid-frame (CloseRequested ->
// pool.TryClose -> Unbind nulls Bound). Re-check before the body so we
@@ -125,7 +141,13 @@ internal sealed class ChannelPopoutWindow : Window, IFocusableChatWindow
)
{
if (body.Success)
{
// Same floor as the main window's log, and the same reasoning:
// no accent wash and barely any motes, because a chat log is read
// line by line.
_backdrop.Draw(accentWashHeight: 0f, moteIntensity: 0.10f, strength: 0.45f);
_messages.Draw(Bound);
}
}
_input.Draw(Bound);
@@ -137,18 +159,37 @@ internal sealed class ChannelPopoutWindow : Window, IFocusableChatWindow
// close button is the canonical "send the tab back" affordance for v1.8.0.
// PartnerHonorific is deferred (HonorificService has no per-target title,
// plan §D / Sub-Spec WARN-8) — no honorific row here.
var colors = _themes.Active.Colors;
var text = ColourUtil.RgbaToAbgr(_resolver.Resolve(Ui.StyleEngine.Token.Text, colors));
var surface = ColourUtil.RgbaToAbgr(
_resolver.Resolve(Ui.StyleEngine.Token.SurfaceBase, colors)
);
var danger = ColourUtil.RgbaToAbgr(
_resolver.Resolve(Ui.StyleEngine.Token.StatusDanger, colors)
);
ImGui.TextUnformatted(tab.Name);
ImGui.SameLine();
using (_fonts.FontAwesome.Push())
// The plugin's own icon button rather than ImGui.Button. The stock one
// took the theme's button colours, which on several themes is a bright
// magenta plate next to plain text -- the loudest thing in the window
// being the way out of it.
var size = ImGui.GetFrameHeight();
ImGui.SameLine(ImGui.GetContentRegionAvail().X - size);
var (clicked, _) = IconButton.Draw(
ImGui.GetID($"popout.close.{_slotIndex}"),
new Vector2(size, size),
FontAwesomeIcon.Times,
ColourUtil.EnsureContrast(text, surface, 3f),
danger,
_fonts.FontAwesome
);
if (clicked)
{
ImGui.SameLine(ImGui.GetContentRegionAvail().X - ImGui.GetFrameHeight());
if (ImGui.Button($"{FontAwesomeIcon.Times.ToIconString()}##popin-{_slotIndex}"))
{
// Pop-In: release the slot via the pool (not a bare Unbind, which
// would orphan the slot — the pool owns the slot bookkeeping).
CloseRequested?.Invoke(tab.Identifier);
}
// Pop-In: release the slot via the pool (not a bare Unbind, which
// would orphan the slot — the pool owns the slot bookkeeping).
CloseRequested?.Invoke(tab.Identifier);
}
ImGui.Separator();
}
}