acdream/src/AcDream.UI.ImGui/ImGuiPanelHost.cs
Erik a7dbce3474 feat(ui): AcDream.UI.ImGui backend — Hexa.NET.ImGui + Silk.NET input bridge
Second piece of Phase D.2a: the ImGui-specific backend that implements
AcDream.UI.Abstractions' IPanelRenderer / IPanelHost. No GameWindow
hookup yet — compiles standalone for clean review before integration.

Packages:
  * Hexa.NET.ImGui 2.2.9 (auto-generated from cimgui 1.92.2b)
  * Hexa.NET.ImGui.Backends 1.0.18 (consolidated — OpenGL3 is here)
  * Silk.NET.Input 2.23.0 + Silk.NET.OpenGL 2.23.0 (matches AcDream.App)

Files:

  ImGuiBootstrapper.cs
    One-shot static Initialize(glslVersion) / Shutdown() pair. Creates
    the ImGui context, applies dark style, enables NavEnableKeyboard,
    and boots ImGuiImplOpenGL3. Re-init is a no-op.

  SilkInputBridge.cs
    Event-driven Silk.NET -> ImGui IO bridge. Subscribes on construction;
    Dispose() unsubscribes. Covers:
      - KeyDown/Up -> ImGui.AddKeyEvent with modifier latching
        (Ctrl/Shift/Alt/Super routed via both ModXxx flags AND named
        key events so both IsKeyPressed checks and ImGui shortcut
        matching work)
      - KeyChar -> AddInputCharacter for text fields
      - MouseMove -> AddMousePosEvent
      - MouseDown/Up -> AddMouseButtonEvent (L=0, R=1, M=2)
      - Scroll -> AddMouseWheelEvent (both axes)
    Silk.NET.Input.Key -> ImGuiKey map covers WASD, arrows, modifiers,
    letters, digits, function keys. Unmapped keys silently ignored.
    BeginFrame(displaySize, dt) sets IO.DisplaySize + IO.DeltaTime.

  ImGuiPanelRenderer.cs
    IPanelRenderer impl — one-line wrappers on ImGui.Begin/End,
    TextUnformatted, SameLine, Separator, ProgressBar. The ONLY place
    Hexa.NET.ImGui types appear outside bootstrap/input plumbing. Panels
    still never import ImGui.

  ImGuiPanelHost.cs
    IPanelHost impl. Dictionary keyed by IPanel.Id for idempotent
    Register. RenderAll iterates visible panels and calls their Render.
    Does NOT call ImGui.NewFrame / ImGui.Render — ownership belongs to
    the caller (GameWindow) so GL state is explicit. Diagnostic `Count`
    property.

No behavior change yet; next commit wires this into GameWindow behind
ACDREAM_DEVTOOLS=1 and ships the first visible VitalsPanel.
2026-04-25 00:29:09 +02:00

46 lines
1.5 KiB
C#

using AcDream.UI.Abstractions;
namespace AcDream.UI.ImGui;
/// <summary>
/// <see cref="IPanelHost"/> implementation for the ImGui backend. Owns the
/// registered panel set; iterates + draws every frame when the caller is
/// inside an ImGui frame (between <c>ImGui.NewFrame</c> and
/// <c>ImGui.Render</c>).
///
/// <para>
/// <b>This class does not call <c>ImGui.NewFrame</c> / <c>ImGui.Render</c>
/// itself.</b> Those belong to the caller (GameWindow) so GL-state
/// ownership is explicit and the render-loop integration point is obvious.
/// </para>
/// </summary>
public sealed class ImGuiPanelHost : IPanelHost
{
private readonly Dictionary<string, IPanel> _panels = new();
private readonly ImGuiPanelRenderer _renderer = new();
/// <inheritdoc />
public void Register(IPanel panel)
{
ArgumentNullException.ThrowIfNull(panel);
_panels[panel.Id] = panel; // idempotent by Id
}
/// <inheritdoc />
public void Unregister(string panelId) => _panels.Remove(panelId);
/// <inheritdoc />
public void RenderAll(PanelContext ctx)
{
// Order-independent — ImGui windows stack in the order they're drawn
// for focus purposes but we have <=1 panel in D.2a.
foreach (var panel in _panels.Values)
{
if (!panel.IsVisible) continue;
panel.Render(ctx, _renderer);
}
}
/// <summary>Current registered count (for diagnostics).</summary>
public int Count => _panels.Count;
}