diff --git a/HellionChat/PluginHostFactory.cs b/HellionChat/PluginHostFactory.cs index bd13342..e7d4721 100644 --- a/HellionChat/PluginHostFactory.cs +++ b/HellionChat/PluginHostFactory.cs @@ -138,7 +138,8 @@ internal static class PluginHostFactory sp.GetRequiredService(), sp.GetRequiredService(), sp.GetRequiredService(), - sp.GetRequiredService() + sp.GetRequiredService(), + sp.GetRequiredService>() )); services.AddSingleton(sp => new Ui.Components.StatusBar( sp.GetRequiredService(), diff --git a/HellionChat/Ui/Components/InputBar.cs b/HellionChat/Ui/Components/InputBar.cs index a10cca3..0601264 100644 --- a/HellionChat/Ui/Components/InputBar.cs +++ b/HellionChat/Ui/Components/InputBar.cs @@ -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 _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 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() diff --git a/HellionChat/Ui/Components/MessageList.cs b/HellionChat/Ui/Components/MessageList.cs index 1bb7c67..7bb09b2 100644 --- a/HellionChat/Ui/Components/MessageList.cs +++ b/HellionChat/Ui/Components/MessageList.cs @@ -37,10 +37,9 @@ internal sealed class MessageList return; } - using var child = ImRaii.Child("##hellion-messages", new Vector2(-1, -1)); - if (!child.Success) - return; - + // No own ImRaii.Child here — MainWindow already wraps the message + // area in one. Nesting would give the window two stacked scrolls + // and a runaway content-height computation. var theme = _themes.Active; var textAbgr = ColourUtil.RgbaToAbgr(theme.Colors.TextPrimary); var mutedAbgr = ColourUtil.RgbaToAbgr(theme.Colors.TextMuted); @@ -49,10 +48,8 @@ internal sealed class MessageList var compact = Plugin.Config.UseCompactDensity; // Track whether the user was pinned to the bottom before this frame - // so newly arriving rows do not yank them up — the standard - // chat-window expectation. Read the scroll state before drawing - // anything inside the child so the comparison is against the - // previous frame's max. + // so newly arriving rows do not yank them up. The check runs against + // the parent child's scroll state, which is the one MainWindow owns. var pinnedToBottom = ImGui.GetScrollY() >= ImGui.GetScrollMaxY() - 1f; if (compact) diff --git a/HellionChat/Ui/Components/Sidebar.cs b/HellionChat/Ui/Components/Sidebar.cs index e1f1e68..3b3bda0 100644 --- a/HellionChat/Ui/Components/Sidebar.cs +++ b/HellionChat/Ui/Components/Sidebar.cs @@ -2,6 +2,7 @@ using System.Numerics; using Dalamud.Bindings.ImGui; using Dalamud.Interface; using Dalamud.Interface.Utility.Raii; +using HellionChat.Code; using HellionChat.Themes; using HellionChat.Ui.StyleEngine; using HellionChat.Util; @@ -164,9 +165,55 @@ internal sealed class Sidebar !string.IsNullOrWhiteSpace(tab.Icon) && IconByName.TryGetValue(tab.Icon, out var mapped) ) return mapped; + + // Auto-tell tabs always show the envelope, regardless of what their + // SelectedChannels filter is set to. + if (tab.IsTempTab) + return FontAwesomeIcon.Envelope; + + // Channel-type fallback. The v1.5.6 TabIconGlyphResolver did the + // same thing — picks the first selected channel and maps its + // ChatType to a category icon so tabs without a user-set icon + // still look distinct. + if (tab.SelectedChannels.Count > 0) + return ResolveByChannelType(tab.SelectedChannels.Keys.First()); + return FontAwesomeIcon.Comment; } + private static FontAwesomeIcon ResolveByChannelType(ChatType type) => + type switch + { + ChatType.TellIncoming or ChatType.TellOutgoing => FontAwesomeIcon.Envelope, + ChatType.FreeCompany + or ChatType.FreeCompanyAnnouncement + or ChatType.FreeCompanyLoginLogout => FontAwesomeIcon.Users, + ChatType.Linkshell1 + or ChatType.Linkshell2 + or ChatType.Linkshell3 + or ChatType.Linkshell4 + or ChatType.Linkshell5 + or ChatType.Linkshell6 + or ChatType.Linkshell7 + or ChatType.Linkshell8 + or ChatType.CrossLinkshell1 + or ChatType.CrossLinkshell2 + or ChatType.CrossLinkshell3 + or ChatType.CrossLinkshell4 + or ChatType.CrossLinkshell5 + or ChatType.CrossLinkshell6 + or ChatType.CrossLinkshell7 + or ChatType.CrossLinkshell8 => FontAwesomeIcon.Link, + ChatType.Party or ChatType.CrossParty => FontAwesomeIcon.UserFriends, + ChatType.Alliance => FontAwesomeIcon.Users, + ChatType.NoviceNetwork or ChatType.NoviceNetworkSystem => FontAwesomeIcon.Users, + ChatType.PvpTeam or ChatType.PvpTeamAnnouncement or ChatType.PvpTeamLoginLogout => + FontAwesomeIcon.Users, + ChatType.System or ChatType.Echo => FontAwesomeIcon.Cog, + ChatType.CustomEmote or ChatType.StandardEmote => FontAwesomeIcon.Comments, + _ => FontAwesomeIcon.Comment, + }; + private void LogPopOutStub(Tab tab) { // The channel-popout pool is built in a later cycle; logging here