From fc9f8d52c354086b6214a11d2bd0149cf988e2af Mon Sep 17 00:00:00 2001 From: DanS Date: Mon, 26 Oct 2026 14:45:39 -0500 Subject: [PATCH] fix(ui): clamp tooltips to a comfortable width instead of spanning the screen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Audited every tooltip. The shared helper material::Tooltip wrapped ImGui's SetTooltip with no width limit, so a long tooltip laid out on one line and stretched across the whole display — most visible on the newly reworded note/consolidation tooltips. 258 call sites go through this helper, so the fix is central: - material::Tooltip / BeginTooltip now push a text wrap position (420 logical px, DPI-scaled) so long text wraps to a comfortable column; short text is unaffected (it's already narrower). A caller that pushes its own wrap pos nests cleanly. Also fixes a latent style-stack leak when ImGui::BeginTooltip returns false (the pushed style is now popped). Six sites bypassed the helper with raw ImGui tooltips. The two that actually stretched are migrated to material::Tooltip: the alert/bell toast-hover showing a full (now ~250-char) message, and the wallets dialog's folder-path hover. The chat over-cap tooltip is migrated too for consistency. The remaining three (two wallet-recovery dialog tips, the chat composer's parts tooltip) already clamp via their own PushTextWrapPos and are left as-is. Build + tests + hygiene green. Co-Authored-By: Claude Opus 4.8 --- src/ui/material/tooltip_style.h | 41 ++++++++++++++++++++++++++------- src/ui/notifications.cpp | 3 ++- src/ui/windows/chat_tab.cpp | 2 +- src/ui/windows/wallets_dialog.h | 3 ++- 4 files changed, 38 insertions(+), 11 deletions(-) diff --git a/src/ui/material/tooltip_style.h b/src/ui/material/tooltip_style.h index 0febf98..2102f76 100644 --- a/src/ui/material/tooltip_style.h +++ b/src/ui/material/tooltip_style.h @@ -36,27 +36,52 @@ inline void PopTooltipStyle() ImGui::PopStyleVar(); } -// Drop-in replacement for ImGui::SetTooltip (same printf-style signature). (No IM_FMTARGS: GCC -// rejects the format attribute on an inline definition; call sites already passed SetTooltip's check.) +// Comfortable maximum tooltip text width (logical px, scaled by DPI). Long tooltip text wraps at this +// instead of stretching across the whole screen; short text is unaffected (it's narrower than the cap). +inline constexpr float kTooltipWrapWidth = 420.0f; + +// The wrap position (window-local x) to clamp a just-begun tooltip's text to. Called right after +// ImGui::BeginTooltip, when the cursor sits at the tooltip's content origin. +inline float tooltipWrapPosX() +{ + return ImGui::GetCursorPosX() + kTooltipWrapWidth * Layout::dpiScale(); +} + +// Drop-in replacement for ImGui::SetTooltip (same printf-style signature), with the text clamped to a +// comfortable width. (No IM_FMTARGS: GCC rejects the format attribute on an inline definition; call sites +// already passed SetTooltip's check.) inline void Tooltip(const char* fmt, ...) { PushTooltipStyle(); - va_list args; - va_start(args, fmt); - ImGui::SetTooltipV(fmt, args); - va_end(args); + if (ImGui::BeginTooltip()) { + ImGui::PushTextWrapPos(tooltipWrapPosX()); + va_list args; + va_start(args, fmt); + ImGui::TextV(fmt, args); + va_end(args); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } PopTooltipStyle(); } -// Drop-in replacements for ImGui::BeginTooltip / ImGui::EndTooltip (must be paired, as before). +// Drop-in replacements for ImGui::BeginTooltip / ImGui::EndTooltip (must be paired, as before). The begin +// clamps text to a comfortable width by default; a caller that pushes its own wrap pos nests cleanly (it +// pops back to this one). Text-only callers need do nothing; non-text content ignores the wrap pos. inline bool BeginTooltip() { PushTooltipStyle(); - return ImGui::BeginTooltip(); + if (ImGui::BeginTooltip()) { + ImGui::PushTextWrapPos(tooltipWrapPosX()); + return true; + } + PopTooltipStyle(); // not shown: EndTooltip won't run, so undo the style push here + return false; } inline void EndTooltip() { + ImGui::PopTextWrapPos(); // pair the wrap pos pushed in BeginTooltip ImGui::EndTooltip(); PopTooltipStyle(); } diff --git a/src/ui/notifications.cpp b/src/ui/notifications.cpp index 3122d2a..1857447 100644 --- a/src/ui/notifications.cpp +++ b/src/ui/notifications.cpp @@ -6,6 +6,7 @@ #include "schema/ui_schema.h" #include "material/type.h" #include "material/draw_helpers.h" +#include "material/tooltip_style.h" // material::Tooltip (width-clamped) #include "../embedded/IconsMaterialDesign.h" #include "imgui.h" @@ -210,7 +211,7 @@ void Notifications::render() if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) requestHistory(); } if (toast_hovered_ && clipped) - ImGui::SetTooltip("%s", notif.message.c_str()); + material::Tooltip("%s", notif.message.c_str()); // wrapped: a long alert message mustn't span the screen } } // namespace ui diff --git a/src/ui/windows/chat_tab.cpp b/src/ui/windows/chat_tab.cpp index efee673..6af7595 100644 --- a/src/ui/windows/chat_tab.cpp +++ b/src/ui/windows/chat_tab.cpp @@ -2216,7 +2216,7 @@ void RenderChatTab(App* app) // When Send is disabled *because the message is over the cap*, explain why on hover — the // gauge went red but the numeric reason is no longer always-visible. if (overCap && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) - ImGui::SetTooltip("%s %d / %d", TR("chat_len_over"), used, kBodyMaxBytes); + material::Tooltip("%s %d / %d", TR("chat_len_over"), used, kBodyMaxBytes); ImGui::PopStyleColor(3); if (submit && s_compose[0] != '\0' && !overCap) { diff --git a/src/ui/windows/wallets_dialog.h b/src/ui/windows/wallets_dialog.h index edcc603..9b5b8df 100644 --- a/src/ui/windows/wallets_dialog.h +++ b/src/ui/windows/wallets_dialog.h @@ -39,6 +39,7 @@ #include "../layout.h" #include "../material/colors.h" #include "../material/draw_helpers.h" +#include "../material/tooltip_style.h" // material::Tooltip (width-clamped) #include "../material/type.h" #include "folder_picker.h" @@ -557,7 +558,7 @@ public: ImGui::AlignTextToFramePadding(); const float textW = listW - bh2 - 2.0f * Layout::spacingSm(); Type().textColored(TypeStyle::Caption, OnSurface(), elideFront(folder, textW).c_str()); - if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", folder.c_str()); + if (ImGui::IsItemHovered()) material::Tooltip("%s", folder.c_str()); // wrapped: long paths ImGui::PopID(); } if (!toRemove.empty()) {