From e01de0403a5a3739ed4554201afe3627deac1508 Mon Sep 17 00:00:00 2001 From: Jon Kazama Date: Tue, 26 May 2026 13:31:00 +0200 Subject: [PATCH] feat(input): expose state API, wire settings cog, add test hooks --- HellionChat/Plugin.cs | 2 ++ HellionChat/PluginHostFactory.cs | 3 ++- HellionChat/Ui/Components/InputBar.cs | 32 ++++++++++++++++++++++----- 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/HellionChat/Plugin.cs b/HellionChat/Plugin.cs index 293263e..94fb5b3 100755 --- a/HellionChat/Plugin.cs +++ b/HellionChat/Plugin.cs @@ -111,6 +111,7 @@ public sealed class Plugin : IAsyncDalamudPlugin internal IpcManager Ipc { get; private set; } = null!; internal ExtraChat ExtraChat { get; private set; } = null!; internal TypingIpc TypingIpc { get; private set; } = null!; + internal Ui.Components.InputBar InputBar { get; private set; } = null!; internal FontManager FontManager { get; private set; } = null!; internal Themes.ThemeRegistry ThemeRegistry { get; private set; } = null!; internal Integrations.HonorificService HonorificService { get; private set; } = null!; @@ -292,6 +293,7 @@ public sealed class Plugin : IAsyncDalamudPlugin MessageManager = _host.Services.GetRequiredService(); AutoTellTabsService = _host.Services.GetRequiredService(); + InputBar = _host.Services.GetRequiredService(); MainWindow = _host.Services.GetRequiredService(); SettingsWindow = _host.Services.GetRequiredService(); DbViewer = _host.Services.GetRequiredService(); diff --git a/HellionChat/PluginHostFactory.cs b/HellionChat/PluginHostFactory.cs index e7d4721..afcc0d8 100644 --- a/HellionChat/PluginHostFactory.cs +++ b/HellionChat/PluginHostFactory.cs @@ -139,7 +139,8 @@ internal static class PluginHostFactory sp.GetRequiredService(), sp.GetRequiredService(), sp.GetRequiredService(), - sp.GetRequiredService>() + sp.GetRequiredService>(), + () => sp.GetRequiredService().SettingsWindow.Toggle() )); services.AddSingleton(sp => new Ui.Components.StatusBar( sp.GetRequiredService(), diff --git a/HellionChat/Ui/Components/InputBar.cs b/HellionChat/Ui/Components/InputBar.cs index 54cef39..eb4e7b3 100644 --- a/HellionChat/Ui/Components/InputBar.cs +++ b/HellionChat/Ui/Components/InputBar.cs @@ -30,15 +30,19 @@ internal sealed class InputBar private readonly ThemeRegistry _themes; private readonly TokenResolver _resolver; private readonly ILogger _logger; + private readonly Action _onOpenSettings; private string _pendingMessage = string.Empty; + private bool _isFocused; + private bool? _isFocusedOverride; // Test-only; null = honour per-frame Draw() value. public InputBar( SymbolPicker symbolPicker, FontManager fonts, ThemeRegistry themes, TokenResolver resolver, - ILogger logger + ILogger logger, + Action onOpenSettings ) { _symbolPicker = symbolPicker; @@ -46,9 +50,22 @@ internal sealed class InputBar _themes = themes; _resolver = resolver; _logger = logger; + _onOpenSettings = onOpenSettings; } public string PendingMessage => _pendingMessage; + public int PendingLength => _pendingMessage.Length; + + // IsFocused respects the test override first so a SelfTest can pin focus + // state without racing against per-frame ImGui.IsItemFocused() in Draw(). + // Note: when MainWindow is closed, DrawInputField never runs, so + // _isFocused keeps the last value written by the previous draw pass. + // The consumer that actually pushes this state across the IPC boundary + // (TypingIpc.BuildState, see F3 Step 2) gates on Plugin.MainWindow.IsOpen + // itself, so the stale backing-field never leaks to subscribers. Mirroring + // the gate here would require an extra Plugin-backref in InputBar that the + // rest of the component doesn't need. + public bool IsFocused => _isFocusedOverride ?? _isFocused; public void ClearBuffer() => _pendingMessage = string.Empty; @@ -163,6 +180,7 @@ internal sealed class InputBar { TrySend(activeTab); } + _isFocused = ImGui.IsItemFocused(); } private void TrySend(Tab? activeTab) @@ -212,10 +230,7 @@ internal sealed class InputBar ImGui.SameLine(); if (ImGui.Button(FontAwesomeIcon.Cog.ToIconString())) { - // Settings toggle wires up when the plugin window registers - // its open handler; no-op until then so the button is - // visible without dragging a half-finished settings call - // into the component. + _onOpenSettings(); } if (ImGui.IsItemHovered()) { @@ -235,4 +250,11 @@ internal sealed class InputBar } } } + + // Test-only hook; do not call from production code. + internal void TestSetPendingMessageForSelfTest(string value) => _pendingMessage = value; + + // Test-only hook; do not call from production code. Pass null to release the + // override and let Draw()'s ImGui.IsItemFocused() result take over again. + internal void TestSetFocusedForSelfTest(bool? value) => _isFocusedOverride = value; }