feat(ui): #25 Phase K.3 — Settings panel + click-to-rebind + Phase K shipped
Phase K final commit. Settings panel with click-to-rebind UX on top of the K.1+K.2 input architecture, plus the roadmap / ISSUES / memory updates that retire Phase K. InputDispatcher gains BeginCapture / CancelCapture / IsCapturing / SetBindings — modal capture suppresses normal action firing for the next chord. Esc cancels (returns sentinel default chord); modifier-only keys don't complete capture; non-modifier key down with current modifier mask completes. IPanelRenderer + ImGuiPanelRenderer + FakePanelRenderer gain BeginMainMenuBar / EndMainMenuBar / BeginMenu / EndMenu / MenuItem primitives. SettingsVM owns a draft copy of KeyBindings with explicit Save / Cancel / Reset semantics. Click-to-rebind enters dispatcher capture mode; on chord captured, conflict-detect against draft (excluding the action being rebound itself); surface a ConflictPrompt when the chord collides; ResolveConflict(replace=true|false) commits or reverts. ResetActionToDefault restores a single action to RetailDefaults(); ResetAllToDefaults rebuilds the entire draft. Save invokes the onSave callback (which writes JSON + swaps the live dispatcher's bindings). SettingsPanel renders 8 retail-keymap-categorized CollapsingHeader sections (Movement, Postures, Camera, Combat, UI panels, Chat, Hotbar, Emotes). Per action: name + current binding(s) summary + "Rebind"/"Reset" buttons. Conflict prompt at the top when pending. Save / Cancel / "Reset all to retail defaults" at the top. GameWindow registers SettingsPanel + wires F11 → ToggleOptionsPanel → IsVisible toggle, plus a top-of-frame ImGui MainMenuBar with View → Settings/Vitals/Chat/Debug entries (calls ImGui directly — the abstraction methods exist for backend portability but the host doesn't own a menu-bar surface). Tests: +37 across InputDispatcherCaptureTests (7), IPanelRendererMainMenuBarTests (9), SettingsVMTests (13), SettingsPanelTests (8). Solution total 1220 green. Roadmap (docs/plans/2026-04-11-roadmap.md) appends Phase K shipped section after Phase J with K.1a–K.3 commit SHAs. ISSUES.md files Phase L deferred work as #L.1–#L.8 (hotbar UI, spellbook favorites, combat-mode dispatch, F-key panels, floating chat windows, UI layout save/load, joystick bindings, plugin input subscription) and adds #21–#25 to Recently closed. project_input_pipeline.md updated to shipped state. CLAUDE.md gets an input-pipeline reference. Closes Phase K. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
af74eac0c2
commit
f42c164b90
14 changed files with 1567 additions and 5 deletions
|
|
@ -789,8 +789,8 @@ public sealed class GameWindow : IDisposable
|
|||
// bars surface only after the first PlayerDescription has
|
||||
// populated LocalPlayer (Issue #5).
|
||||
_vitalsVm = new AcDream.UI.Abstractions.Panels.Vitals.VitalsVM(Combat, LocalPlayer);
|
||||
_panelHost.Register(
|
||||
new AcDream.UI.Abstractions.Panels.Vitals.VitalsPanel(_vitalsVm));
|
||||
_vitalsPanel = new AcDream.UI.Abstractions.Panels.Vitals.VitalsPanel(_vitalsVm);
|
||||
_panelHost.Register(_vitalsPanel);
|
||||
|
||||
// ChatPanel: reads the tail of the shared ChatLog. No GUID
|
||||
// dependency — works pre-login (empty) and post-login (live
|
||||
|
|
@ -863,7 +863,38 @@ public sealed class GameWindow : IDisposable
|
|||
_debugPanel = new AcDream.UI.Abstractions.Panels.Debug.DebugPanel(_debugVm);
|
||||
_panelHost.Register(_debugPanel);
|
||||
|
||||
Console.WriteLine("devtools: ImGui panel host ready (VitalsPanel + ChatPanel + DebugPanel registered)");
|
||||
// Phase K.3 — Settings panel. SettingsVM owns a draft
|
||||
// copy of the active KeyBindings. Save replaces the
|
||||
// dispatcher's live table + writes JSON; Cancel reverts
|
||||
// the draft. Construction is null-safe vs. the
|
||||
// dispatcher because the dispatcher is built earlier in
|
||||
// the same OnLoad path (see _inputDispatcher field).
|
||||
if (_inputDispatcher is not null)
|
||||
{
|
||||
_settingsVm = new AcDream.UI.Abstractions.Panels.Settings.SettingsVM(
|
||||
persisted: _keyBindings,
|
||||
dispatcher: _inputDispatcher,
|
||||
onSave: bindings =>
|
||||
{
|
||||
_inputDispatcher.SetBindings(bindings);
|
||||
try
|
||||
{
|
||||
bindings.SaveToFile(
|
||||
AcDream.UI.Abstractions.Input.KeyBindings.DefaultPath());
|
||||
Console.WriteLine(
|
||||
"keybinds: saved to "
|
||||
+ AcDream.UI.Abstractions.Input.KeyBindings.DefaultPath());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"keybinds: save failed: {ex.Message}");
|
||||
}
|
||||
});
|
||||
_settingsPanel = new AcDream.UI.Abstractions.Panels.Settings.SettingsPanel(_settingsVm);
|
||||
_panelHost.Register(_settingsPanel);
|
||||
}
|
||||
|
||||
Console.WriteLine("devtools: ImGui panel host ready (VitalsPanel + ChatPanel + DebugPanel + SettingsPanel registered)");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
@ -872,9 +903,12 @@ public sealed class GameWindow : IDisposable
|
|||
_imguiBootstrap = null;
|
||||
_panelHost = null;
|
||||
_vitalsVm = null;
|
||||
_vitalsPanel = null;
|
||||
_debugVm = null;
|
||||
_debugPanel = null;
|
||||
_chatPanel = null;
|
||||
_settingsVm = null;
|
||||
_settingsPanel = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -4198,6 +4232,35 @@ public sealed class GameWindow : IDisposable
|
|||
var ctx = new AcDream.UI.Abstractions.PanelContext(
|
||||
(float)deltaSeconds,
|
||||
bus);
|
||||
|
||||
// Phase K.3 — top-of-screen menu bar. Provides discoverable
|
||||
// entries for users who don't memorize the F-keys (View →
|
||||
// Settings / Vitals / Chat / Debug). Uses ImGuiNET directly
|
||||
// here because the panel host doesn't own a menu-bar
|
||||
// surface; the abstraction (BeginMainMenuBar / BeginMenu /
|
||||
// MenuItem) exists for backend portability + tests but only
|
||||
// gets exercised here once per frame.
|
||||
if (ImGuiNET.ImGui.BeginMainMenuBar())
|
||||
{
|
||||
if (ImGuiNET.ImGui.BeginMenu("View"))
|
||||
{
|
||||
if (_settingsPanel is not null
|
||||
&& ImGuiNET.ImGui.MenuItem("Settings", "F11"))
|
||||
_settingsPanel.IsVisible = !_settingsPanel.IsVisible;
|
||||
if (_vitalsPanel is not null
|
||||
&& ImGuiNET.ImGui.MenuItem("Vitals"))
|
||||
_vitalsPanel.IsVisible = !_vitalsPanel.IsVisible;
|
||||
if (_chatPanel is not null
|
||||
&& ImGuiNET.ImGui.MenuItem("Chat"))
|
||||
_chatPanel.IsVisible = !_chatPanel.IsVisible;
|
||||
if (_debugPanel is not null
|
||||
&& ImGuiNET.ImGui.MenuItem("Debug", "F1"))
|
||||
_debugPanel.IsVisible = !_debugPanel.IsVisible;
|
||||
ImGuiNET.ImGui.EndMenu();
|
||||
}
|
||||
ImGuiNET.ImGui.EndMainMenuBar();
|
||||
}
|
||||
|
||||
_panelHost.RenderAll(ctx);
|
||||
_imguiBootstrap.Render();
|
||||
}
|
||||
|
|
@ -5048,6 +5111,13 @@ public sealed class GameWindow : IDisposable
|
|||
// DevToolsEnabled construction block; null otherwise.
|
||||
private AcDream.UI.Abstractions.Panels.Chat.ChatPanel? _chatPanel;
|
||||
|
||||
// Phase K.3 — Settings panel (click-to-rebind keymap UI). Hidden by
|
||||
// default; F11 / View → Settings toggles. Null when devtools are off.
|
||||
private AcDream.UI.Abstractions.Panels.Settings.SettingsPanel? _settingsPanel;
|
||||
private AcDream.UI.Abstractions.Panels.Settings.SettingsVM? _settingsVm;
|
||||
// Vitals panel reference cached for the View menu's toggle entry.
|
||||
private AcDream.UI.Abstractions.Panels.Vitals.VitalsPanel? _vitalsPanel;
|
||||
|
||||
// ── K.1b: dispatcher action handler ──────────────────────────────────
|
||||
//
|
||||
// SINGLE place where every game-side keyboard/mouse-button reaction
|
||||
|
|
@ -5175,6 +5245,14 @@ public sealed class GameWindow : IDisposable
|
|||
_chatPanel?.FocusInput();
|
||||
break;
|
||||
|
||||
case AcDream.UI.Abstractions.Input.InputAction.ToggleOptionsPanel:
|
||||
// K.3: F11 toggles the Settings panel. Null-safe vs.
|
||||
// devtools-off / panel-not-registered — the [input] log
|
||||
// line above still records the press regardless.
|
||||
if (_settingsPanel is not null)
|
||||
_settingsPanel.IsVisible = !_settingsPanel.IsVisible;
|
||||
break;
|
||||
|
||||
case AcDream.UI.Abstractions.Input.InputAction.EscapeKey:
|
||||
if (_cameraController?.IsFlyMode == true)
|
||||
_cameraController.ToggleFly(); // exit fly, release cursor
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue