diff --git a/src/ui/material/draw_helpers.h b/src/ui/material/draw_helpers.h index 4e37150..c9ac779 100644 --- a/src/ui/material/draw_helpers.h +++ b/src/ui/material/draw_helpers.h @@ -114,7 +114,9 @@ inline bool IsCurrentWindowOverlayDialog() { ImGuiWindow* window = ImGui::GetCurrentWindow(); for (ImGuiWindow* node = window; node; node = node->ParentWindow) { - if (node->Name && strcmp(node->Name, "##OverlayScrim") == 0) + // Prefix match so both "##OverlayScrim" and suffixed "##OverlayScrim_" blur overlays + // (used by nested dialogs and the unified blur-overlay framework) are recognized. + if (node->Name && strncmp(node->Name, "##OverlayScrim", 14) == 0) return true; } return false; @@ -1027,6 +1029,75 @@ inline bool DrawDialogTitleBar(const char* title, bool* p_open, ImU32 accent_col return closeClicked; } +// ============================================================================ +// Blur-backdrop modal primitives +// ============================================================================ +// Shared building blocks for the "Manage Portfolio"-style overlay (live-blur backdrop, floating +// content, segmented section nav, edge-fade scroll). Promoted verbatim from the Market tab so every +// modal can adopt the same look. The overlay begin/end (below) drives these. + +// Refcount of open blur-backdrop overlays (nesting-safe) + a 1-frame latch. App::render builds its +// effect-suppression guard at the TOP of the frame, before dialogs dispatch, so it must read a +// stable "was any blur overlay up last frame" signal rather than this frame's in-progress count. +inline int& BlurOverlayOpenCountRef() { static int n = 0; return n; } +inline bool& BlurOverlayActiveLastFrameRef() { static bool v = false; return v; } +inline void PushBlurOverlayOpen() { ++BlurOverlayOpenCountRef(); } +inline void PopBlurOverlayOpen() { if (BlurOverlayOpenCountRef() > 0) --BlurOverlayOpenCountRef(); } +inline bool AnyBlurOverlayActive() { return BlurOverlayOpenCountRef() > 0; } +inline bool AnyBlurOverlayActiveLastFrame() { return BlurOverlayActiveLastFrameRef(); } +// Latch this frame's state for the next frame's guard. Call once at the end of App::render. +inline void LatchBlurOverlayActive() { BlurOverlayActiveLastFrameRef() = AnyBlurOverlayActive(); } + +// True during the frames a blur overlay is (re)capturing the live backdrop — the scroll-fade shader +// is suppressed then, since the acrylic capture rebinds render state on those frames. +inline bool& CapturingBlurBackdropRef() { static bool v = false; return v; } +inline bool IsCapturingBlurBackdrop() { return CapturingBlurBackdropRef(); } + +// Right-align the next item(s) of total width `w` within the remaining content region (cursor form). +inline void RightAlignX(float w) +{ + float avail = ImGui::GetContentRegionAvail().x; + if (avail > w) ImGui::SetCursorPosX(ImGui::GetCursorPosX() + avail - w); +} + +// Equal-width segmented control (rounded track + N cells, each a centered label + an active pill) at +// screen-space `origin`, spanning `totalW` x `height`. Each cell is a hit-tested InvisibleButton +// (Hand cursor on hover), disambiguated by `idBase`. Does NOT move the layout cursor. Returns the +// clicked cell index, or -1. +inline int SegmentedControl(ImDrawList* dl, ImVec2 origin, float totalW, float height, + const char* const* labels, int count, int selected, + ImFont* font, const char* idBase, float dp) +{ + int clicked = -1; + float cellW = totalW / (float)count; + dl->AddRectFilled(origin, ImVec2(origin.x + totalW, origin.y + height), + WithAlpha(OnSurface(), 20), height * 0.5f); + for (int i = 0; i < count; i++) { + ImVec2 cMin(origin.x + i * cellW, origin.y), cMax(cMin.x + cellW, origin.y + height); + bool active = (selected == i); + bool hov = ImGui::IsMouseHoveringRect(cMin, cMax); + if (active) + dl->AddRectFilled(ImVec2(cMin.x + 2.0f * dp, cMin.y + 2.0f * dp), + ImVec2(cMax.x - 2.0f * dp, cMax.y - 2.0f * dp), + WithAlpha(Primary(), 210), (height - 4.0f * dp) * 0.5f); + ImVec2 ts = font->CalcTextSizeA(font->LegacySize, FLT_MAX, 0, labels[i]); + dl->AddText(font, font->LegacySize, + ImVec2(cMin.x + (cellW - ts.x) * 0.5f, cMin.y + (height - ts.y) * 0.5f), + active ? IM_COL32(255, 255, 255, 255) : (hov ? OnSurface() : OnSurfaceMedium()), + labels[i]); + ImGui::PushID(i); + ImGui::SetCursorScreenPos(cMin); + if (ImGui::InvisibleButton(idBase, ImVec2(cellW, height))) clicked = i; + if (ImGui::IsItemHovered()) ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); + ImGui::PopID(); + } + return clicked; +} + +// The edge-fade scroll child (BeginFadeScrollChild/EndFadeScrollChild) depends on +// effects::ScrollFadeShader, whose GL/DX headers must not be pulled into this widely-included base +// header — it lives in material/overlay_scroll.h, included only by modal implementations. + // ============================================================================ // Fullscreen Overlay Dialog // ============================================================================ diff --git a/src/ui/material/overlay_scroll.h b/src/ui/material/overlay_scroll.h new file mode 100644 index 0000000..b7dd89b --- /dev/null +++ b/src/ui/material/overlay_scroll.h @@ -0,0 +1,68 @@ +// DragonX Wallet - ImGui Edition +// Copyright 2024-2026 The Hush Developers +// Released under the GPLv3 + +#pragma once + +// Edge-fade scroll region for the blur-backdrop modals (smooth scroll + Settings-tab edge fade + +// a thicker, inset, rounded scrollbar). Split out of draw_helpers.h because it depends on +// effects::ScrollFadeShader, whose GL/DX headers must not be pulled into the widely-included base +// header (they clash with SDL's GL headers in some TUs). Include this only from modal .cpp files. + +#include "draw_helpers.h" // IsCapturingBlurBackdrop, ApplySmoothScroll +#include "../effects/scroll_fade_shader.h" +#include "../effects/low_spec.h" +#include "imgui.h" +#include +#include + +namespace dragonx { +namespace ui { +namespace material { + +// Scroll region with smooth scrolling + edge fade + a thicker, inset, rounded scrollbar. Uses a +// bordered/rounded OUTER frame whose padding insets the scrollbar, holding an INNER scrolling child. +// `fade` must persist across frames (one instance per scroll region); `pad` is the inner content +// inset. Returns the inner draw list; pair with EndFadeScrollChild(). +inline ImDrawList* BeginFadeScrollChild(const char* id, effects::ScrollFadeShader& fade, + const ImVec2& pad, float dp) +{ + ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 6.0f * dp); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(6.0f * dp, 6.0f * dp)); + ImGui::BeginChild(id, ImVec2(0, std::max(120.0f * dp, ImGui::GetContentRegionAvail().y)), true, + ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); + ImGui::PopStyleVar(2); // ChildRounding + outer WindowPadding (captured by the outer child) + ImGui::PushStyleVar(ImGuiStyleVar_ScrollbarSize, 11.0f * dp); + ImGui::PushStyleVar(ImGuiStyleVar_ScrollbarRounding, 5.5f * dp); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, pad); + std::string innerId = std::string(id) + "_i"; + ImGui::BeginChild(innerId.c_str(), ImVec2(0, 0), false, ImGuiWindowFlags_NoScrollWithMouse); + ImGui::PopStyleVar(); // inner WindowPadding — captured by this child; don't cascade to tooltips + ApplySmoothScroll(); + ImDrawList* dl = ImGui::GetWindowDrawList(); + const float fadeH = 22.0f * dp; + if (!effects::isLowSpecMode() && !IsCapturingBlurBackdrop() && fade.init()) { + ImVec2 cMin = ImGui::GetWindowPos(); + ImVec2 cMax(cMin.x + ImGui::GetWindowSize().x, cMin.y + ImGui::GetWindowSize().y); + float sY = ImGui::GetScrollY(), sMax = ImGui::GetScrollMaxY(); + fade.fadeTopY = cMin.y; + fade.fadeBottomY = cMax.y; + fade.fadeZoneTop = (sY > 1.0f) ? fadeH : 0.0f; + fade.fadeZoneBottom = (sMax > 0.0f && sY < sMax - 1.0f) ? fadeH : 0.0f; + fade.addBind(dl); + } + return dl; +} + +inline void EndFadeScrollChild(effects::ScrollFadeShader& fade, ImDrawList* dl) +{ + if (!effects::isLowSpecMode() && !IsCapturingBlurBackdrop() && fade.ready) + effects::ScrollFadeShader::addUnbind(dl); + ImGui::EndChild(); // inner + ImGui::PopStyleVar(2); // ScrollbarSize + ScrollbarRounding + ImGui::EndChild(); // outer +} + +} // namespace material +} // namespace ui +} // namespace dragonx