feat(keybind): cycle tabs and switch channel with pill sync
This commit is contained in:
@@ -501,20 +501,40 @@ internal unsafe class KeybindManager : IDisposable
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
Plugin.KeyState[currentBest.Item1] = false;
|
Plugin.KeyState[currentBest.Item1] = false;
|
||||||
if (!KeybindsToIntercept.ContainsKey(currentBest.Item2))
|
if (!KeybindsToIntercept.TryGetValue(currentBest.Item2, out var info))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Re-surface the chat-activation entry point retired in v1.6.0: a chat-open
|
// Re-surface the chat-activation entry point retired in v1.6.0: a chat-open
|
||||||
// keybind shows + focuses the window, restoring it from a user-hide or a
|
// keybind shows + focuses the window, restoring it from a user-hide or a
|
||||||
// closed state. Channel/prefill routing from the bind stays out of scope.
|
// closed state.
|
||||||
Plugin.Instance.MainWindow?.ActivateChat();
|
Plugin.Instance.MainWindow?.ActivateChat();
|
||||||
|
|
||||||
|
// Direct channel-switch binds (CMD_SAY/PARTY/numbered linkshells/…): switch the
|
||||||
|
// game channel AND mirror it onto the active tab so the input pill shows the
|
||||||
|
// real send target (pill-sync, Flo decision 2026-06-15). Rotation binds (REPLY /
|
||||||
|
// linkshell-cycle, Rotate != None) and the Permanent nuance stay deferred to the
|
||||||
|
// keybind-routing follow-cycle.
|
||||||
|
if (info.Channel is { } channel && info.Rotate == RotateMode.None)
|
||||||
|
{
|
||||||
|
Plugin.Instance.Functions.Chat.SetChannel(channel);
|
||||||
|
if (Plugin.Instance.MainWindow?.ActiveTab is { } activeTab)
|
||||||
|
{
|
||||||
|
activeTab.CurrentChannel.SetChannel(channel);
|
||||||
|
activeTab.CurrentChannel.TellTarget = null;
|
||||||
|
activeTab.CurrentChannel.ResetTempChannel();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tab-cycle dispatch is offline until the new chat layer surfaces a
|
// Prefill text binds (CMD_COMMAND seeds "/"): drop the token into our input.
|
||||||
// ChangeTabDelta entry point and pop-out input bars come back online.
|
if (info.Text is { } text)
|
||||||
|
Plugin.Instance.InputBar.SetPendingMessage(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cycle the main window's active tab. Pop-out input-bar focus-forward stays
|
||||||
|
// deferred (no focus contract yet) — main-window tabs only.
|
||||||
private void DispatchTabDelta(int delta)
|
private void DispatchTabDelta(int delta)
|
||||||
{
|
{
|
||||||
_ = delta;
|
Plugin.Instance.MainWindow?.ChangeTabDelta(delta);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Keybind GetKeybind(string id)
|
private static Keybind GetKeybind(string id)
|
||||||
|
|||||||
@@ -160,6 +160,25 @@ internal sealed class MainWindow : Window
|
|||||||
TabLifecycleHelpers.OnTabActivated(tab, previous);
|
TabLifecycleHelpers.OnTabActivated(tab, previous);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tab-cycle entry point for the ChatTabForward/Backward keybinds. Empty list is a
|
||||||
|
// no-op; a null active tab seeds tabs[0]; a single-tab cycle that lands on the
|
||||||
|
// already-active tab is a no-op (ActivateTab early-returns on the same reference).
|
||||||
|
// Routes through ActivateTab so the cycle strips stale tell state + re-derives the
|
||||||
|
// channel exactly like a sidebar/top-tab click. Pop-out focus-forward stays
|
||||||
|
// deferred (no focus contract) — main-window tabs only.
|
||||||
|
internal void ChangeTabDelta(int delta)
|
||||||
|
{
|
||||||
|
var tabs = Plugin.Config.Tabs;
|
||||||
|
if (tabs.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var idx = _activeTab is null ? 0 : tabs.IndexOf(_activeTab);
|
||||||
|
if (idx < 0)
|
||||||
|
idx = 0; // active tab not in the list (mid-strip) -> start from the first
|
||||||
|
|
||||||
|
ActivateTab(tabs[TabLifecycleHelpers.WrapTabIndex(idx, delta, tabs.Count)]);
|
||||||
|
}
|
||||||
|
|
||||||
// Internal accessors for self-tests so the probes can reach the live
|
// Internal accessors for self-tests so the probes can reach the live
|
||||||
// component without exposing them as public surface.
|
// component without exposing them as public surface.
|
||||||
internal Components.Sidebar GetSidebarForSelfTest() => _sidebar;
|
internal Components.Sidebar GetSidebarForSelfTest() => _sidebar;
|
||||||
|
|||||||
@@ -83,4 +83,15 @@ internal static class TabLifecycleHelpers
|
|||||||
tab.CurrentChannel.TellTarget = null;
|
tab.CurrentChannel.TellTarget = null;
|
||||||
tab.CurrentChannel.ResetTempChannel();
|
tab.CurrentChannel.ResetTempChannel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wrap-around tab index for keybind cycling. Pure so the Build-Suite can test the
|
||||||
|
// wrap math without a live window. count == 0 returns 0 (the caller dead-zones
|
||||||
|
// before activating); negative deltas wrap correctly via the double-mod.
|
||||||
|
// TEST-MIRROR: ../../Hellion Build test/_Helpers/TabLifecycleHelpersTests.cs
|
||||||
|
internal static int WrapTabIndex(int current, int delta, int count)
|
||||||
|
{
|
||||||
|
if (count <= 0)
|
||||||
|
return 0;
|
||||||
|
return ((current + delta) % count + count) % count;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user