feat(themes): subtle card drop shadow for Light + Marble

Cards on the pale Light / Marble backgrounds now lift off the surface with a
subtle drop shadow. Drawn in DrawGlassPanel (behind the card fill) as fading
rounded-rect STROKES offset down — strokes never paint across the card body, so
only the outer soft blur remains once the fill covers the interior (the
"render then mask out the UI area" result, no FBO needed). Gated to the Light and
Marble themes (cached per theme-generation); dark themes are unchanged. Verified
across marble / light (shadow present, smooth, body clean) and dark (none).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-10 20:07:24 -05:00
parent ad7a150ad6
commit 063f1fa4be

View File

@@ -545,10 +545,51 @@ inline bool& FullWindowBlurOverlayActiveRef() { static bool v = false; return v;
inline void SetFullWindowBlurOverlayActive(bool v) { FullWindowBlurOverlayActiveRef() = v; }
inline bool IsFullWindowBlurOverlayActive() { return FullWindowBlurOverlayActiveRef(); }
// Light-surface themes (Light / Marble) want cards to lift off the pale background with a subtle
// drop shadow. Cached per theme-generation so the string compare runs once per theme load, not per
// panel. (Data-driven later if more themes want it.)
inline bool CardDropShadowWanted()
{
static uint32_t s_gen = ~0u;
static bool s_want = false;
uint32_t g = schema::UI().generation();
if (g != s_gen) {
s_gen = g;
const std::string& tn = schema::UI().themeName();
s_want = (tn == "Light" || tn == "Marble");
}
return s_want;
}
// Soft drop shadow OUTSIDE a rounded rect, offset down. Drawn as fading rounded-rect STROKES (not
// fills), so it never paints across the card body — only the outer blur remains once the card fill
// covers the interior. This is the "render then mask out the UI area" result without an FBO.
inline void DrawCardDropShadow(ImDrawList* dl, const ImVec2& pMin, const ImVec2& pMax, float rounding)
{
const float dp = Layout::dpiScale();
const int kRings = 10;
const float spread = 13.0f * dp; // how far the shadow reaches beyond the card edge
const float offY = 3.0f * dp; // downward drop offset
for (int i = kRings; i >= 1; --i) {
float t = (float)i / (float)kRings; // 1 (outer) .. 0.1 (near the card)
float e = spread * t; // expansion at this ring
int a = (int)(20.0f * (1.0f - t) + 0.5f); // alpha: ~0 at the outer edge, strongest by the card
if (a < 1) continue;
ImVec2 smn(pMin.x - e, pMin.y - e + offY);
ImVec2 smx(pMax.x + e, pMax.y + e + offY);
dl->AddRect(smn, smx, IM_COL32(0, 0, 0, a), rounding + e, 0, 2.0f * dp);
}
}
inline void DrawGlassPanel(ImDrawList* dl, const ImVec2& pMin,
const ImVec2& pMax,
const GlassPanelSpec& spec = GlassPanelSpec())
{
// Subtle drop shadow behind the card (light themes only). Drawn first so the card fill below
// covers the interior, leaving only the outer soft blur — the card body is never contaminated.
if (CardDropShadowWanted())
DrawCardDropShadow(dl, pMin, pMax, spec.rounding);
if (IsBackdropActive() && !dragonx::ui::effects::isLowSpecMode() && !IsFullWindowBlurOverlayActive()) {
// --- Cached color lookups (invalidated on theme change) ---
// These 3 resolveColor() calls do string parsing + map lookup