From 4c43f2e0821be67b75627b9568f431d327cfa0a2 Mon Sep 17 00:00:00 2001 From: DanS Date: Tue, 21 Jul 2026 01:23:23 -0500 Subject: [PATCH] fix(chat): draw the jump-to-latest pill on top of the messages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pill was drawn on the parent window's draw list after EndChild, but ImGui renders a child window ON TOP of its parent — so the message text covered it. Draw it instead on the thread child's own draw list, after the message loop (later draw commands render on top), with a PushClipRect so the child's content padding / scrollbar doesn't clip it. Clickability is unchanged (hand-drawn + IsMouseHoveringRect / IsMouseClicked test the cursor directly), and it stays pinned to the visible bottom-right corner via absolute screen coordinates. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/ui/windows/chat_tab.cpp | 55 +++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/src/ui/windows/chat_tab.cpp b/src/ui/windows/chat_tab.cpp index 4a5fe7b..bc6373c 100644 --- a/src/ui/windows/chat_tab.cpp +++ b/src/ui/windows/chat_tab.cpp @@ -1398,35 +1398,38 @@ void RenderChatTab(App* app) atBottom = ImGui::GetScrollMaxY() <= 0.0f || ImGui::GetScrollY() >= ImGui::GetScrollMaxY() - 4.0f; // Re-arm auto-scroll once the user returns to the bottom (after the wheel cooldown). if (atBottom && s_chat_scroll_cooldown <= 0.0f) s_chat_auto_scroll = true; - } - ImGui::EndChild(); - ImGui::PopStyleVar(2); // ##ChatMessages WindowPadding + zeroed vertical ItemSpacing - // Jump-to-latest pill when scrolled up (Q9). Hand-drawn (rect + IsMouseHoveringRect) rather than - // ImGui::Button: the pill overlaps the ##ChatMessages child, which owns mouse-hover in that - // region, so a real widget on the parent window never registers the click. IsMouseHoveringRect / - // IsMouseClicked test the cursor position directly, independent of window hover ownership. - if (!atBottom) { - ImDrawList* pdl = ImGui::GetWindowDrawList(); - const ImVec2 pmin = jumpPillMin, pmax = jumpPillMax; - const float pw = pmax.x - pmin.x, ph = pmax.y - pmin.y; - const bool hov = ImGui::IsMouseHoveringRect(pmin, pmax); - pdl->AddRectFilled(pmin, pmax, - hov ? material::Primary() : material::WithAlpha(material::Primary(), 220), - ph * 0.5f); - ImFont* lf = ImGui::GetFont(); const float lsz = ImGui::GetFontSize(); - const char* lbl = TR("chat_jump_latest"); - const ImVec2 tszp = lf->CalcTextSizeA(lsz, FLT_MAX, 0.0f, lbl); - pdl->AddText(lf, lsz, ImVec2(pmin.x + (pw - tszp.x) * 0.5f, pmin.y + (ph - tszp.y) * 0.5f), - IM_COL32(255, 255, 255, 235), lbl); - if (hov) { - ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); - if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) { - s_scroll_to_cid = sel->cid; - s_chat_auto_scroll = true; // clicking Latest re-arms follow-the-newest - s_chat_scroll_cooldown = 0.0f; + + // Jump-to-latest pill when scrolled up (Q9). Drawn LAST and INSIDE the thread child (on the + // child's own draw list) so it sits on top of the messages — a parent-window draw after + // EndChild renders UNDER the child and gets covered by the text. PushClipRect keeps the + // child's content padding / scrollbar from clipping it. Hand-drawn + IsMouseHoveringRect so + // the click lands despite the child owning hover here (a real widget would still miss it). + if (!atBottom) { + const ImVec2 pmin = jumpPillMin, pmax = jumpPillMax; + const float pw = pmax.x - pmin.x, ph = pmax.y - pmin.y; + const bool hov = ImGui::IsMouseHoveringRect(pmin, pmax); + dl->PushClipRect(pmin, pmax, false); + dl->AddRectFilled(pmin, pmax, + hov ? material::Primary() : material::WithAlpha(material::Primary(), 220), + ph * 0.5f); + ImFont* lf = ImGui::GetFont(); const float lsz = ImGui::GetFontSize(); + const char* lbl = TR("chat_jump_latest"); + const ImVec2 tszp = lf->CalcTextSizeA(lsz, FLT_MAX, 0.0f, lbl); + dl->AddText(lf, lsz, ImVec2(pmin.x + (pw - tszp.x) * 0.5f, pmin.y + (ph - tszp.y) * 0.5f), + IM_COL32(255, 255, 255, 235), lbl); + dl->PopClipRect(); + if (hov) { + ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); + if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) { + s_scroll_to_cid = sel->cid; + s_chat_auto_scroll = true; // clicking Latest re-arms follow-the-newest + s_chat_scroll_cooldown = 0.0f; + } } } } + ImGui::EndChild(); + ImGui::PopStyleVar(2); // ##ChatMessages WindowPadding + zeroed vertical ItemSpacing // Composer is rendered OUTSIDE this box, directly below it (see after EndChild). } else { if (convs.empty()) centeredEmptyState(ICON_MD_FORUM, TR("chat_empty_title"), TR("chat_empty_start"));