From df9f2fd3dab5a60d9e418cabfe61ade1e98ff4cc Mon Sep 17 00:00:00 2001 From: Erik Date: Sun, 26 Apr 2026 23:10:01 +0200 Subject: [PATCH] fix(ui): wrap chat panel body in outer BeginChild so drag-trap covers it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The InvisibleButton drag-trap inside BeginChild only catches clicks inside that specific child. Chat had widgets OUTSIDE the inner ##chattail child (the Copy-mode Checkbox + a Separator at top, the footer Separator + InputTextSubmit at bottom) — empty space around those widgets fell through directly to the parent window's window-drag init. Fix: wrap the entire chat panel body in a single outer ##chatbody BeginChild before drawing any content. The renderer's drag-trap fires inside this outer child too, absorbing every empty-space click in the chat panel body. The inner ##chattail child is now nested inside it, which doesn't change its scroll-tail semantics but does mean it gets its own drag-trap as a bonus. Test fixed: Render_BeginChild_ReservesNegativeFooterFromFrameHeight was using Single(BeginChild) — there are now two BeginChild calls (##chatbody outer + ##chattail inner). Switched to Single(... && Args[0] == "##chattail") so the test still pins the footer reserve on the inner call where it lives. dotnet build green; 1,309 / 1,309 tests green. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../Panels/Chat/ChatPanel.cs | 17 +++++++++++++++++ .../Panels/Chat/ChatPanelLayoutTests.cs | 9 +++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/AcDream.UI.Abstractions/Panels/Chat/ChatPanel.cs b/src/AcDream.UI.Abstractions/Panels/Chat/ChatPanel.cs index 28e035b..c8ece99 100644 --- a/src/AcDream.UI.Abstractions/Panels/Chat/ChatPanel.cs +++ b/src/AcDream.UI.Abstractions/Panels/Chat/ChatPanel.cs @@ -90,6 +90,20 @@ public sealed class ChatPanel : IPanel return; } + // L.0 follow-up: wrap the entire chat panel body in a single + // outer BeginChild so empty-space clicks anywhere in the body + // (Checkbox row, between Separator and input, etc.) are + // absorbed by BeginChild's drag-trap (an InvisibleButton the + // ImGui renderer adds inside every BeginChild). Without this + // wrapper the chat panel was draggable from any empty body + // pixel — only the inner ##chattail area was protected. + if (!renderer.BeginChild("##chatbody", new System.Numerics.Vector2(0f, 0f))) + { + renderer.EndChild(); + renderer.End(); + return; + } + // L.0 follow-up: top-of-panel "Copy mode" toggle. When on, the // chat tail rendering swaps to TextMultilineReadOnly so the // user can mark + Ctrl+C any text. Off (default) preserves the @@ -186,6 +200,7 @@ public sealed class ChatPanel : IPanel if (TryHandleClientCommand(trimmed)) { _input = string.Empty; + renderer.EndChild(); // outer ##chatbody renderer.End(); return; } @@ -206,6 +221,7 @@ public sealed class ChatPanel : IPanel _vm.ShowSystemMessage( $"Unknown command: {verb}. Type /help for the list of supported commands."); _input = string.Empty; + renderer.EndChild(); // outer ##chatbody renderer.End(); return; } @@ -225,6 +241,7 @@ public sealed class ChatPanel : IPanel _input = string.Empty; } + renderer.EndChild(); // outer ##chatbody renderer.End(); } diff --git a/tests/AcDream.UI.Abstractions.Tests/Panels/Chat/ChatPanelLayoutTests.cs b/tests/AcDream.UI.Abstractions.Tests/Panels/Chat/ChatPanelLayoutTests.cs index 0e80233..28813d3 100644 --- a/tests/AcDream.UI.Abstractions.Tests/Panels/Chat/ChatPanelLayoutTests.cs +++ b/tests/AcDream.UI.Abstractions.Tests/Panels/Chat/ChatPanelLayoutTests.cs @@ -67,8 +67,13 @@ public sealed class ChatPanelLayoutTests panel.Render(new PanelContext(0.016f, new NoBus()), renderer); - var beginChildCall = renderer.Calls.Single(c => c.Method == "BeginChild"); - var size = (System.Numerics.Vector2)beginChildCall.Args[1]!; + // L.0 follow-up: the chat panel now wraps its body in an outer + // ##chatbody BeginChild (so empty-space clicks can't drag the + // parent window). The inner ##chattail BeginChild is the one + // that reserves the footer; that's what this test asserts. + var chattailCall = renderer.Calls.Single(c => c.Method == "BeginChild" + && (string)c.Args[0]! == "##chattail"); + var size = (System.Numerics.Vector2)chattailCall.Args[1]!; // Width 0 = fill available; height < 0 = "fill minus this". // Reserved height should equal FrameHeightWithSpacing + a small // separator pad (~6f) so the input never visually clips the