using System.Numerics;
using AcDream.UI.Abstractions;
using ImGuiNET;
namespace AcDream.UI.ImGui;
///
/// 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.
///
public sealed class ImGuiPanelRenderer : IPanelRenderer
{
///
public bool Begin(string title) => ImGuiNET.ImGui.Begin(title);
///
public void End() => ImGuiNET.ImGui.End();
///
public void Text(string text) => ImGuiNET.ImGui.TextUnformatted(text);
///
public void SameLine() => ImGuiNET.ImGui.SameLine();
///
public void Separator() => ImGuiNET.ImGui.Separator();
///
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 ---------------------------------
///
public void TextColored(Vector4 rgba, string text)
=> ImGuiNET.ImGui.TextColored(rgba, text);
///
public bool CollapsingHeader(string label, bool defaultOpen = true)
=> ImGuiNET.ImGui.CollapsingHeader(
label,
defaultOpen ? ImGuiTreeNodeFlags.DefaultOpen : ImGuiTreeNodeFlags.None);
///
public bool TreeNode(string label) => ImGuiNET.ImGui.TreeNode(label);
///
public void TreePop() => ImGuiNET.ImGui.TreePop();
///
public bool Checkbox(string label, ref bool value)
=> ImGuiNET.ImGui.Checkbox(label, ref value);
///
public bool Button(string label) => ImGuiNET.ImGui.Button(label);
///
public bool Combo(string label, ref int selectedIndex, string[] items)
=> ImGuiNET.ImGui.Combo(label, ref selectedIndex, items, items.Length);
///
public bool SliderFloat(string label, ref float value, float min, float max)
=> ImGuiNET.ImGui.SliderFloat(label, ref value, min, max);
///
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);
}
///
public void BeginTable(string id, int columns)
=> ImGuiNET.ImGui.BeginTable(id, columns);
///
public void TableNextColumn() => ImGuiNET.ImGui.TableNextColumn();
///
public void EndTable() => ImGuiNET.ImGui.EndTable();
///
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;
}
///
public void Spacing() => ImGuiNET.ImGui.Spacing();
///
public void Dummy(Vector2 size) => ImGuiNET.ImGui.Dummy(size);
///
public void TextWrapped(string text) => ImGuiNET.ImGui.TextWrapped(text);
// -- Phase J Tier 3 — scrollable child for chat-style layouts ---------
///
public bool BeginChild(string id, Vector2 size, bool border = false)
{
// ImGuiChildFlags has changed names across ImGui.NET versions
// (Border vs Borders); 0x01 is the stable bit value for "draw
// a border". Casting from a numeric literal sidesteps the
// version-skew without requiring a hard reference to either
// enum spelling.
bool open = ImGuiNET.ImGui.BeginChild(id, size, (ImGuiChildFlags)(border ? 0x01 : 0));
if (open)
{
// Title-bar-only drag fix (chat tail specifically): empty
// clicks inside a scrollable child fall through to the
// parent window for drag-init, which is exactly what the
// user reported in the chat panel ("clicking anywhere
// moves the window"). An InvisibleButton sized to the
// child's content region absorbs those clicks so they
// don't propagate. Real widgets drawn afterwards still
// claim their own clicks (click priority = "last drawn,
// first checked"). Wheel scrolling is window-level, not
// item-level, so the absorber doesn't interfere with
// the chat tail's auto-scroll.
//
// Scoped to BeginChild only (NOT Begin) because Begin's
// body might host tab bars whose hit-testing competes with
// an absorber on equal terms — adding it at Begin level
// broke Settings tab clicks.
var avail = ImGuiNET.ImGui.GetContentRegionAvail();
if (avail.X > 0f && avail.Y > 0f)
{
var savedCursor = ImGuiNET.ImGui.GetCursorPos();
ImGuiNET.ImGui.InvisibleButton("##childbodyabsorb", avail);
ImGuiNET.ImGui.SetCursorPos(savedCursor);
}
}
return open;
}
///
public void EndChild() => ImGuiNET.ImGui.EndChild();
///
public float FrameHeightWithSpacing() => ImGuiNET.ImGui.GetFrameHeightWithSpacing();
///
public void SetScrollHereY(float ratio) => ImGuiNET.ImGui.SetScrollHereY(ratio);
///
public void SetKeyboardFocusHere() => ImGuiNET.ImGui.SetKeyboardFocusHere();
// -- Phase K.3 — main menu bar -----------------------------------------
///
public bool BeginMainMenuBar() => ImGuiNET.ImGui.BeginMainMenuBar();
///
public void EndMainMenuBar() => ImGuiNET.ImGui.EndMainMenuBar();
///
public bool BeginMenu(string label) => ImGuiNET.ImGui.BeginMenu(label);
///
public void EndMenu() => ImGuiNET.ImGui.EndMenu();
///
public bool MenuItem(string label, string? shortcut = null)
=> shortcut is null
? ImGuiNET.ImGui.MenuItem(label)
: ImGuiNET.ImGui.MenuItem(label, shortcut);
// -- Tab bar -----------------------------------------------------------
///
public bool BeginTabBar(string id) => ImGuiNET.ImGui.BeginTabBar(id);
///
public void EndTabBar() => ImGuiNET.ImGui.EndTabBar();
///
public bool BeginTabItem(string label) => ImGuiNET.ImGui.BeginTabItem(label);
///
public void EndTabItem() => ImGuiNET.ImGui.EndTabItem();
// -- Selectable / copyable text ---------------------------------------
///
public void TextMultilineReadOnly(string id, string content, Vector2 size)
{
// ImGui's InputTextMultiline takes a `ref string` even with the
// ReadOnly flag — we just hand it a local copy. maxLength caps
// what the user could type if ReadOnly were ever cleared; we
// size it to the current content (+1 for ImGui's internal NUL
// terminator in some bindings). Min of 1 keeps the empty case
// from confusing native bindings.
string buffer = content;
uint maxLen = (uint)System.Math.Max(content.Length + 1, 1);
ImGuiNET.ImGui.InputTextMultiline(id, ref buffer, maxLen, size,
ImGuiInputTextFlags.ReadOnly);
}
}