Adds 14 widget signatures to IPanelRenderer + ImGuiPanelRenderer impl: TextColored, CollapsingHeader, TreeNode/TreePop, Checkbox, Button, Combo, SliderFloat, PlotLines, BeginTable/TableNextColumn/EndTable, InputTextSubmit (Enter-key submit), Spacing, Dummy, TextWrapped. InputTextSubmit uses ImGuiInputTextFlags.EnterReturnsTrue and clears the buffer + emits via `out submitted` on the frame Enter is pressed. PlotLines passes `ref values[0]` with empty-array guard. CollapsingHeader defaultOpen=true uses ImGuiTreeNodeFlags.DefaultOpen (= 0x20). FakePanelRenderer test double records (Method, Args) tuples and exposes knobs to drive ref/out values. 17 new tests dispatch through IPanelRenderer (not the concrete fake) so tests fail to compile when the interface itself lacks a method - real RED -> GREEN signal. Tests: 26 -> 43 in UI.Abstractions.Tests. Total solution 881 green. Foundation for Phase I.2 (DebugPanel) and I.4 (ChatPanel input field). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
153 lines
5.1 KiB
C#
153 lines
5.1 KiB
C#
using System.Numerics;
|
|
using AcDream.UI.Abstractions;
|
|
using ImGuiNET;
|
|
|
|
namespace AcDream.UI.ImGui;
|
|
|
|
/// <summary>
|
|
/// <see cref="IPanelRenderer"/> implemented as thin wrappers around
|
|
/// ImGui.NET calls. This is the ONLY place where ImGuiNET types appear
|
|
/// outside of bootstrap plumbing — panels that need a feature must
|
|
/// extend the abstraction here, not by importing ImGuiNET in panel
|
|
/// files.
|
|
/// </summary>
|
|
public sealed class ImGuiPanelRenderer : IPanelRenderer
|
|
{
|
|
/// <inheritdoc />
|
|
public bool Begin(string title) => ImGuiNET.ImGui.Begin(title);
|
|
|
|
/// <inheritdoc />
|
|
public void End() => ImGuiNET.ImGui.End();
|
|
|
|
/// <inheritdoc />
|
|
public void Text(string text) => ImGuiNET.ImGui.TextUnformatted(text);
|
|
|
|
/// <inheritdoc />
|
|
public void SameLine() => ImGuiNET.ImGui.SameLine();
|
|
|
|
/// <inheritdoc />
|
|
public void Separator() => ImGuiNET.ImGui.Separator();
|
|
|
|
/// <inheritdoc />
|
|
public void ProgressBar(float fraction, float width, string? overlay = null)
|
|
{
|
|
// Clamp defensively; ImGui clamps internally but the abstraction
|
|
// contract promises to handle out-of-range values.
|
|
if (fraction < 0f) fraction = 0f;
|
|
else if (fraction > 1f) fraction = 1f;
|
|
|
|
var size = new Vector2(width, 0f); // height 0 → ImGui picks based on font
|
|
ImGuiNET.ImGui.ProgressBar(fraction, size, overlay ?? string.Empty);
|
|
}
|
|
|
|
// -- Phase I.1 widget extensions ---------------------------------
|
|
|
|
/// <inheritdoc />
|
|
public void TextColored(Vector4 rgba, string text)
|
|
=> ImGuiNET.ImGui.TextColored(rgba, text);
|
|
|
|
/// <inheritdoc />
|
|
public bool CollapsingHeader(string label, bool defaultOpen = true)
|
|
=> ImGuiNET.ImGui.CollapsingHeader(
|
|
label,
|
|
defaultOpen ? ImGuiTreeNodeFlags.DefaultOpen : ImGuiTreeNodeFlags.None);
|
|
|
|
/// <inheritdoc />
|
|
public bool TreeNode(string label) => ImGuiNET.ImGui.TreeNode(label);
|
|
|
|
/// <inheritdoc />
|
|
public void TreePop() => ImGuiNET.ImGui.TreePop();
|
|
|
|
/// <inheritdoc />
|
|
public bool Checkbox(string label, ref bool value)
|
|
=> ImGuiNET.ImGui.Checkbox(label, ref value);
|
|
|
|
/// <inheritdoc />
|
|
public bool Button(string label) => ImGuiNET.ImGui.Button(label);
|
|
|
|
/// <inheritdoc />
|
|
public bool Combo(string label, ref int selectedIndex, string[] items)
|
|
=> ImGuiNET.ImGui.Combo(label, ref selectedIndex, items, items.Length);
|
|
|
|
/// <inheritdoc />
|
|
public bool SliderFloat(string label, ref float value, float min, float max)
|
|
=> ImGuiNET.ImGui.SliderFloat(label, ref value, min, max);
|
|
|
|
/// <inheritdoc />
|
|
public void PlotLines(
|
|
string label,
|
|
float[] values,
|
|
int count,
|
|
int offset = 0,
|
|
string? overlay = null,
|
|
float? min = null,
|
|
float? max = null,
|
|
Vector2? size = null)
|
|
{
|
|
// ImGui.NET 1.91.6.1's PlotLines binding takes `ref float values`
|
|
// (pointer-to-first-element semantics) plus a separate values_count
|
|
// and values_offset. The "no fixed bound" / "default size" sentinels
|
|
// are float.MaxValue and Vector2.Zero respectively — we pass those
|
|
// when the caller leaves the optional args null.
|
|
if (count <= 0 || values.Length == 0)
|
|
{
|
|
// Nothing to plot — emit the label so layout doesn't shift but
|
|
// skip the native call (ref to values[0] would NRE on empty).
|
|
ImGuiNET.ImGui.TextUnformatted(label);
|
|
return;
|
|
}
|
|
|
|
float scaleMin = min ?? float.MaxValue;
|
|
float scaleMax = max ?? float.MaxValue;
|
|
Vector2 graphSize = size ?? Vector2.Zero;
|
|
ImGuiNET.ImGui.PlotLines(
|
|
label,
|
|
ref values[0],
|
|
count,
|
|
offset,
|
|
overlay ?? string.Empty,
|
|
scaleMin,
|
|
scaleMax,
|
|
graphSize);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public void BeginTable(string id, int columns)
|
|
=> ImGuiNET.ImGui.BeginTable(id, columns);
|
|
|
|
/// <inheritdoc />
|
|
public void TableNextColumn() => ImGuiNET.ImGui.TableNextColumn();
|
|
|
|
/// <inheritdoc />
|
|
public void EndTable() => ImGuiNET.ImGui.EndTable();
|
|
|
|
/// <inheritdoc />
|
|
public bool InputTextSubmit(string label, ref string buffer, int maxLen, out string? submitted)
|
|
{
|
|
// EnterReturnsTrue: the call returns true on the frame the user
|
|
// pressed Enter. On every other frame ImGui still mutates `buffer`
|
|
// as the user types; we just don't surface a submit.
|
|
bool entered = ImGuiNET.ImGui.InputText(
|
|
label,
|
|
ref buffer,
|
|
(uint)maxLen,
|
|
ImGuiInputTextFlags.EnterReturnsTrue);
|
|
if (entered)
|
|
{
|
|
submitted = buffer;
|
|
buffer = string.Empty; // contract: clear for next frame
|
|
return true;
|
|
}
|
|
submitted = null;
|
|
return false;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public void Spacing() => ImGuiNET.ImGui.Spacing();
|
|
|
|
/// <inheritdoc />
|
|
public void Dummy(Vector2 size) => ImGuiNET.ImGui.Dummy(size);
|
|
|
|
/// <inheritdoc />
|
|
public void TextWrapped(string text) => ImGuiNET.ImGui.TextWrapped(text);
|
|
}
|