diff --git a/ChatTwo/GameFunctions/KeybindManager.cs b/ChatTwo/GameFunctions/KeybindManager.cs index 88d4020..d15bd73 100644 --- a/ChatTwo/GameFunctions/KeybindManager.cs +++ b/ChatTwo/GameFunctions/KeybindManager.cs @@ -465,12 +465,21 @@ internal unsafe class KeybindManager : IDisposable { } } - // v0.6.0 — central dispatch for ChatTabForward/Backward so Task 25 - // can extend it with focus-aware routing to pop-out ChatInputBars. - // Right now both windows share the main ChatLogWindow.ChangeTabDelta, - // identical to v0.5.x behavior. + // v0.6.0 — central dispatch for ChatTabForward/Backward. If a pop-out + // window currently has its compact input focused, the keybind is + // forwarded into that pop-out's ChatInputBar so the user navigates + // tabs in the window they are typing in. Otherwise the main window + // handles it (= v0.5.x behavior). private void DispatchTabDelta(int delta) { + foreach (var popout in Plugin.ChatLogWindow.ActivePopouts) + { + if (popout.HasFocusedInputBar && popout.InputBar != null) + { + popout.InputBar.HandleKeybindForward(delta); + return; + } + } Plugin.ChatLogWindow.ChangeTabDelta(delta); } diff --git a/ChatTwo/InputHistoryService.cs b/ChatTwo/InputHistoryService.cs index 5ebfec2..bc65431 100644 --- a/ChatTwo/InputHistoryService.cs +++ b/ChatTwo/InputHistoryService.cs @@ -5,7 +5,11 @@ namespace ChatTwo; // Hellion Chat — v0.6.0 shared input history. Replaces the embedded // ChatLogWindow.InputBacklog so that pop-out windows with their own // ChatInputBar can navigate the same Up/Down history as the main window. -// Newest entry at index 0; consecutive duplicates are collapsed. +// Index semantics are kept identical to the v0.5.x InputBacklog: +// index 0 = oldest entry +// index Count - 1 = newest entry +// Push performs move-to-newest deduplication: existing entries are +// removed before the new one is appended at the end. public static class InputHistoryService { private const int MaxSize = 30; @@ -13,6 +17,8 @@ public static class InputHistoryService public static IReadOnlyList Entries => _entries; + public static int Count => _entries.Count; + public static void Push(string entry) { if (string.IsNullOrWhiteSpace(entry)) @@ -20,14 +26,20 @@ public static class InputHistoryService var trimmed = entry.Trim(); - // Drop consecutive duplicates so spamming the same line does not - // pollute the history with repeats. - if (_entries.Count > 0 && _entries[0] == trimmed) - return; + // Move-to-newest: existing entries are removed before the append + // so the same line typed twice does not occupy two history slots. + for (var i = 0; i < _entries.Count; i++) + { + if (_entries[i] == trimmed) + { + _entries.RemoveAt(i); + break; + } + } - _entries.Insert(0, trimmed); + _entries.Add(trimmed); if (_entries.Count > MaxSize) - _entries.RemoveAt(_entries.Count - 1); + _entries.RemoveAt(0); } public static string? GetByCursor(int cursor) @@ -36,6 +48,4 @@ public static class InputHistoryService return null; return _entries[cursor]; } - - public static int Count => _entries.Count; } diff --git a/ChatTwo/Ui/ChatLogWindow.cs b/ChatTwo/Ui/ChatLogWindow.cs index e39111e..33763a8 100644 --- a/ChatTwo/Ui/ChatLogWindow.cs +++ b/ChatTwo/Ui/ChatLogWindow.cs @@ -1490,6 +1490,13 @@ public sealed class ChatLogWindow : Window internal readonly List PopOutDocked = []; internal readonly HashSet PopOutWindows = []; + + // v0.6.0 — live enumeration of all active Popout windows so the + // KeybindManager can find a focused ChatInputBar to forward tab-cycle + // keybinds to. Filter on IsOpen prevents touching closed-but-still- + // registered popouts. + internal IEnumerable ActivePopouts => + Plugin.WindowSystem.Windows.OfType().Where(p => p.IsOpen); private void AddPopOutsToDraw() { HandlerLender.ResetCounter(); diff --git a/ChatTwo/Ui/Popout.cs b/ChatTwo/Ui/Popout.cs index c3f85ab..0333433 100644 --- a/ChatTwo/Ui/Popout.cs +++ b/ChatTwo/Ui/Popout.cs @@ -15,6 +15,13 @@ internal class Popout : Window private long FrameTime; // set every frame private long LastActivityTime = Environment.TickCount64; + // v0.6.0 — optional input bar inside the pop-out window. Lazy-allocated + // when the user enables Tab.PopOutInputEnabled and torn down when the + // toggle is turned off (independent text buffer is intentionally + // discarded — see v0.6.0 spec edge-case P1). + public ChatInputBar? InputBar { get; private set; } + public bool HasFocusedInputBar => InputBar?.IsFocused ?? false; + public Popout(ChatLogWindow chatLogWindow, Tab tab, int idx) : base($"{tab.Name}##popout") { ChatLogWindow = chatLogWindow; @@ -93,8 +100,33 @@ internal class Popout : Window ImGui.Separator(); } + // v0.6.0 — pop-out optional input bar. Reserve height first so the + // message log draws into the right region; only shown when the + // per-tab toggle is on. Toggle-OFF resets InputBar so the next + // toggle-ON gives a fresh buffer (no stale text persists). + var inputEnabled = Tab.PopOutInputEnabled; + if (!inputEnabled && InputBar != null) + { + InputBar = null; + } + if (inputEnabled) + { + InputBar ??= new ChatInputBar(ChatLogWindow.Plugin, ChatLogWindow, () => Tab); + } + + var inputBarHeight = inputEnabled + ? ImGui.GetFrameHeightWithSpacing() + ImGui.GetStyle().ItemSpacing.Y + : 0f; + var handler = ChatLogWindow.HandlerLender.Borrow(); - ChatLogWindow.DrawMessageLog(Tab, handler, ImGui.GetContentRegionAvail().Y, false); + var logHeight = ImGui.GetContentRegionAvail().Y - inputBarHeight; + ChatLogWindow.DrawMessageLog(Tab, handler, logHeight, false); + + if (inputEnabled && InputBar != null) + { + ImGui.Separator(); + InputBar.RenderCompact(); + } if (ImGui.IsWindowHovered(ImGuiHoveredFlags.ChildWindows)) LastActivityTime = FrameTime;