fix(ui): wire chat send and fix sidebar icons, channel pill, scrollbar

Five smoke bugs from the first in-game test:

1. Sidebar showed fa-comment for every tab because the resolve path
   only honoured tab.Icon. Channel-type fallback restored — auto-tell
   tabs render the envelope, the rest map their first SelectedChannels
   key onto FontAwesome (Linkshells → link, FC → users, Party →
   user-friends, System/Echo → cog, emotes → comments).

2. InputBar's channel pill read from tab.Channel (the saved default),
   which is null on most non-FC tabs and rendered as "—". The pill now
   reads tab.CurrentChannel.Channel first so the runtime input state
   surfaces on every tab, with the saved default as a second fallback.

3. MessageList was making its own ImRaii.Child inside the main-area
   child MainWindow already owns. That nested scroll created the second
   scrollbar on the outer window. The component now lays out directly
   into the parent's scroll region.

4. The input field reserved 90px for the three FontAwesome buttons,
   which clipped them on standard frame padding. Reserve raised to
   130px so the trailing buttons fully render.

5. Pressing Enter dropped the buffer — there was no send wiring. The
   field now uses ImGuiInputTextFlags.EnterReturnsTrue and routes the
   pending message through GameFunctions.ChatBox.SendMessage. Lines
   that don't start with a slash get the active channel's prefix
   prepended so typing in /fc lands on the FC channel instead of the
   current game-side default.

InputBar gains an ILogger<InputBar> for the send-failure path; the
DI registration in PluginHostFactory is updated to match.
This commit is contained in:
2026-05-23 20:50:54 +02:00
parent 0109bfd222
commit 52b0fa7c67
4 changed files with 122 additions and 19 deletions
+68 -10
View File
@@ -3,28 +3,33 @@ using Dalamud.Bindings.ImGui;
using Dalamud.Interface;
using Dalamud.Interface.Utility.Raii;
using HellionChat.Code;
using HellionChat.GameFunctions;
using HellionChat.Themes;
using HellionChat.Ui.StyleEngine;
using HellionChat.Util;
using Microsoft.Extensions.Logging;
namespace HellionChat.Ui.Components;
// Bottom input row: channel pill, text field, quick buttons. Channel pill
// recolours by tab type — cyan accent for a normal channel, ember accent
// for a tell. Send wiring lands when the main window assembles the
// components; for now this layer only handles buffer state and the symbol
// picker overlay.
// for a tell. Enter on the input field sends through ChatBox; messages
// that don't already start with a slash get the active channel's prefix
// prepended so a typed line in /fc reaches free-company chat instead of
// the current game-side channel.
internal sealed class InputBar
{
public const float Height = 32f;
private const float PillHeight = 22f;
private const float PillPaddingX = 8f;
private const int BufferCapacity = 500;
private const float QuickButtonsReserve = 130f;
private readonly SymbolPicker _symbolPicker;
private readonly FontManager _fonts;
private readonly ThemeRegistry _themes;
private readonly TokenResolver _resolver;
private readonly ILogger<InputBar> _logger;
private string _pendingMessage = string.Empty;
@@ -32,13 +37,15 @@ internal sealed class InputBar
SymbolPicker symbolPicker,
FontManager fonts,
ThemeRegistry themes,
TokenResolver resolver
TokenResolver resolver,
ILogger<InputBar> logger
)
{
_symbolPicker = symbolPicker;
_fonts = fonts;
_themes = themes;
_resolver = resolver;
_logger = logger;
}
public string PendingMessage => _pendingMessage;
@@ -62,7 +69,7 @@ internal sealed class InputBar
DrawChannelPill(activeTab, isTell, pillAbgr, pillTextAbgr);
ImGui.SameLine();
DrawInputField();
DrawInputField(activeTab);
ImGui.SameLine();
DrawQuickButtons();
@@ -77,8 +84,17 @@ internal sealed class InputBar
{
if (isTell && tab?.TellTarget is { } t && t.IsSet())
return $"→ {t.Name}";
if (tab?.Channel is { } ch)
return ch.ToChatType().Name();
// CurrentChannel carries the runtime input state; Tab.Channel is the
// saved default and is null for most non-FC tabs, which produced
// the "—" placeholder users saw.
var current = tab?.CurrentChannel?.Channel ?? InputChannel.Invalid;
if (current != InputChannel.Invalid)
return current.ToChatType().Name();
if (tab?.Channel is { } saved)
return saved.ToChatType().Name();
return "—";
}
@@ -98,10 +114,52 @@ internal sealed class InputBar
ImGui.Dummy(new Vector2(width, PillHeight));
}
private void DrawInputField()
private void DrawInputField(Tab? activeTab)
{
ImGui.SetNextItemWidth(-90f);
ImGui.InputText("##hellion-input", ref _pendingMessage, BufferCapacity);
ImGui.SetNextItemWidth(-QuickButtonsReserve);
if (
ImGui.InputText(
"##hellion-input",
ref _pendingMessage,
BufferCapacity,
ImGuiInputTextFlags.EnterReturnsTrue
)
)
{
TrySend(activeTab);
}
}
private void TrySend(Tab? activeTab)
{
var text = _pendingMessage.Trim();
if (string.IsNullOrEmpty(text))
return;
// Slash commands route through verbatim — the game's chat parser
// handles /tell, /fc, /hellion etc. on its own. Other text gets
// the active channel's prefix so the line lands on the channel
// the user is reading instead of the game-side default.
string toSend;
if (text.StartsWith('/'))
{
toSend = text;
}
else
{
var current = activeTab?.CurrentChannel?.Channel ?? InputChannel.Invalid;
toSend = current == InputChannel.Invalid ? text : $"{current.Prefix()} {text}";
}
try
{
ChatBox.SendMessage(toSend);
_pendingMessage = string.Empty;
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to send chat message ({Length} chars)", toSend.Length);
}
}
private void DrawQuickButtons()