fix(ui): draw scroll-to-bottom button in a standalone overlay window

Button drawn in the parent window over the ##chat2-messages child was never
clickable: ImGui resolves g.HoveredWindow to the child for that screen rect, so
ItemHoverable rejects any item submitted in the parent. A top-level Begin/End
window is a sibling in the window list and wins the hit-test for its own rect.
ownerId parameter keeps the window name distinct between the main window and
each pop-out, preventing Begin/End collisions when both render in the same frame.
This commit is contained in:
2026-05-21 14:10:01 +02:00
parent 5781be2e41
commit c909d1646b
2 changed files with 44 additions and 17 deletions
+43 -16
View File
@@ -1548,7 +1548,8 @@ public sealed class ChatLogWindow : Window
Tab tab,
PayloadHandler handler,
float childHeight,
bool switchedTab
bool switchedTab,
string ownerId = "main"
)
{
// Capture these before entering the child so we can position the overlay
@@ -1581,7 +1582,7 @@ public sealed class ChatLogWindow : Window
// (which caused it to drift) and avoids the scrollbar's inner clip rect
// (which caused it to be cut off on the right edge).
if (_childScrolledUp)
DrawScrollToBottomButtonOverlay(childScreenPos, childWidth, childHeight);
DrawScrollToBottomButtonOverlay(childScreenPos, childWidth, childHeight, ownerId);
}
private void DrawLogNormalStyle(Tab tab, PayloadHandler handler, bool switchedTab)
@@ -1631,33 +1632,59 @@ public sealed class ChatLogWindow : Window
}
// UI-5: floating jump-to-latest button, drawn in the PARENT window after the
// ##chat2-messages child closes. Placing it here prevents two bugs:
// 1. Inside the child, SetCursorPos adds to CursorMaxPos.y and inflates
// ContentSize.y / ScrollMaxY every frame, so the button chased its own
// shadow and never landed at a stable position.
// 2. GetWindowWidth() inside the child includes the scrollbar column, so the
// button overlapped the scrollbar and was clipped by the inner clip rect.
// Screen-space positioning via SetCursorScreenPos is immune to both.
// UI-5: floating jump-to-latest button.
//
// Why a standalone overlay window rather than drawing in the parent?
// When this button was drawn in the parent window after the ##chat2-messages
// child closed, ImGui's hit-test still resolved g.HoveredWindow to the child
// (the child occupies the same screen rect). ItemHoverable then rejected the
// button submitted in the parent, so it was visible but never clickable.
//
// A top-level Begin/End window is a sibling in the window list, not nested
// under the child, so it wins the hit-test for its own rect and the button
// inside it is fully clickable.
//
// The ownerId parameter makes the window name unique per calling context so
// the main window and each pop-out don't share a single ImGui window entry.
private void DrawScrollToBottomButtonOverlay(
Vector2 childScreenPos,
float childWidth,
float childHeight)
float childHeight,
string ownerId)
{
var size = ImGui.GetFrameHeight();
var pad = 8f * ImGuiHelpers.GlobalScale;
var scrollbarWidth = ImGui.GetStyle().ScrollbarSize;
// Position confirmed correct in-game: bottom-right of the chat child,
// inset by pad, and pulled left of the vertical scrollbar.
var btnPos = new Vector2(
childScreenPos.X + childWidth - scrollbarWidth - size - pad,
childScreenPos.Y + childHeight - size - pad);
ImGui.SetCursorScreenPos(btnPos);
ImGui.SetNextWindowPos(btnPos, ImGuiCond.Always);
ImGui.SetNextWindowSize(new Vector2(size, size), ImGuiCond.Always);
if (ImGuiUtil.IconButton(
FontAwesomeIcon.ArrowDown,
tooltip: HellionStrings.ChatLog_ScrollToBottom_Tooltip))
_scrollToBottomRequested = true;
const ImGuiWindowFlags overlayFlags =
ImGuiWindowFlags.NoTitleBar
| ImGuiWindowFlags.NoResize
| ImGuiWindowFlags.NoMove
| ImGuiWindowFlags.NoScrollbar
| ImGuiWindowFlags.NoScrollWithMouse
| ImGuiWindowFlags.NoSavedSettings
| ImGuiWindowFlags.NoFocusOnAppearing
| ImGuiWindowFlags.NoBackground;
// End() must be called unconditionally after Begin() — ImGui rule.
if (ImGui.Begin($"##scroll-to-bottom-overlay-{ownerId}", overlayFlags))
{
if (ImGuiUtil.IconButton(
FontAwesomeIcon.ArrowDown,
tooltip: HellionStrings.ChatLog_ScrollToBottom_Tooltip))
_scrollToBottomRequested = true;
}
ImGui.End();
}
private void DrawMessages(
+1 -1
View File
@@ -118,7 +118,7 @@ internal class Popout : Window
var handler = ChatLogWindow.HandlerLender.Borrow();
var logHeight = ImGui.GetContentRegionAvail().Y - inputBarHeight - hintBannerHeight;
ChatLogWindow.DrawMessageLog(Tab, handler, logHeight, false);
ChatLogWindow.DrawMessageLog(Tab, handler, logHeight, false, Tab.Identifier.ToString());
if (inputEnabled && InputBar != null)
{