fix(ui): title-bar-only drag — absorb body clicks via InvisibleButton

User reported that clicking anywhere in a panel (chat, settings, etc)
started a window drag. ImGui's default window-drag init fires on any
body click that doesn't land on an "active" widget — empty space
between Text widgets, BeginChild background pad, etc. all qualified.

Fix: right after Begin, place an InvisibleButton sized to the full
body content region, then reset the cursor so subsequent panel
content renders normally. ImGui's click-priority is "last drawn,
first checked" — so real widgets drawn afterwards still claim their
own clicks. The InvisibleButton catches ONLY clicks on empty body
space, marks itself as the active item, and ImGui's window-drag
check sees ActiveId != 0 → no drag.

Net effect: title bar still drags (ImGui default), body never
drags. Applies uniformly to every panel that calls
IPanelRenderer.Begin (chat / settings / vitals / debug).

dotnet build green (0 warnings).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-04-26 22:58:05 +02:00
parent 4c75ced92b
commit 627325559c

View file

@ -14,7 +14,30 @@ namespace AcDream.UI.ImGui;
public sealed class ImGuiPanelRenderer : IPanelRenderer
{
/// <inheritdoc />
public bool Begin(string title) => ImGuiNET.ImGui.Begin(title);
public bool Begin(string title)
{
bool open = ImGuiNET.ImGui.Begin(title);
if (open)
{
// Title-bar-only drag: ImGui's default lets the user drag the
// window by clicking on the empty body background (because a
// window-drag is initiated whenever a body click lands without
// any widget being "active"). Filling the body with an
// InvisibleButton absorbs those stray clicks — real widgets
// drawn afterwards still claim their own clicks because click
// priority is "last drawn, first checked", so the button
// catches only empty-space clicks. Net effect: title bar
// still drags (ImGui default), body never does.
var avail = ImGuiNET.ImGui.GetContentRegionAvail();
if (avail.X > 0f && avail.Y > 0f)
{
var savedCursor = ImGuiNET.ImGui.GetCursorPos();
ImGuiNET.ImGui.InvisibleButton("##bodydragabsorb", avail);
ImGuiNET.ImGui.SetCursorPos(savedCursor);
}
}
return open;
}
/// <inheritdoc />
public void End() => ImGuiNET.ImGui.End();