C2/C3: restore rotation keybinds (REPLY/LS-cycle) + focus contract routing on the focused chat surface

This commit is contained in:
2026-06-16 14:09:28 +02:00
parent ea3f00f107
commit fa20b53455
5 changed files with 213 additions and 30 deletions
+13 -1
View File
@@ -13,7 +13,7 @@ namespace HellionChat.Ui.Windows;
// via ctor — see plan §B.2. The ###id carries the slot index so all N
// instances are unique for WindowSystem.AddWindow and ImGui state is stable
// per slot (not per bound tab).
internal sealed class ChannelPopoutWindow : Window
internal sealed class ChannelPopoutWindow : Window, IFocusableChatWindow
{
private readonly int _slotIndex;
private readonly MessageList _messages;
@@ -75,6 +75,18 @@ internal sealed class ChannelPopoutWindow : Window
IsOpen = false;
}
// IFocusableChatWindow — this pop-out's own InputBar carries the focus state
// the keybind tail checks when deciding whether to route at this surface (C3).
public bool HasFocusedInput => _input.IsFocused;
// Arm-and-hold the one-frame Activate flag; the pop-out's Draw applies the
// ImGui focus next frame. Framework-thread safe (field write only).
public void RequestInputFocus()
{
BringToFront();
_input.Activate = true;
}
public override void PreDraw()
{
// Gate the native title bar on the user toggle (1.5.6 parity). DrawHeader
@@ -0,0 +1,14 @@
namespace HellionChat.Ui.Windows;
// Focus contract shared by the main window and each pop-out so the keybind tail
// can route channel-set / REPLY / prefill to whichever surface currently owns the
// input focus, without the KeybindManager reaching into either window's privates.
// HasFocusedInput reads the bound InputBar's per-frame focus state; RequestInputFocus
// only arms the one-frame Activate flag (ImGui focus is frame-bound — never call
// SetKeyboardFocusHere from the framework thread).
internal interface IFocusableChatWindow
{
bool HasFocusedInput { get; }
void RequestInputFocus();
}
+13 -1
View File
@@ -16,7 +16,7 @@ namespace HellionChat.Ui.Windows;
// Components are fully qualified through the Ui.Components prefix so the
// old Ui.StatusBar type (still alive until the cleanup block removes it)
// cannot shadow the new layer through parent-namespace resolution.
internal sealed class MainWindow : Window
internal sealed class MainWindow : Window, IFocusableChatWindow
{
private const float DefaultWidth = 620f;
private const float DefaultHeight = 340f;
@@ -205,6 +205,18 @@ internal sealed class MainWindow : Window
_input.Activate = true;
}
// IFocusableChatWindow — the keybind tail resolves which surface owns the
// input focus before routing a channel-set/REPLY/prefill at it (C3).
public bool HasFocusedInput => _input.IsFocused;
// Arm-and-hold: field writes only, safe from the framework thread; the draw
// path applies the actual ImGui focus next frame (same path as ActivateChat).
public void RequestInputFocus()
{
BringToFront();
_input.Activate = true;
}
// new-shadow on Window.Toggle so the open path also writes Config. A user-hide
// counts as "not visible", so /hellion is a reliable one-press recovery even when
// the Enter keybind can't fire (DirectChat / a focused game text field).