feat(material): promote blur-overlay modal primitives (Phase 0a)
First step toward unifying all app modals on the Manage-Portfolio visual style. Add, as shared material:: helpers (additive — no caller yet, no visible change): - blur-overlay open refcount + 1-frame latch (Push/Pop/AnyBlurOverlayActive[LastFrame], LatchBlurOverlayActive) so App::render's effect guard can be modal-agnostic; - capture-frame query (IsCapturingBlurBackdrop) for scroll-fade suppression; - RightAlignX and SegmentedControl (promoted verbatim from market_tab); - BeginFadeScrollChild/EndFadeScrollChild in a dedicated overlay_scroll.h (kept out of the widely-included draw_helpers.h because effects::ScrollFadeShader's GL headers clash with SDL's in some TUs). Also fix IsCurrentWindowOverlayDialog to prefix-match "##OverlayScrim" so suffixed overlay windows are recognized by the modal-aware hover check. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -114,7 +114,9 @@ inline bool IsCurrentWindowOverlayDialog()
|
|||||||
{
|
{
|
||||||
ImGuiWindow* window = ImGui::GetCurrentWindow();
|
ImGuiWindow* window = ImGui::GetCurrentWindow();
|
||||||
for (ImGuiWindow* node = window; node; node = node->ParentWindow) {
|
for (ImGuiWindow* node = window; node; node = node->ParentWindow) {
|
||||||
if (node->Name && strcmp(node->Name, "##OverlayScrim") == 0)
|
// Prefix match so both "##OverlayScrim" and suffixed "##OverlayScrim_<id>" 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 true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -1027,6 +1029,75 @@ inline bool DrawDialogTitleBar(const char* title, bool* p_open, ImU32 accent_col
|
|||||||
return closeClicked;
|
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
|
// Fullscreen Overlay Dialog
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|||||||
68
src/ui/material/overlay_scroll.h
Normal file
68
src/ui/material/overlay_scroll.h
Normal file
@@ -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 <algorithm>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
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
|
||||||
Reference in New Issue
Block a user