diff --git a/src/AcDream.UI.ImGui/ImGuiPanelRenderer.cs b/src/AcDream.UI.ImGui/ImGuiPanelRenderer.cs
index 94f09d4..4aa94ae 100644
--- a/src/AcDream.UI.ImGui/ImGuiPanelRenderer.cs
+++ b/src/AcDream.UI.ImGui/ImGuiPanelRenderer.cs
@@ -14,30 +14,7 @@ namespace AcDream.UI.ImGui;
public sealed class ImGuiPanelRenderer : IPanelRenderer
{
///
- 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;
- }
+ public bool Begin(string title) => ImGuiNET.ImGui.Begin(title);
///
public void End() => ImGuiNET.ImGui.End();
@@ -178,12 +155,41 @@ public sealed class ImGuiPanelRenderer : IPanelRenderer
///
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.
- => ImGuiNET.ImGui.BeginChild(id, size, (ImGuiChildFlags)(border ? 0x01 : 0));
+ 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();