refactor(audit): batch 7 — RPC/settings/lite/effects mechanical dedups

Six behavior-preserving consolidations from the audit:

- RPCClient::call() overloads share a private performCall() (payload dump +
  curl_easy_perform + http code) and a static parseRpcResult() (error->RpcError
  extraction). The timeout overload restores the prior timeout in both the
  success and catch paths, exactly as before.
- The six UnifiedCallback methods delegate to one splitUnified() that builds
  the (Callback, ErrorCallback) pair once (null-check preserved).
- One liteTrimCopy() in lite_connection_service replaces 5 file-local
  trim-copy helpers across the lite slice.
- lite_wallet_controller's inline height JSON parse now routes through the
  tested parseLiteHeightResponse().
- theme_effects.cpp: unpackRGB()/scaledAlpha() replace 14 RGB-unpack + 5
  alpha-scale copy-paste blocks.
- settings.cpp load() uses loadScalar()/loadClamped() helpers for ~44 scalar
  fields. Besides removing boilerplate this HARDENS loading: ~44 fields that
  previously only checked contains() now also verify the JSON type, so a
  malformed value in settings.json falls back to the default instead of
  throwing/misreading. Custom cases (enum parses, legacy migrations, arrays)
  stay inline; save() is unchanged.

Full-node + Lite build clean; ctest 1/1; hygiene clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-02 19:56:54 -05:00
parent 4635a56e89
commit dcd09dc542
11 changed files with 245 additions and 328 deletions

View File

@@ -15,6 +15,23 @@ namespace dragonx {
namespace ui {
namespace effects {
namespace {
// Colors are packed little-endian RGBA (R at bit 0, G at 8, B at 16, A at 24) — the
// same layout IM_COL32 produces. unpackRGB pulls out the three color channels (alpha is
// handled separately at each site).
struct RGB { int r, g, b; };
static inline RGB unpackRGB(ImU32 c) {
return RGB{ (int)(c & 0xFF), (int)((c >> 8) & 0xFF), (int)((c >> 16) & 0xFF) };
}
// Scales a 0..1 base alpha by the background opacity into a clamped 0..255 int.
static inline int scaledAlpha(float base, float bgOpacity) {
return std::clamp((int)(base * bgOpacity * 255.0f), 0, 255);
}
} // namespace
// ============================================================================
// Singleton
// ============================================================================
@@ -327,16 +344,13 @@ void ThemeEffects::drawShimmer(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax,
if (clLeft >= clRight) return;
float mid = (clLeft + clRight) * 0.5f;
int peakA = (int)(shimmer_.alpha * bgOpacity_ * 255.0f);
peakA = std::clamp(peakA, 0, 255);
int peakA = scaledAlpha(shimmer_.alpha, bgOpacity_);
// Extract shimmer color RGB (ignore alpha, use shimmer_.alpha)
int sR = shimmer_.color & 0xFF;
int sG = (shimmer_.color >> 8) & 0xFF;
int sB = (shimmer_.color >> 16) & 0xFF;
RGB s = unpackRGB(shimmer_.color);
ImU32 clear = IM_COL32(sR, sG, sB, 0);
ImU32 peak = IM_COL32(sR, sG, sB, peakA);
ImU32 clear = IM_COL32(s.r, s.g, s.b, 0);
ImU32 peak = IM_COL32(s.r, s.g, s.b, peakA);
// Inset clip rect by corner rounding to prevent shimmer bleeding into rounded corners
float cr = std::min(rounding, std::min(w, h) * 0.5f) * 0.3f;
@@ -374,9 +388,7 @@ void ThemeEffects::drawSpecularGlare(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax,
float minDim = std::min(w, h);
float glareR = minDim * specular_glare_.radius;
int cR = specular_glare_.color & 0xFF;
int cG = (specular_glare_.color >> 8) & 0xFF;
int cB = (specular_glare_.color >> 16) & 0xFF;
RGB c = unpackRGB(specular_glare_.color);
// Per-panel seed from position — unique drift per panel
float seed = pMin.x * 0.0073f + pMin.y * 0.0137f;
@@ -410,7 +422,7 @@ void ThemeEffects::drawSpecularGlare(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax,
if (a <= 0) continue;
dl->AddCircleFilled(ImVec2(cx, cy), ringRadius,
IM_COL32(cR, cG, cB, a), 32);
IM_COL32(c.r, c.g, c.b, a), 32);
}
}
@@ -445,18 +457,14 @@ ImU32 ThemeEffects::tintByPosition(ImU32 baseColor, float screenY) const {
float s = positional_hue_.strength;
float inv = 1.0f - s;
int bR = baseColor & 0xFF;
int bG = (baseColor >> 8) & 0xFF;
int bB = (baseColor >> 16) & 0xFF;
RGB base = unpackRGB(baseColor);
int bA = (baseColor >> 24) & 0xFF;
int tR = tint & 0xFF;
int tG = (tint >> 8) & 0xFF;
int tB = (tint >> 16) & 0xFF;
RGB t = unpackRGB(tint);
int r = (int)(bR * inv + tR * s);
int g = (int)(bG * inv + tG * s);
int b = (int)(bB * inv + tB * s);
int r = (int)(base.r * inv + t.r * s);
int g = (int)(base.g * inv + t.g * s);
int b = (int)(base.b * inv + t.b * s);
return IM_COL32(r, g, b, bA);
}
@@ -474,10 +482,8 @@ void ThemeEffects::drawGlowPulse(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax,
float alpha = glow_pulse_.minAlpha + phase * (glow_pulse_.maxAlpha - glow_pulse_.minAlpha);
if (alpha <= 0.001f) return;
int a = std::clamp((int)(alpha * bgOpacity_ * 255.0f), 0, 255);
int cR = glow_pulse_.color & 0xFF;
int cG = (glow_pulse_.color >> 8) & 0xFF;
int cB = (glow_pulse_.color >> 16) & 0xFF;
int a = scaledAlpha(alpha, bgOpacity_);
RGB c = unpackRGB(glow_pulse_.color);
// Border-only glow: concentric outlines with smooth Gaussian falloff.
// Use many fine sub-rings (4× the pixel radius) to avoid visible
@@ -496,7 +502,7 @@ void ThemeEffects::drawGlowPulse(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax,
dl->AddRect(
ImVec2(pMin.x - expand, pMin.y - expand),
ImVec2(pMax.x + expand, pMax.y + expand),
IM_COL32(cR, cG, cB, ringAlpha),
IM_COL32(c.r, c.g, c.b, ringAlpha),
rounding + expand * 0.5f, 0, thickness);
}
}
@@ -513,17 +519,13 @@ void ThemeEffects::drawGradientBorderShift(ImDrawList* dl, ImVec2 pMin, ImVec2 p
float phase = std::sin(time_ * gradient_border_.speed * 2.0f * 3.14159265f) * 0.5f + 0.5f;
// Extract RGBA from both colors and lerp
int aR = gradient_border_.colorA & 0xFF;
int aG = (gradient_border_.colorA >> 8) & 0xFF;
int aB = (gradient_border_.colorA >> 16) & 0xFF;
int bR = gradient_border_.colorB & 0xFF;
int bG = (gradient_border_.colorB >> 8) & 0xFF;
int bB = (gradient_border_.colorB >> 16) & 0xFF;
RGB ca = unpackRGB(gradient_border_.colorA);
RGB cb = unpackRGB(gradient_border_.colorB);
int r = aR + (int)((bR - aR) * phase);
int g = aG + (int)((bG - aG) * phase);
int b = aB + (int)((bB - aB) * phase);
int a = std::clamp((int)(gradient_border_.alpha * bgOpacity_ * 255.0f), 0, 255);
int r = ca.r + (int)((cb.r - ca.r) * phase);
int g = ca.g + (int)((cb.g - ca.g) * phase);
int b = ca.b + (int)((cb.b - ca.b) * phase);
int a = scaledAlpha(gradient_border_.alpha, bgOpacity_);
// Draw the shifting border
dl->AddRect(pMin, pMax, IM_COL32(r, g, b, a),
@@ -640,9 +642,7 @@ void ThemeEffects::drawEdgeTrace(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax,
float headPos = std::fmod(time_ * edge_trace_.speed, 1.0f);
float traceLen = edge_trace_.length;
int cR = edge_trace_.color & 0xFF;
int cG = (edge_trace_.color >> 8) & 0xFF;
int cB = (edge_trace_.color >> 16) & 0xFF;
RGB c = unpackRGB(edge_trace_.color);
// Draw the trace as connected line segments with fading alpha.
// Use enough segments to make curved corners smooth.
@@ -667,16 +667,16 @@ void ThemeEffects::drawEdgeTrace(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax,
aFrac = aFrac * aFrac; // quadratic falloff for natural fade
int a = (int)(edge_trace_.alpha * aFrac * bgOpacity_ * 255.0f);
if (a > 0) {
dl->AddLine(prev, pt, IM_COL32(cR, cG, cB, a), edge_trace_.thickness);
dl->AddLine(prev, pt, IM_COL32(c.r, c.g, c.b, a), edge_trace_.thickness);
}
prev = pt;
}
// Bright dot at the head
int headA = (int)(edge_trace_.alpha * bgOpacity_ * 255.0f);
int headA = scaledAlpha(edge_trace_.alpha, bgOpacity_);
ImVec2 headPt = perimeterPoint(pMin, pMax, headPos, rounding);
dl->AddCircleFilled(headPt, edge_trace_.thickness * 1.5f,
IM_COL32(cR, cG, cB, headA), 8);
IM_COL32(c.r, c.g, c.b, headA), 8);
}
// ============================================================================
@@ -690,9 +690,7 @@ void ThemeEffects::drawEmberRise(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax) const
float h = pMax.y - pMin.y;
if (w <= 0 || h <= 0) return;
int cR = ember_rise_.color & 0xFF;
int cG = (ember_rise_.color >> 8) & 0xFF;
int cB = (ember_rise_.color >> 16) & 0xFF;
RGB c = unpackRGB(ember_rise_.color);
for (int i = 0; i < ember_rise_.count; i++) {
// Golden-ratio spacing gives evenly distributed phases
@@ -723,11 +721,11 @@ void ThemeEffects::drawEmberRise(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax) const
// Warm core
dl->AddCircleFilled(ImVec2(x, y), size,
IM_COL32(cR, cG, cB, a), 6);
IM_COL32(c.r, c.g, c.b, a), 6);
// Softer outer glow
if (a > 30) {
dl->AddCircleFilled(ImVec2(x, y), size * 2.0f,
IM_COL32(cR, cG, cB, a / 4), 6);
IM_COL32(c.r, c.g, c.b, a / 4), 6);
}
}
}
@@ -746,9 +744,7 @@ void ThemeEffects::drawViewportEmbers(ImDrawList* dl) const {
float vpY = vp->WorkPos.y;
if (vpW <= 0 || vpH <= 0) return;
int cR = ember_rise_.color & 0xFF;
int cG = (ember_rise_.color >> 8) & 0xFF;
int cB = (ember_rise_.color >> 16) & 0xFF;
RGB c = unpackRGB(ember_rise_.color);
// Viewport embers: more particles, spread across the whole screen
int vpCount = ember_rise_.count * 3;
@@ -777,10 +773,10 @@ void ThemeEffects::drawViewportEmbers(ImDrawList* dl) const {
if (a <= 0) continue;
dl->AddCircleFilled(ImVec2(x, y), size,
IM_COL32(cR, cG, cB, a), 6);
IM_COL32(c.r, c.g, c.b, a), 6);
if (a > 20) {
dl->AddCircleFilled(ImVec2(x, y), size * 2.5f,
IM_COL32(cR, cG, cB, a / 5), 6);
IM_COL32(c.r, c.g, c.b, a / 5), 6);
}
}
}
@@ -799,9 +795,7 @@ void ThemeEffects::drawSandstorm(ImDrawList* dl) const {
float vpY = vp->WorkPos.y;
if (vpW <= 0 || vpH <= 0) return;
int cR = sandstorm_.color & 0xFF;
int cG = (sandstorm_.color >> 8) & 0xFF;
int cB = (sandstorm_.color >> 16) & 0xFF;
RGB c = unpackRGB(sandstorm_.color);
// Wind direction vector from angle (degrees from horizontal)
float windRad = sandstorm_.windAngle * 3.14159265f / 180.0f;
@@ -875,21 +869,21 @@ void ThemeEffects::drawSandstorm(ImDrawList* dl) const {
dl->AddLine(
ImVec2(x, y),
ImVec2(x + sx, y + sy),
IM_COL32(cR, cG, cB, a),
IM_COL32(c.r, c.g, c.b, a),
size * 0.7f);
// Bright head dot
dl->AddCircleFilled(ImVec2(x, y), size * 0.5f,
IM_COL32(cR, cG, cB, std::min(255, a * 3 / 2)), 5);
IM_COL32(c.r, c.g, c.b, std::min(255, a * 3 / 2)), 5);
} else {
// Small/slow particles: simple circle
dl->AddCircleFilled(ImVec2(x, y), size,
IM_COL32(cR, cG, cB, a), 6);
IM_COL32(c.r, c.g, c.b, a), 6);
}
// Occasional larger dust puff (every ~8th particle, double size, half alpha)
if (i % 8 == 0 && a > 15) {
dl->AddCircleFilled(ImVec2(x, y), size * 2.5f,
IM_COL32(cR, cG, cB, a / 4), 8);
IM_COL32(c.r, c.g, c.b, a / 4), 8);
}
}
}
@@ -916,9 +910,7 @@ void ThemeEffects::drawPanelEffects(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax,
float headPos = std::fmod(time_ * edge_trace_.speed + posKey, 1.0f);
float traceLen = edge_trace_.length;
int cR = edge_trace_.color & 0xFF;
int cG = (edge_trace_.color >> 8) & 0xFF;
int cB = (edge_trace_.color >> 16) & 0xFF;
RGB c = unpackRGB(edge_trace_.color);
// Panel edge trace is subtler than sidebar — 60% alpha
float panelAlpha = edge_trace_.alpha * 0.6f * bgOpacity_;
@@ -935,7 +927,7 @@ void ThemeEffects::drawPanelEffects(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax,
aFrac = aFrac * aFrac;
int a = (int)(panelAlpha * aFrac * 255.0f);
if (a > 0) {
dl->AddLine(prev, pt, IM_COL32(cR, cG, cB, a),
dl->AddLine(prev, pt, IM_COL32(c.r, c.g, c.b, a),
edge_trace_.thickness * 0.8f);
}
prev = pt;
@@ -945,7 +937,7 @@ void ThemeEffects::drawPanelEffects(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax,
int headA = (int)(panelAlpha * 255.0f);
ImVec2 headPt = perimeterPoint(pMin, pMax, headPos, rounding);
dl->AddCircleFilled(headPt, edge_trace_.thickness * 1.2f,
IM_COL32(cR, cG, cB, headA), 6);
IM_COL32(c.r, c.g, c.b, headA), 6);
}
}
@@ -958,9 +950,7 @@ void ThemeEffects::drawPanelEffects(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax,
int panelCount = std::max(2, ember_rise_.count / 3);
float panelAlpha = ember_rise_.alpha * 0.5f * bgOpacity_;
int cR = ember_rise_.color & 0xFF;
int cG = (ember_rise_.color >> 8) & 0xFF;
int cB = (ember_rise_.color >> 16) & 0xFF;
RGB c = unpackRGB(ember_rise_.color);
// Use panel position as seed for unique particle distribution
float seed = pMin.x * 0.013f + pMin.y * 0.031f;
@@ -987,7 +977,7 @@ void ThemeEffects::drawPanelEffects(ImDrawList* dl, ImVec2 pMin, ImVec2 pMax,
if (a <= 0) continue;
dl->AddCircleFilled(ImVec2(x, y), size,
IM_COL32(cR, cG, cB, a), 6);
IM_COL32(c.r, c.g, c.b, a), 6);
}
dl->PopClipRect();
@@ -1074,14 +1064,12 @@ void ThemeEffects::drawViewportOverlay(ImDrawList* dl) const {
// === Vignette: edge darkening/tinting (4 gradient strips) ===
// Overlapping strips naturally darken corners more than edges — cinematic look
if (viewport_overlay_.vignetteEnabled) {
int cR = viewport_overlay_.vignetteColor & 0xFF;
int cG = (viewport_overlay_.vignetteColor >> 8) & 0xFF;
int cB = (viewport_overlay_.vignetteColor >> 16) & 0xFF;
int maxA = std::clamp((int)(viewport_overlay_.vignetteAlpha * bgOpacity_ * 255.0f), 0, 255);
RGB c = unpackRGB(viewport_overlay_.vignetteColor);
int maxA = scaledAlpha(viewport_overlay_.vignetteAlpha, bgOpacity_);
if (maxA > 0) {
ImU32 edgeCol = IM_COL32(cR, cG, cB, maxA);
ImU32 clearCol = IM_COL32(cR, cG, cB, 0);
ImU32 edgeCol = IM_COL32(c.r, c.g, c.b, maxA);
ImU32 clearCol = IM_COL32(c.r, c.g, c.b, 0);
float fadeW = vpSize.x * viewport_overlay_.vignetteRadius;
float fadeH = vpSize.y * viewport_overlay_.vignetteRadius;