feat: Full UI internationalization, pool hashrate stats, and layout caching
- Replace all hardcoded English strings with TR() translation keys across every tab, dialog, and component (~20 UI files) - Expand all 8 language files (de, es, fr, ja, ko, pt, ru, zh) with complete translations (~37k lines added) - Improve i18n loader with exe-relative path fallback and English base fallback for missing keys - Add pool-side hashrate polling via pool stats API in xmrig_manager - Introduce Layout::beginFrame() per-frame caching and refresh balance layout config only on schema generation change - Offload daemon output parsing to worker thread - Add CJK subset fallback font for Chinese/Japanese/Korean glyphs
This commit is contained in:
@@ -572,12 +572,12 @@ void AcrylicMaterial::drawRect(ImDrawList* drawList, const ImVec2& pMin, const I
|
||||
float u1 = localX1 / viewportWidth_;
|
||||
float v1 = 1.0f - localY1 / viewportHeight_; // V at bottom-right (low)
|
||||
|
||||
// Draw the blurred background. The glass opacity comes from
|
||||
// fallbackColor.w — blur always renders at full strength.
|
||||
// UI opacity is handled separately by card surface alpha.
|
||||
// Draw the blurred background. Scale by both the glass preset
|
||||
// opacity (fallbackColor.w) AND the user's UI opacity slider so
|
||||
// that lowering card opacity lets the sharp background through.
|
||||
ImTextureID blurTex = getBlurredTexture();
|
||||
uint8_t glassAlpha = static_cast<uint8_t>(
|
||||
std::min(255.0f, std::max(0.0f, params.fallbackColor.w * 255.0f)));
|
||||
std::min(255.0f, std::max(0.0f, params.fallbackColor.w * settings_.uiOpacity * 255.0f)));
|
||||
|
||||
if (blurTex) {
|
||||
drawList->AddImageRounded(
|
||||
@@ -774,11 +774,11 @@ AcrylicFallback AcrylicMaterial::detectFallback() const
|
||||
void AcrylicMaterial::drawTintedRect(ImDrawList* drawList, const ImVec2& pMin, const ImVec2& pMax,
|
||||
const AcrylicParams& params, float rounding)
|
||||
{
|
||||
// Draw semi-transparent tint without blur
|
||||
// This is the "Tinted Only" fallback mode (not affected by UI opacity)
|
||||
// Draw semi-transparent tint without blur — scale by UI opacity
|
||||
// so the tinted fallback also respects the card transparency slider.
|
||||
ImU32 tintCol = ImGui::ColorConvertFloat4ToU32(
|
||||
ImVec4(params.tintColor.x, params.tintColor.y, params.tintColor.z,
|
||||
params.tintColor.w * params.tintOpacity * 0.9f)
|
||||
params.tintColor.w * params.tintOpacity * 0.9f * settings_.uiOpacity)
|
||||
);
|
||||
drawList->AddRectFilled(pMin, pMax, tintCol, rounding);
|
||||
|
||||
@@ -1528,11 +1528,11 @@ void AcrylicMaterial::drawRect(ImDrawList* drawList, const ImVec2& pMin, const I
|
||||
(void*)blurTex, u0, v0, u1, v1);
|
||||
}
|
||||
|
||||
// Draw the blurred background. Glass opacity from fallbackColor
|
||||
// alpha. Blur always renders at full strength; UI opacity is
|
||||
// handled separately by card surface alpha.
|
||||
// Draw the blurred background. Scale by both the glass preset
|
||||
// opacity (fallbackColor.w) AND the user's UI opacity slider so
|
||||
// that lowering card opacity lets the sharp background through.
|
||||
uint8_t glassAlpha = (uint8_t)std::min(255.f,
|
||||
std::max(0.f, params.fallbackColor.w * 255.f));
|
||||
std::max(0.f, params.fallbackColor.w * settings_.uiOpacity * 255.f));
|
||||
|
||||
if (blurTex) {
|
||||
drawList->AddImageRounded(
|
||||
@@ -1559,7 +1559,7 @@ void AcrylicMaterial::drawTintedRect(ImDrawList* drawList, const ImVec2& pMin, c
|
||||
{
|
||||
ImU32 tintCol = ImGui::ColorConvertFloat4ToU32(
|
||||
ImVec4(params.tintColor.x, params.tintColor.y, params.tintColor.z,
|
||||
params.tintColor.w * params.tintOpacity * 0.9f));
|
||||
params.tintColor.w * params.tintOpacity * 0.9f * settings_.uiOpacity));
|
||||
drawList->AddRectFilled(pMin, pMax, tintCol, rounding);
|
||||
|
||||
// Noise grain overlay — single draw call via pre-tiled texture
|
||||
|
||||
@@ -105,6 +105,12 @@ public:
|
||||
bool hasSandstorm() const { return enabled_ && sandstorm_.enabled; }
|
||||
bool hasViewportOverlay() const { return enabled_ && (viewport_overlay_.colorWashEnabled || viewport_overlay_.vignetteEnabled); }
|
||||
|
||||
/// True when any per-panel visual effect is active (rainbow, shimmer, specular, edge, ember, gradient)
|
||||
bool hasAnyPanelEffect() const {
|
||||
return hasRainbowBorder() || hasShimmer() || hasSpecularGlare()
|
||||
|| hasEdgeTrace() || hasEmberRise() || hasGradientBorder();
|
||||
}
|
||||
|
||||
/// True when any time-dependent effect is active and needs continuous redraws
|
||||
bool hasActiveAnimation() const {
|
||||
if (!enabled_) return false;
|
||||
|
||||
187
src/ui/layout.h
187
src/ui/layout.h
@@ -185,6 +185,102 @@ inline float kItemSpacing() { return schema::UI().drawElement("spacing", "it
|
||||
inline float kLabelValueGap() { return schema::UI().drawElement("spacing", "label-value").sizeOr(4.0f) * dpiScale(); }
|
||||
inline float kSeparatorGap() { return schema::UI().drawElement("spacing", "separator").sizeOr(20.0f) * dpiScale(); }
|
||||
|
||||
// ============================================================================
|
||||
// Per-frame cache — populated once via beginFrame(), avoids repeated
|
||||
// schema hash-map lookups for the hottest accessors.
|
||||
// ============================================================================
|
||||
|
||||
namespace detail {
|
||||
|
||||
struct FrameCache {
|
||||
uint32_t gen = 0; // schema generation when last populated
|
||||
float rawDp = 1.0f; // rawDpiScale() snapshot
|
||||
float dp = 1.0f; // dpiScale() snapshot
|
||||
|
||||
// Spacing tokens (raw, unscaled)
|
||||
float spXs = 2.0f;
|
||||
float spSm = 4.0f;
|
||||
float spMd = 8.0f;
|
||||
float spLg = 12.0f;
|
||||
float spXl = 16.0f;
|
||||
float spXxl = 24.0f;
|
||||
|
||||
// Responsive scale config
|
||||
float refW = 1200.0f;
|
||||
float refH = 700.0f;
|
||||
float minHS = 0.5f;
|
||||
float maxHS = 1.5f;
|
||||
float minVS = 0.5f;
|
||||
float maxVS = 1.4f;
|
||||
float minDen = 0.6f;
|
||||
float maxDen = 1.2f;
|
||||
|
||||
// Breakpoint thresholds (raw, unscaled)
|
||||
float compactW = 500.0f;
|
||||
float compactH = 450.0f;
|
||||
float expandedW = 900.0f;
|
||||
float expandedH = 750.0f;
|
||||
|
||||
// Glass / card helpers (raw, unscaled)
|
||||
float glassRnd = 8.0f;
|
||||
float cardPad = 12.0f;
|
||||
float cardGap = 8.0f;
|
||||
};
|
||||
|
||||
inline FrameCache& frameCache() { static FrameCache c; return c; }
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/**
|
||||
* @brief Refresh the per-frame layout cache.
|
||||
*
|
||||
* Call once per frame (e.g., from App::preFrame) BEFORE any rendering.
|
||||
* Reads all high-frequency TOML values into fast statics. Invalidated
|
||||
* automatically when the schema generation changes (hot-reload).
|
||||
*/
|
||||
inline void beginFrame() {
|
||||
auto& c = detail::frameCache();
|
||||
uint32_t g = schema::UI().generation();
|
||||
// Also capture DPI each frame (can change on display-scale events)
|
||||
float curRawDp = rawDpiScale();
|
||||
float curDp = dpiScale();
|
||||
if (g == c.gen && curRawDp == c.rawDp && curDp == c.dp) return;
|
||||
c.gen = g;
|
||||
c.rawDp = curRawDp;
|
||||
c.dp = curDp;
|
||||
|
||||
const auto& S = schema::UI();
|
||||
|
||||
// Spacing tokens
|
||||
c.spXs = S.drawElement("spacing-tokens", "xs").sizeOr(2.0f);
|
||||
c.spSm = S.drawElement("spacing-tokens", "sm").sizeOr(4.0f);
|
||||
c.spMd = S.drawElement("spacing-tokens", "md").sizeOr(8.0f);
|
||||
c.spLg = S.drawElement("spacing-tokens", "lg").sizeOr(12.0f);
|
||||
c.spXl = S.drawElement("spacing-tokens", "xl").sizeOr(16.0f);
|
||||
c.spXxl = S.drawElement("spacing-tokens", "xxl").sizeOr(24.0f);
|
||||
|
||||
// Responsive config
|
||||
c.refW = S.drawElement("responsive", "ref-width").sizeOr(1200.0f);
|
||||
c.refH = S.drawElement("responsive", "ref-height").sizeOr(700.0f);
|
||||
c.minHS = S.drawElement("responsive", "min-h-scale").sizeOr(0.5f);
|
||||
c.maxHS = S.drawElement("responsive", "max-h-scale").sizeOr(1.5f);
|
||||
c.minVS = S.drawElement("responsive", "min-v-scale").sizeOr(0.5f);
|
||||
c.maxVS = S.drawElement("responsive", "max-v-scale").sizeOr(1.4f);
|
||||
c.minDen = S.drawElement("responsive", "min-density").sizeOr(0.6f);
|
||||
c.maxDen = S.drawElement("responsive", "max-density").sizeOr(1.2f);
|
||||
|
||||
// Breakpoints
|
||||
c.compactW = S.drawElement("responsive", "compact-width").sizeOr(500.0f);
|
||||
c.compactH = S.drawElement("responsive", "compact-height").sizeOr(450.0f);
|
||||
c.expandedW = S.drawElement("responsive", "expanded-width").sizeOr(900.0f);
|
||||
c.expandedH = S.drawElement("responsive", "expanded-height").sizeOr(750.0f);
|
||||
|
||||
// Glass / card
|
||||
c.glassRnd = S.drawElement("responsive", "glass-rounding").sizeOr(8.0f);
|
||||
c.cardPad = S.drawElement("responsive", "card-inner-padding").sizeOr(12.0f);
|
||||
c.cardGap = S.drawElement("responsive", "card-gap").sizeOr(8.0f);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Layout Tier (responsive breakpoints)
|
||||
// ============================================================================
|
||||
@@ -203,12 +299,12 @@ enum class LayoutTier { Compact, Normal, Expanded };
|
||||
* Call after ImGui::BeginChild for the content area, or pass explicit avail.
|
||||
*/
|
||||
inline LayoutTier currentTier() {
|
||||
const auto& S = schema::UI();
|
||||
float dp = dpiScale();
|
||||
float cw = S.drawElement("responsive", "compact-width").sizeOr(500.0f) * dp;
|
||||
float ch = S.drawElement("responsive", "compact-height").sizeOr(450.0f) * dp;
|
||||
float ew = S.drawElement("responsive", "expanded-width").sizeOr(900.0f) * dp;
|
||||
float eh = S.drawElement("responsive", "expanded-height").sizeOr(750.0f) * dp;
|
||||
const auto& c = detail::frameCache();
|
||||
float dp = c.dp;
|
||||
float cw = c.compactW * dp;
|
||||
float ch = c.compactH * dp;
|
||||
float ew = c.expandedW * dp;
|
||||
float eh = c.expandedH * dp;
|
||||
ImVec2 avail = ImGui::GetContentRegionAvail();
|
||||
if (avail.x < cw || avail.y < ch) return LayoutTier::Compact;
|
||||
if (avail.x > ew && avail.y > eh) return LayoutTier::Expanded;
|
||||
@@ -216,12 +312,12 @@ inline LayoutTier currentTier() {
|
||||
}
|
||||
|
||||
inline LayoutTier currentTier(float availW, float availH) {
|
||||
const auto& S = schema::UI();
|
||||
float dp = dpiScale();
|
||||
float cw = S.drawElement("responsive", "compact-width").sizeOr(500.0f) * dp;
|
||||
float ch = S.drawElement("responsive", "compact-height").sizeOr(450.0f) * dp;
|
||||
float ew = S.drawElement("responsive", "expanded-width").sizeOr(900.0f) * dp;
|
||||
float eh = S.drawElement("responsive", "expanded-height").sizeOr(750.0f) * dp;
|
||||
const auto& c = detail::frameCache();
|
||||
float dp = c.dp;
|
||||
float cw = c.compactW * dp;
|
||||
float ch = c.compactH * dp;
|
||||
float ew = c.expandedW * dp;
|
||||
float eh = c.expandedH * dp;
|
||||
if (availW < cw || availH < ch) return LayoutTier::Compact;
|
||||
if (availW > ew && availH > eh) return LayoutTier::Expanded;
|
||||
return LayoutTier::Normal;
|
||||
@@ -240,15 +336,10 @@ inline LayoutTier currentTier(float availW, float availH) {
|
||||
* while emitting physical-pixel results.
|
||||
*/
|
||||
inline float hScale(float availWidth) {
|
||||
const auto& S = schema::UI();
|
||||
float rawDp = rawDpiScale(); // reference uses hardware DPI only
|
||||
float dp = dpiScale(); // output includes user font scale
|
||||
float rw = S.drawElement("responsive", "ref-width").sizeOr(1200.0f) * rawDp;
|
||||
float minH = S.drawElement("responsive", "min-h-scale").sizeOr(0.5f);
|
||||
float maxH = S.drawElement("responsive", "max-h-scale").sizeOr(1.5f);
|
||||
// Clamp the logical (DPI-neutral) portion, then apply effective DPI.
|
||||
float logical = std::clamp(availWidth / rw, minH, maxH);
|
||||
return logical * dp;
|
||||
const auto& c = detail::frameCache();
|
||||
float rw = c.refW * c.rawDp;
|
||||
float logical = std::clamp(availWidth / rw, c.minHS, c.maxHS);
|
||||
return logical * c.dp;
|
||||
}
|
||||
|
||||
inline float hScale() {
|
||||
@@ -261,14 +352,10 @@ inline float hScale() {
|
||||
* Same decomposition as hScale — logical clamp × DPI.
|
||||
*/
|
||||
inline float vScale(float availHeight) {
|
||||
const auto& S = schema::UI();
|
||||
float rawDp = rawDpiScale(); // reference uses hardware DPI only
|
||||
float dp = dpiScale(); // output includes user font scale
|
||||
float rh = S.drawElement("responsive", "ref-height").sizeOr(700.0f) * rawDp;
|
||||
float minV = S.drawElement("responsive", "min-v-scale").sizeOr(0.5f);
|
||||
float maxV = S.drawElement("responsive", "max-v-scale").sizeOr(1.4f);
|
||||
float logical = std::clamp(availHeight / rh, minV, maxV);
|
||||
return logical * dp;
|
||||
const auto& c = detail::frameCache();
|
||||
float rh = c.refH * c.rawDp;
|
||||
float logical = std::clamp(availHeight / rh, c.minVS, c.maxVS);
|
||||
return logical * c.dp;
|
||||
}
|
||||
|
||||
inline float vScale() {
|
||||
@@ -282,14 +369,10 @@ inline float vScale() {
|
||||
* values scale proportionally with fonts and style.
|
||||
*/
|
||||
inline float densityScale(float availHeight) {
|
||||
const auto& S = schema::UI();
|
||||
float rawDp = rawDpiScale(); // reference uses hardware DPI only
|
||||
float dp = dpiScale(); // output includes user font scale
|
||||
float rh = S.drawElement("responsive", "ref-height").sizeOr(700.0f) * rawDp;
|
||||
float minDen = S.drawElement("responsive", "min-density").sizeOr(0.6f);
|
||||
float maxDen = S.drawElement("responsive", "max-density").sizeOr(1.2f);
|
||||
float logical = std::clamp(availHeight / rh, minDen, maxDen);
|
||||
return logical * dp;
|
||||
const auto& c = detail::frameCache();
|
||||
float rh = c.refH * c.rawDp;
|
||||
float logical = std::clamp(availHeight / rh, c.minDen, c.maxDen);
|
||||
return logical * c.dp;
|
||||
}
|
||||
|
||||
inline float densityScale() {
|
||||
@@ -301,33 +384,33 @@ inline float densityScale() {
|
||||
// ============================================================================
|
||||
|
||||
/** @brief Get spacing token scaled by current density. */
|
||||
inline float spacingXs() { return schema::UI().drawElement("spacing-tokens", "xs").sizeOr(2.0f) * densityScale(); }
|
||||
inline float spacingSm() { return schema::UI().drawElement("spacing-tokens", "sm").sizeOr(4.0f) * densityScale(); }
|
||||
inline float spacingMd() { return schema::UI().drawElement("spacing-tokens", "md").sizeOr(8.0f) * densityScale(); }
|
||||
inline float spacingLg() { return schema::UI().drawElement("spacing-tokens", "lg").sizeOr(12.0f) * densityScale(); }
|
||||
inline float spacingXl() { return schema::UI().drawElement("spacing-tokens", "xl").sizeOr(16.0f) * densityScale(); }
|
||||
inline float spacingXxl() { return schema::UI().drawElement("spacing-tokens", "xxl").sizeOr(24.0f) * densityScale(); }
|
||||
inline float spacingXs() { return detail::frameCache().spXs * densityScale(); }
|
||||
inline float spacingSm() { return detail::frameCache().spSm * densityScale(); }
|
||||
inline float spacingMd() { return detail::frameCache().spMd * densityScale(); }
|
||||
inline float spacingLg() { return detail::frameCache().spLg * densityScale(); }
|
||||
inline float spacingXl() { return detail::frameCache().spXl * densityScale(); }
|
||||
inline float spacingXxl() { return detail::frameCache().spXxl * densityScale(); }
|
||||
|
||||
/** @brief Get raw (unscaled) spacing token. */
|
||||
inline float spacingXsRaw() { return schema::UI().drawElement("spacing-tokens", "xs").sizeOr(2.0f); }
|
||||
inline float spacingSmRaw() { return schema::UI().drawElement("spacing-tokens", "sm").sizeOr(4.0f); }
|
||||
inline float spacingMdRaw() { return schema::UI().drawElement("spacing-tokens", "md").sizeOr(8.0f); }
|
||||
inline float spacingLgRaw() { return schema::UI().drawElement("spacing-tokens", "lg").sizeOr(12.0f); }
|
||||
inline float spacingXlRaw() { return schema::UI().drawElement("spacing-tokens", "xl").sizeOr(16.0f); }
|
||||
inline float spacingXxlRaw() { return schema::UI().drawElement("spacing-tokens", "xxl").sizeOr(24.0f); }
|
||||
inline float spacingXsRaw() { return detail::frameCache().spXs; }
|
||||
inline float spacingSmRaw() { return detail::frameCache().spSm; }
|
||||
inline float spacingMdRaw() { return detail::frameCache().spMd; }
|
||||
inline float spacingLgRaw() { return detail::frameCache().spLg; }
|
||||
inline float spacingXlRaw() { return detail::frameCache().spXl; }
|
||||
inline float spacingXxlRaw() { return detail::frameCache().spXxl; }
|
||||
|
||||
// ============================================================================
|
||||
// Responsive Globals Helpers
|
||||
// ============================================================================
|
||||
|
||||
/** @brief Default glass panel rounding (8.0 default). */
|
||||
inline float glassRounding() { return schema::UI().drawElement("responsive", "glass-rounding").sizeOr(8.0f) * dpiScale(); }
|
||||
inline float glassRounding() { return detail::frameCache().glassRnd * dpiScale(); }
|
||||
|
||||
/** @brief Default card inner padding (12.0 default). */
|
||||
inline float cardInnerPadding() { return schema::UI().drawElement("responsive", "card-inner-padding").sizeOr(12.0f) * dpiScale(); }
|
||||
inline float cardInnerPadding() { return detail::frameCache().cardPad * dpiScale(); }
|
||||
|
||||
/** @brief Default card gap (8.0 default). */
|
||||
inline float cardGap() { return schema::UI().drawElement("responsive", "card-gap").sizeOr(8.0f) * dpiScale(); }
|
||||
inline float cardGap() { return detail::frameCache().cardGap * dpiScale(); }
|
||||
|
||||
/**
|
||||
* @brief Compute a responsive card height from a base value.
|
||||
|
||||
@@ -351,18 +351,24 @@ inline void DrawGlassPanel(ImDrawList* dl, const ImVec2& pMin,
|
||||
|
||||
// Noise grain overlay — drawn OVER the surface overlay so card
|
||||
// opacity doesn't hide it. Gives cards a tactile paper feel.
|
||||
// Noise tint is cached per-generation, opacity checked each panel.
|
||||
{
|
||||
float noiseMul = dragonx::ui::effects::ImGuiAcrylic::GetNoiseOpacity();
|
||||
if (noiseMul > 0.0f) {
|
||||
uint8_t origAlpha = (s_glassNoiseTint >> IM_COL32_A_SHIFT) & 0xFF;
|
||||
uint8_t scaledAlpha = static_cast<uint8_t>(std::min(255.0f, origAlpha * noiseMul));
|
||||
// Tint base color changes only on theme reload; opacity slider may change per-frame
|
||||
static uint32_t s_noiseGen = 0;
|
||||
static uint8_t s_baseAlpha = 0;
|
||||
if (curGen != s_noiseGen) {
|
||||
s_noiseGen = curGen;
|
||||
s_baseAlpha = (s_glassNoiseTint >> IM_COL32_A_SHIFT) & 0xFF;
|
||||
}
|
||||
uint8_t scaledAlpha = static_cast<uint8_t>(std::min(255.0f, s_baseAlpha * noiseMul));
|
||||
ImU32 noiseTint = (s_glassNoiseTint & ~(0xFFu << IM_COL32_A_SHIFT)) | (scaledAlpha << IM_COL32_A_SHIFT);
|
||||
float inset = spec.rounding * 0.3f;
|
||||
ImVec2 clipMin(pMin.x + inset, pMin.y + inset);
|
||||
ImVec2 clipMax(pMax.x - inset, pMax.y - inset);
|
||||
dl->PushClipRect(clipMin, clipMax, true);
|
||||
dragonx::util::DrawTiledNoiseRect(dl, clipMin, clipMax, noiseTint);
|
||||
dl->PopClipRect();
|
||||
ImVec2 noiseMin(pMin.x + inset, pMin.y + inset);
|
||||
ImVec2 noiseMax(pMax.x - inset, pMax.y - inset);
|
||||
// Image rect matches clip bounds exactly — no PushClipRect needed
|
||||
dragonx::util::DrawTiledNoiseRect(dl, noiseMin, noiseMax, noiseTint);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -375,18 +381,20 @@ inline void DrawGlassPanel(ImDrawList* dl, const ImVec2& pMin,
|
||||
// Theme visual effects drawn on ForegroundDrawList so they
|
||||
// render above card content (text, values, etc.), not below.
|
||||
auto& fx = effects::ThemeEffects::instance();
|
||||
ImDrawList* fxDl = ImGui::GetForegroundDrawList();
|
||||
if (fx.hasRainbowBorder()) {
|
||||
fx.drawRainbowBorder(fxDl, pMin, pMax, spec.rounding, spec.borderWidth);
|
||||
if (fx.hasAnyPanelEffect()) {
|
||||
ImDrawList* fxDl = ImGui::GetForegroundDrawList();
|
||||
if (fx.hasRainbowBorder()) {
|
||||
fx.drawRainbowBorder(fxDl, pMin, pMax, spec.rounding, spec.borderWidth);
|
||||
}
|
||||
if (fx.hasShimmer()) {
|
||||
fx.drawShimmer(fxDl, pMin, pMax, spec.rounding);
|
||||
}
|
||||
if (fx.hasSpecularGlare()) {
|
||||
fx.drawSpecularGlare(fxDl, pMin, pMax, spec.rounding);
|
||||
}
|
||||
// Per-panel theme effects: edge trace + ember rise
|
||||
fx.drawPanelEffects(fxDl, pMin, pMax, spec.rounding);
|
||||
}
|
||||
if (fx.hasShimmer()) {
|
||||
fx.drawShimmer(fxDl, pMin, pMax, spec.rounding);
|
||||
}
|
||||
if (fx.hasSpecularGlare()) {
|
||||
fx.drawSpecularGlare(fxDl, pMin, pMax, spec.rounding);
|
||||
}
|
||||
// Per-panel theme effects: edge trace + ember rise
|
||||
fx.drawPanelEffects(fxDl, pMin, pMax, spec.rounding);
|
||||
} else {
|
||||
// Low-spec opaque fallback
|
||||
dl->AddRectFilled(pMin, pMax,
|
||||
@@ -749,8 +757,10 @@ inline void ApplySmoothScroll(float speed = 12.0f)
|
||||
s.current = actualY;
|
||||
}
|
||||
|
||||
// Capture mouse wheel when hovered
|
||||
if (ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows)) {
|
||||
// Capture mouse wheel when hovered, but not when a popup (combo dropdown
|
||||
// etc.) is open — let the popup handle its own scrolling exclusively.
|
||||
bool popupOpen = ImGui::IsPopupOpen("", ImGuiPopupFlags_AnyPopupId | ImGuiPopupFlags_AnyPopupLevel);
|
||||
if (!popupOpen && ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows)) {
|
||||
float wheel = ImGui::GetIO().MouseWheel;
|
||||
if (wheel != 0.0f) {
|
||||
float step = ImGui::GetTextLineHeightWithSpacing() * 3.0f;
|
||||
|
||||
@@ -246,11 +246,14 @@ ImFont* Typography::loadFont(ImGuiIO& io, int weight, float size, const char* na
|
||||
cfg.PixelSnapH = true;
|
||||
|
||||
// Include default ASCII + Latin, Latin Extended (for Spanish/multilingual),
|
||||
// plus arrows (⇄ U+21C4), math (≈ U+2248),
|
||||
// general punctuation (— U+2014, … U+2026, etc.)
|
||||
// Cyrillic (for Russian), Greek, plus arrows (⇄ U+21C4),
|
||||
// math (≈ U+2248), general punctuation (— U+2014, … U+2026, etc.)
|
||||
static const ImWchar glyphRanges[] = {
|
||||
0x0020, 0x00FF, // Basic Latin + Latin-1 Supplement
|
||||
0x0100, 0x024F, // Latin Extended-A + Latin Extended-B (Spanish, etc.)
|
||||
0x0370, 0x03FF, // Greek and Coptic
|
||||
0x0400, 0x04FF, // Cyrillic (Russian, Ukrainian, etc.)
|
||||
0x0500, 0x052F, // Cyrillic Supplement
|
||||
0x2000, 0x206F, // General Punctuation (em dash, ellipsis, etc.)
|
||||
0x2190, 0x21FF, // Arrows (includes ⇄ U+21C4, ↻ U+21BB)
|
||||
0x2200, 0x22FF, // Mathematical Operators (includes ≈ U+2248)
|
||||
@@ -269,6 +272,36 @@ ImFont* Typography::loadFont(ImGuiIO& io, int weight, float size, const char* na
|
||||
|
||||
if (font) {
|
||||
DEBUG_LOGF("Typography: Loaded %s (%.0fpx) as '%s'\n", name, size, cfg.Name);
|
||||
|
||||
// Merge CJK fallback glyphs (Chinese/Japanese/Korean) from subset font
|
||||
if (g_noto_cjk_subset_size > 0) {
|
||||
void* cjkCopy = IM_ALLOC(g_noto_cjk_subset_size);
|
||||
memcpy(cjkCopy, g_noto_cjk_subset_data, g_noto_cjk_subset_size);
|
||||
|
||||
ImFontConfig cjkCfg;
|
||||
cjkCfg.FontDataOwnedByAtlas = true;
|
||||
cjkCfg.MergeMode = true; // merge into the font we just loaded
|
||||
cjkCfg.OversampleH = 1;
|
||||
cjkCfg.OversampleV = 1;
|
||||
cjkCfg.PixelSnapH = true;
|
||||
cjkCfg.GlyphMinAdvanceX = 0;
|
||||
// CJK Unified Ideographs + Hiragana + Katakana + Hangul + fullwidth punctuation
|
||||
static const ImWchar cjkRanges[] = {
|
||||
0x2E80, 0x2FDF, // CJK Radicals
|
||||
0x3000, 0x30FF, // CJK Symbols, Hiragana, Katakana
|
||||
0x3100, 0x312F, // Bopomofo
|
||||
0x31F0, 0x31FF, // Katakana Extensions
|
||||
0x3400, 0x4DBF, // CJK Extension A
|
||||
0x4E00, 0x9FFF, // CJK Unified Ideographs
|
||||
0xAC00, 0xD7AF, // Hangul Syllables
|
||||
0xFF00, 0xFFEF, // Fullwidth Forms
|
||||
0,
|
||||
};
|
||||
cjkCfg.GlyphRanges = cjkRanges;
|
||||
snprintf(cjkCfg.Name, sizeof(cjkCfg.Name), "NotoSansCJK %.0fpx (merge)", size);
|
||||
|
||||
io.Fonts->AddFontFromMemoryTTF(cjkCopy, g_noto_cjk_subset_size, size, &cjkCfg);
|
||||
}
|
||||
} else {
|
||||
DEBUG_LOGF("Typography: Failed to load %s\n", name);
|
||||
IM_FREE(fontDataCopy);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -45,6 +45,7 @@ bool UISchema::loadFromFile(const std::string& path) {
|
||||
|
||||
// Clear previous data
|
||||
elements_.clear();
|
||||
styleCache_.clear();
|
||||
backgroundImagePath_.clear();
|
||||
logoImagePath_.clear();
|
||||
|
||||
@@ -92,6 +93,7 @@ bool UISchema::loadFromFile(const std::string& path) {
|
||||
overlayPath_.clear(); // No overlay yet
|
||||
loaded_ = true;
|
||||
dirty_ = false;
|
||||
++generation_;
|
||||
|
||||
// Record initial modification time
|
||||
try {
|
||||
@@ -126,6 +128,7 @@ bool UISchema::loadFromString(const std::string& tomlStr, const std::string& lab
|
||||
|
||||
// Clear previous data
|
||||
elements_.clear();
|
||||
styleCache_.clear();
|
||||
backgroundImagePath_.clear();
|
||||
logoImagePath_.clear();
|
||||
|
||||
@@ -222,6 +225,7 @@ bool UISchema::mergeOverlayFromFile(const std::string& path) {
|
||||
// Track overlay file for hot-reload
|
||||
currentPath_ = path;
|
||||
++generation_;
|
||||
styleCache_.clear(); // Invalidate after overlay merges new elements
|
||||
try {
|
||||
lastModTime_ = std::filesystem::last_write_time(path);
|
||||
} catch (const std::filesystem::filesystem_error&) {}
|
||||
@@ -495,6 +499,9 @@ void UISchema::applyIfDirty() {
|
||||
if (!dirty_) return;
|
||||
dirty_ = false;
|
||||
|
||||
// Clear style cache before snapshot so we re-read from TOML
|
||||
styleCache_.clear();
|
||||
|
||||
// Snapshot font sizes before reload for change detection
|
||||
static const char* fontKeys[] = {
|
||||
"h1", "h2", "h3", "h4", "h5", "h6",
|
||||
@@ -513,10 +520,17 @@ void UISchema::applyIfDirty() {
|
||||
|
||||
DEBUG_LOGF("[UISchema] Hot-reload: re-parsing %s\n", currentPath_.c_str());
|
||||
|
||||
// Save overlay path before reloading — loadFromFile/loadFromString clear it
|
||||
std::string savedOverlay = overlayPath_;
|
||||
|
||||
// If an overlay is active, reload base first then re-merge overlay
|
||||
if (!overlayPath_.empty() && !basePath_.empty()) {
|
||||
if (!savedOverlay.empty() && !basePath_.empty()) {
|
||||
loadFromFile(basePath_);
|
||||
mergeOverlayFromFile(overlayPath_);
|
||||
mergeOverlayFromFile(savedOverlay);
|
||||
} else if (!savedOverlay.empty() && !embeddedTomlStr_.empty()) {
|
||||
// Embedded base (e.g. Windows single-file): reload from stored string
|
||||
loadFromString(embeddedTomlStr_, "embedded-reload");
|
||||
mergeOverlayFromFile(savedOverlay);
|
||||
} else {
|
||||
loadFromFile(currentPath_);
|
||||
}
|
||||
@@ -836,15 +850,26 @@ SeparatorStyle UISchema::separator(const std::string& section, const std::string
|
||||
return result;
|
||||
}
|
||||
|
||||
DrawElementStyle UISchema::drawElement(const std::string& section, const std::string& name) const {
|
||||
DrawElementStyle result;
|
||||
const DrawElementStyle& UISchema::drawElement(const std::string& section, const std::string& name) const {
|
||||
static const DrawElementStyle s_empty{};
|
||||
|
||||
const void* elem = findElement(section, name);
|
||||
if (elem) {
|
||||
detail::parseDrawElementStyle(elem, result);
|
||||
std::string key = section + "." + name;
|
||||
|
||||
// Return from cache if already parsed
|
||||
auto cit = styleCache_.find(key);
|
||||
if (cit != styleCache_.end()) {
|
||||
return cit->second;
|
||||
}
|
||||
|
||||
return result;
|
||||
// Parse from TOML and cache
|
||||
const void* elem = findElement(section, name);
|
||||
if (!elem) {
|
||||
return s_empty;
|
||||
}
|
||||
|
||||
auto [it, _] = styleCache_.emplace(std::move(key), DrawElementStyle{});
|
||||
detail::parseDrawElementStyle(elem, it->second);
|
||||
return it->second;
|
||||
}
|
||||
|
||||
} // namespace schema
|
||||
|
||||
@@ -255,8 +255,12 @@ public:
|
||||
|
||||
/**
|
||||
* @brief Look up a DrawList custom element style
|
||||
*
|
||||
* Returns a cached const reference. The cache is invalidated on
|
||||
* hot-reload (applyIfDirty) and full loads. For missing elements
|
||||
* a static empty sentinel is returned.
|
||||
*/
|
||||
DrawElementStyle drawElement(const std::string& section, const std::string& name) const;
|
||||
const DrawElementStyle& drawElement(const std::string& section, const std::string& name) const;
|
||||
|
||||
/**
|
||||
* @brief Find a raw stored element by section and name.
|
||||
@@ -365,6 +369,10 @@ private:
|
||||
std::any data; // holds toml::table at runtime
|
||||
};
|
||||
std::unordered_map<std::string, StoredElement> elements_;
|
||||
|
||||
// Parsed DrawElementStyle cache — avoids repeated TOML table iteration.
|
||||
// Populated lazily on first drawElement() lookup, cleared on reload.
|
||||
mutable std::unordered_map<std::string, DrawElementStyle> styleCache_;
|
||||
};
|
||||
|
||||
// Convenience alias
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "layout.h"
|
||||
#include "schema/ui_schema.h"
|
||||
#include "../embedded/IconsMaterialDesign.h"
|
||||
#include "../util/i18n.h"
|
||||
#include <cstdio>
|
||||
#include <cmath>
|
||||
|
||||
@@ -34,25 +35,35 @@ enum class NavPage {
|
||||
};
|
||||
|
||||
struct NavItem {
|
||||
const char* label;
|
||||
const char* label; // fallback label (English)
|
||||
NavPage page;
|
||||
const char* section_label; // if non-null, render section label above this item
|
||||
const char* section_label; // if non-null, render section label above this item
|
||||
const char* tr_key; // i18n key for label
|
||||
const char* section_tr_key; // i18n key for section_label
|
||||
};
|
||||
|
||||
inline const NavItem kNavItems[] = {
|
||||
{ "Overview", NavPage::Overview, nullptr },
|
||||
{ "Send", NavPage::Send, nullptr },
|
||||
{ "Receive", NavPage::Receive, nullptr },
|
||||
{ "History", NavPage::History, nullptr },
|
||||
{ "Mining", NavPage::Mining, "TOOLS" },
|
||||
{ "Market", NavPage::Market, nullptr },
|
||||
{ "Console", NavPage::Console, "ADVANCED" },
|
||||
{ "Network", NavPage::Peers, nullptr },
|
||||
{ "Settings", NavPage::Settings, nullptr },
|
||||
{ "Overview", NavPage::Overview, nullptr, "overview", nullptr },
|
||||
{ "Send", NavPage::Send, nullptr, "send", nullptr },
|
||||
{ "Receive", NavPage::Receive, nullptr, "receive", nullptr },
|
||||
{ "History", NavPage::History, nullptr, "history", nullptr },
|
||||
{ "Mining", NavPage::Mining, "TOOLS", "mining", "tools" },
|
||||
{ "Market", NavPage::Market, nullptr, "market", nullptr },
|
||||
{ "Console", NavPage::Console, "ADVANCED","console", "advanced" },
|
||||
{ "Network", NavPage::Peers, nullptr, "network", nullptr },
|
||||
{ "Settings", NavPage::Settings, nullptr, "settings", nullptr },
|
||||
};
|
||||
static_assert(sizeof(kNavItems) / sizeof(kNavItems[0]) == (int)NavPage::Count_,
|
||||
"kNavItems must match NavPage::Count_");
|
||||
|
||||
// Get translated nav label at runtime
|
||||
inline const char* NavLabel(const NavItem& item) {
|
||||
return item.tr_key ? TR(item.tr_key) : item.label;
|
||||
}
|
||||
inline const char* NavSectionLabel(const NavItem& item) {
|
||||
return item.section_tr_key ? TR(item.section_tr_key) : item.section_label;
|
||||
}
|
||||
|
||||
// Get the Material Design icon string for a navigation page.
|
||||
inline const char* GetNavIconMD(NavPage page)
|
||||
{
|
||||
@@ -541,7 +552,7 @@ inline bool RenderSidebar(NavPage& current, float sidebarWidth, float contentHei
|
||||
float olFsz = ScaledFontSize(olFont);
|
||||
dl->AddText(olFont, olFsz,
|
||||
ImVec2(wp.x + sbSectionLabelPadLeft, labelY),
|
||||
ImGui::ColorConvertFloat4ToU32(olCol), item.section_label);
|
||||
ImGui::ColorConvertFloat4ToU32(olCol), NavSectionLabel(item));
|
||||
ImGui::Dummy(ImVec2(0, olFsz + 2.0f));
|
||||
} else if (item.section_label && !showLabels) {
|
||||
// Collapsed: thin separator instead of label
|
||||
@@ -609,11 +620,19 @@ inline bool RenderSidebar(NavPage& current, float sidebarWidth, float contentHei
|
||||
ImU32 textCol = selected ? Primary() : OnSurfaceMedium();
|
||||
|
||||
if (showLabels) {
|
||||
// Measure total width of icon + gap + label, then center
|
||||
// Measure total width of icon + gap + label, then center.
|
||||
// If the translated label is too wide, shrink the font to fit.
|
||||
ImFont* font = selected ? Type().subtitle2() : Type().body2();
|
||||
float gap = iconLabelGap;
|
||||
float lblFsz = ScaledFontSize(font);
|
||||
ImVec2 labelSz = font->CalcTextSizeA(lblFsz, 1000.0f, 0.0f, item.label);
|
||||
float btnW = indMax.x - indMin.x;
|
||||
float maxLabelW = btnW - iconS * 2.0f - gap - Layout::spacingXs() * 2;
|
||||
ImVec2 labelSz = font->CalcTextSizeA(lblFsz, 1000.0f, 0.0f, NavLabel(item));
|
||||
if (labelSz.x > maxLabelW && maxLabelW > 0) {
|
||||
float shrink = maxLabelW / labelSz.x;
|
||||
lblFsz *= shrink;
|
||||
labelSz = font->CalcTextSizeA(lblFsz, 1000.0f, 0.0f, NavLabel(item));
|
||||
}
|
||||
float totalW = iconS * 2.0f + gap + labelSz.x;
|
||||
float btnCX = (indMin.x + indMax.x) * 0.5f;
|
||||
float startX = btnCX - totalW * 0.5f;
|
||||
@@ -625,7 +644,7 @@ inline bool RenderSidebar(NavPage& current, float sidebarWidth, float contentHei
|
||||
ImVec4 lc = ImGui::ColorConvertU32ToFloat4(textCol);
|
||||
lc.w *= expandFrac;
|
||||
dl->AddText(font, lblFsz, ImVec2(labelX, textY),
|
||||
ImGui::ColorConvertFloat4ToU32(lc), item.label);
|
||||
ImGui::ColorConvertFloat4ToU32(lc), NavLabel(item));
|
||||
} else {
|
||||
float iconCX = (indMin.x + indMax.x) * 0.5f;
|
||||
DrawNavIcon(dl, item.page, iconCX, iconCY, iconS, textCol);
|
||||
@@ -633,7 +652,7 @@ inline bool RenderSidebar(NavPage& current, float sidebarWidth, float contentHei
|
||||
|
||||
// Tooltip when collapsed + hovered
|
||||
if (!showLabels && hovered) {
|
||||
ImGui::SetTooltip("%s", item.label);
|
||||
ImGui::SetTooltip("%s", NavLabel(item));
|
||||
}
|
||||
|
||||
// ---- Badge indicator ----
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "about_dialog.h"
|
||||
#include "../../app.h"
|
||||
#include "../../config/version.h"
|
||||
#include "../../util/i18n.h"
|
||||
#include "../schema/ui_schema.h"
|
||||
#include "../material/type.h"
|
||||
#include "../material/draw_helpers.h"
|
||||
@@ -23,7 +24,7 @@ void RenderAboutDialog(App* app, bool* p_open)
|
||||
auto versionLbl = S.label("dialogs.about", "version-label");
|
||||
auto editionLbl = S.label("dialogs.about", "edition-label");
|
||||
|
||||
if (!material::BeginOverlayDialog("About ObsidianDragon", p_open, win.width, 0.94f)) {
|
||||
if (!material::BeginOverlayDialog(TR("about_title"), p_open, win.width, 0.94f)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -38,33 +39,33 @@ void RenderAboutDialog(App* app, bool* p_open)
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
ImGui::SameLine(ImGui::GetWindowWidth() - editionLbl.position);
|
||||
ImGui::TextDisabled("ImGui Edition");
|
||||
ImGui::TextDisabled("%s", TR("about_edition"));
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
// Version info
|
||||
ImGui::Text("Version:");
|
||||
ImGui::Text("%s", TR("about_version"));
|
||||
ImGui::SameLine(versionLbl.position);
|
||||
ImGui::Text("%s", DRAGONX_VERSION);
|
||||
|
||||
ImGui::Text("ImGui:");
|
||||
ImGui::Text("%s", TR("about_imgui"));
|
||||
ImGui::SameLine(versionLbl.position);
|
||||
ImGui::Text("%s", IMGUI_VERSION);
|
||||
|
||||
ImGui::Text("Build Date:");
|
||||
ImGui::Text("%s", TR("about_build_date"));
|
||||
ImGui::SameLine(versionLbl.position);
|
||||
ImGui::Text("%s %s", __DATE__, __TIME__);
|
||||
|
||||
#ifdef DRAGONX_DEBUG
|
||||
ImGui::Text("Build Type:");
|
||||
ImGui::Text("%s", TR("about_build_type"));
|
||||
ImGui::SameLine(versionLbl.position);
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.0f, 1.0f), "Debug");
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.0f, 1.0f), "%s", TR("about_debug"));
|
||||
#else
|
||||
ImGui::Text("Build Type:");
|
||||
ImGui::Text("%s", TR("about_build_type"));
|
||||
ImGui::SameLine(versionLbl.position);
|
||||
ImGui::Text("Release");
|
||||
ImGui::Text("%s", TR("about_release"));
|
||||
#endif
|
||||
|
||||
// Daemon info
|
||||
@@ -73,22 +74,22 @@ void RenderAboutDialog(App* app, bool* p_open)
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Text("Daemon:");
|
||||
ImGui::Text("%s", TR("about_daemon"));
|
||||
ImGui::SameLine(versionLbl.position);
|
||||
ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f), "Connected");
|
||||
ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f), "%s", TR("connected"));
|
||||
|
||||
const auto& state = app->getWalletState();
|
||||
ImGui::Text("Chain:");
|
||||
ImGui::Text("%s", TR("about_chain"));
|
||||
ImGui::SameLine(versionLbl.position);
|
||||
ImGui::Text("ObsidianDragon");
|
||||
|
||||
ImGui::Text("Block Height:");
|
||||
ImGui::Text("%s", TR("about_block_height"));
|
||||
ImGui::SameLine(versionLbl.position);
|
||||
ImGui::Text("%d", state.sync.blocks);
|
||||
|
||||
ImGui::Text("Connections:");
|
||||
ImGui::Text("%s", TR("about_connections"));
|
||||
ImGui::SameLine(versionLbl.position);
|
||||
ImGui::Text("%zu peers", state.peers.size());
|
||||
ImGui::Text(TR("about_peers_count"), state.peers.size());
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
@@ -96,7 +97,7 @@ void RenderAboutDialog(App* app, bool* p_open)
|
||||
ImGui::Spacing();
|
||||
|
||||
// Credits
|
||||
ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "Credits");
|
||||
ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "%s", TR("about_credits"));
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::BulletText("The Hush Developers");
|
||||
@@ -110,19 +111,16 @@ void RenderAboutDialog(App* app, bool* p_open)
|
||||
ImGui::Spacing();
|
||||
|
||||
// License
|
||||
ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "License");
|
||||
ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "%s", TR("about_license"));
|
||||
ImGui::Spacing();
|
||||
ImGui::TextWrapped(
|
||||
"This software is released under the GNU General Public License v3 (GPLv3). "
|
||||
"You are free to use, modify, and distribute this software under the terms of the license."
|
||||
);
|
||||
ImGui::TextWrapped("%s", TR("about_license_text"));
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
// Links
|
||||
if (material::StyledButton("Website", ImVec2(linkBtn.width, 0), S.resolveFont(linkBtn.font))) {
|
||||
if (material::StyledButton(TR("about_website"), ImVec2(linkBtn.width, 0), S.resolveFont(linkBtn.font))) {
|
||||
#ifdef _WIN32
|
||||
system("start https://dragonx.is");
|
||||
#elif __APPLE__
|
||||
@@ -132,7 +130,7 @@ void RenderAboutDialog(App* app, bool* p_open)
|
||||
#endif
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (material::StyledButton("GitHub", ImVec2(linkBtn.width, 0), S.resolveFont(linkBtn.font))) {
|
||||
if (material::StyledButton(TR("about_github"), ImVec2(linkBtn.width, 0), S.resolveFont(linkBtn.font))) {
|
||||
#ifdef _WIN32
|
||||
system("start https://git.dragonx.is/dragonx/ObsidianDragon");
|
||||
#elif __APPLE__
|
||||
@@ -142,7 +140,7 @@ void RenderAboutDialog(App* app, bool* p_open)
|
||||
#endif
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (material::StyledButton("Block Explorer", ImVec2(linkBtn.width, 0), S.resolveFont(linkBtn.font))) {
|
||||
if (material::StyledButton(TR("about_block_explorer"), ImVec2(linkBtn.width, 0), S.resolveFont(linkBtn.font))) {
|
||||
#ifdef _WIN32
|
||||
system("start https://explorer.dragonx.is");
|
||||
#elif __APPLE__
|
||||
@@ -157,7 +155,7 @@ void RenderAboutDialog(App* app, bool* p_open)
|
||||
// Close button
|
||||
float button_width = closeBtn.width;
|
||||
ImGui::SetCursorPosX((ImGui::GetWindowWidth() - button_width) * 0.5f);
|
||||
if (material::StyledButton("Close", ImVec2(button_width, 0), S.resolveFont(closeBtn.font))) {
|
||||
if (material::StyledButton(TR("close"), ImVec2(button_width, 0), S.resolveFont(closeBtn.font))) {
|
||||
*p_open = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "address_book_dialog.h"
|
||||
#include "../../app.h"
|
||||
#include "../../data/address_book.h"
|
||||
#include "../../util/i18n.h"
|
||||
#include "../notifications.h"
|
||||
#include "../schema/ui_schema.h"
|
||||
#include "../material/draw_helpers.h"
|
||||
@@ -65,11 +66,11 @@ void AddressBookDialog::render(App* app)
|
||||
auto notesInput = S.input("dialogs.address-book", "notes-input");
|
||||
auto actionBtn = S.button("dialogs.address-book", "action-button");
|
||||
|
||||
if (material::BeginOverlayDialog("Address Book", &s_open, win.width, 0.94f)) {
|
||||
if (material::BeginOverlayDialog(TR("address_book_title"), &s_open, win.width, 0.94f)) {
|
||||
auto& book = getAddressBook();
|
||||
|
||||
// Toolbar
|
||||
if (material::StyledButton("Add New", ImVec2(0,0), S.resolveFont(actionBtn.font))) {
|
||||
if (material::StyledButton(TR("address_book_add_new"), ImVec2(0,0), S.resolveFont(actionBtn.font))) {
|
||||
s_show_add_dialog = true;
|
||||
s_edit_label[0] = '\0';
|
||||
s_edit_address[0] = '\0';
|
||||
@@ -82,7 +83,7 @@ void AddressBookDialog::render(App* app)
|
||||
|
||||
if (!has_selection) ImGui::BeginDisabled();
|
||||
|
||||
if (material::StyledButton("Edit", ImVec2(0,0), S.resolveFont(actionBtn.font))) {
|
||||
if (material::StyledButton(TR("edit"), ImVec2(0,0), S.resolveFont(actionBtn.font))) {
|
||||
if (has_selection) {
|
||||
const auto& entry = book.entries()[s_selected_index];
|
||||
strncpy(s_edit_label, entry.label.c_str(), sizeof(s_edit_label) - 1);
|
||||
@@ -94,20 +95,20 @@ void AddressBookDialog::render(App* app)
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (material::StyledButton("Delete", ImVec2(0,0), S.resolveFont(actionBtn.font))) {
|
||||
if (material::StyledButton(TR("delete"), ImVec2(0,0), S.resolveFont(actionBtn.font))) {
|
||||
if (has_selection) {
|
||||
book.removeEntry(s_selected_index);
|
||||
s_selected_index = -1;
|
||||
Notifications::instance().success("Entry deleted");
|
||||
Notifications::instance().success(TR("address_book_deleted"));
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (material::StyledButton("Copy Address", ImVec2(0,0), S.resolveFont(actionBtn.font))) {
|
||||
if (material::StyledButton(TR("copy_address"), ImVec2(0,0), S.resolveFont(actionBtn.font))) {
|
||||
if (has_selection) {
|
||||
ImGui::SetClipboardText(book.entries()[s_selected_index].address.c_str());
|
||||
Notifications::instance().info("Address copied to clipboard");
|
||||
Notifications::instance().info(TR("address_copied"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,16 +126,16 @@ void AddressBookDialog::render(App* app)
|
||||
{
|
||||
float labelColW = (addrTable.columns.count("label") && addrTable.columns.at("label").width > 0) ? addrTable.columns.at("label").width : 150;
|
||||
float notesColW = (addrTable.columns.count("notes") && addrTable.columns.at("notes").width > 0) ? addrTable.columns.at("notes").width : 150;
|
||||
ImGui::TableSetupColumn("Label", ImGuiTableColumnFlags_WidthFixed, labelColW);
|
||||
ImGui::TableSetupColumn("Address", ImGuiTableColumnFlags_WidthStretch);
|
||||
ImGui::TableSetupColumn("Notes", ImGuiTableColumnFlags_WidthFixed, notesColW);
|
||||
ImGui::TableSetupColumn(TR("label"), ImGuiTableColumnFlags_WidthFixed, labelColW);
|
||||
ImGui::TableSetupColumn(TR("address_label"), ImGuiTableColumnFlags_WidthStretch);
|
||||
ImGui::TableSetupColumn(TR("notes"), ImGuiTableColumnFlags_WidthFixed, notesColW);
|
||||
ImGui::TableSetupScrollFreeze(0, 1);
|
||||
ImGui::TableHeadersRow();
|
||||
|
||||
if (book.empty()) {
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::TextDisabled("No saved addresses. Click 'Add New' to add one.");
|
||||
ImGui::TextDisabled("%s", TR("address_book_empty"));
|
||||
} else {
|
||||
for (size_t i = 0; i < book.size(); i++) {
|
||||
const auto& entry = book.entries()[i];
|
||||
@@ -182,7 +183,7 @@ void AddressBookDialog::render(App* app)
|
||||
}
|
||||
|
||||
// Status line
|
||||
ImGui::TextDisabled("%zu addresses saved", book.size());
|
||||
ImGui::TextDisabled(TR("address_book_count"), book.size());
|
||||
material::EndOverlayDialog();
|
||||
}
|
||||
|
||||
@@ -196,20 +197,20 @@ void AddressBookDialog::render(App* app)
|
||||
ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
|
||||
|
||||
if (ImGui::BeginPopupModal("Add Address", &s_show_add_dialog, ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
material::Type().text(material::TypeStyle::H6, "Add Address");
|
||||
material::Type().text(material::TypeStyle::H6, TR("address_book_add"));
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
|
||||
ImGui::Text("Label:");
|
||||
ImGui::Text("%s", TR("label"));
|
||||
ImGui::SetNextItemWidth(addrInput.width);
|
||||
ImGui::InputText("##AddLabel", s_edit_label, sizeof(s_edit_label));
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Text("Address:");
|
||||
ImGui::Text("%s", TR("address_label"));
|
||||
ImGui::SetNextItemWidth(addrInput.width);
|
||||
ImGui::InputText("##AddAddress", s_edit_address, sizeof(s_edit_address));
|
||||
ImGui::SameLine();
|
||||
if (material::StyledButton("Paste##Add", ImVec2(0,0), S.resolveFont(actionBtn.font))) {
|
||||
if (material::StyledButton(TR("paste"), ImVec2(0,0), S.resolveFont(actionBtn.font))) {
|
||||
const char* clipboard = ImGui::GetClipboardText();
|
||||
if (clipboard) {
|
||||
strncpy(s_edit_address, clipboard, sizeof(s_edit_address) - 1);
|
||||
@@ -218,7 +219,7 @@ void AddressBookDialog::render(App* app)
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Text("Notes (optional):");
|
||||
ImGui::Text("%s", TR("notes_optional"));
|
||||
ImGui::SetNextItemWidth(addrInput.width);
|
||||
ImGui::InputTextMultiline("##AddNotes", s_edit_notes, sizeof(s_edit_notes), ImVec2(addrInput.width, notesInput.height > 0 ? notesInput.height : 60));
|
||||
|
||||
@@ -229,20 +230,20 @@ void AddressBookDialog::render(App* app)
|
||||
bool can_add = strlen(s_edit_label) > 0 && strlen(s_edit_address) > 0;
|
||||
if (!can_add) ImGui::BeginDisabled();
|
||||
|
||||
if (material::StyledButton("Add", ImVec2(actionBtn.width, 0), S.resolveFont(actionBtn.font))) {
|
||||
if (material::StyledButton(TR("add"), ImVec2(actionBtn.width, 0), S.resolveFont(actionBtn.font))) {
|
||||
data::AddressBookEntry entry(s_edit_label, s_edit_address, s_edit_notes);
|
||||
if (getAddressBook().addEntry(entry)) {
|
||||
Notifications::instance().success("Address added to book");
|
||||
Notifications::instance().success(TR("address_book_added"));
|
||||
s_show_add_dialog = false;
|
||||
} else {
|
||||
Notifications::instance().error("Address already exists in book");
|
||||
Notifications::instance().error(TR("address_book_exists"));
|
||||
}
|
||||
}
|
||||
|
||||
if (!can_add) ImGui::EndDisabled();
|
||||
|
||||
ImGui::SameLine();
|
||||
if (material::StyledButton("Cancel", ImVec2(actionBtn.width, 0), S.resolveFont(actionBtn.font))) {
|
||||
if (material::StyledButton(TR("cancel"), ImVec2(actionBtn.width, 0), S.resolveFont(actionBtn.font))) {
|
||||
s_show_add_dialog = false;
|
||||
}
|
||||
|
||||
@@ -257,22 +258,22 @@ void AddressBookDialog::render(App* app)
|
||||
ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
|
||||
|
||||
if (ImGui::BeginPopupModal("Edit Address", &s_show_edit_dialog, ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
material::Type().text(material::TypeStyle::H6, "Edit Address");
|
||||
material::Type().text(material::TypeStyle::H6, TR("address_book_edit"));
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
|
||||
ImGui::Text("Label:");
|
||||
ImGui::Text("%s", TR("label"));
|
||||
ImGui::SetNextItemWidth(addrInput.width);
|
||||
ImGui::InputText("##EditLabel", s_edit_label, sizeof(s_edit_label));
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Text("Address:");
|
||||
ImGui::Text("%s", TR("address_label"));
|
||||
ImGui::SetNextItemWidth(addrInput.width);
|
||||
ImGui::InputText("##EditAddress", s_edit_address, sizeof(s_edit_address));
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Text("Notes (optional):");
|
||||
ImGui::Text("%s", TR("notes_optional"));
|
||||
ImGui::SetNextItemWidth(addrInput.width);
|
||||
ImGui::InputTextMultiline("##EditNotes", s_edit_notes, sizeof(s_edit_notes), ImVec2(addrInput.width, notesInput.height > 0 ? notesInput.height : 60));
|
||||
|
||||
@@ -283,20 +284,20 @@ void AddressBookDialog::render(App* app)
|
||||
bool can_save = strlen(s_edit_label) > 0 && strlen(s_edit_address) > 0;
|
||||
if (!can_save) ImGui::BeginDisabled();
|
||||
|
||||
if (material::StyledButton("Save", ImVec2(actionBtn.width, 0), S.resolveFont(actionBtn.font))) {
|
||||
if (material::StyledButton(TR("save"), ImVec2(actionBtn.width, 0), S.resolveFont(actionBtn.font))) {
|
||||
data::AddressBookEntry entry(s_edit_label, s_edit_address, s_edit_notes);
|
||||
if (getAddressBook().updateEntry(s_selected_index, entry)) {
|
||||
Notifications::instance().success("Address updated");
|
||||
Notifications::instance().success(TR("address_book_updated"));
|
||||
s_show_edit_dialog = false;
|
||||
} else {
|
||||
Notifications::instance().error("Failed to update - address may be duplicate");
|
||||
Notifications::instance().error(TR("address_book_update_failed"));
|
||||
}
|
||||
}
|
||||
|
||||
if (!can_save) ImGui::EndDisabled();
|
||||
|
||||
ImGui::SameLine();
|
||||
if (material::StyledButton("Cancel", ImVec2(actionBtn.width, 0), S.resolveFont(actionBtn.font))) {
|
||||
if (material::StyledButton(TR("cancel"), ImVec2(actionBtn.width, 0), S.resolveFont(actionBtn.font))) {
|
||||
s_show_edit_dialog = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -61,11 +61,8 @@ void BackupWalletDialog::render(App* app)
|
||||
auto backupBtn = S.button("dialogs.backup-wallet", "backup-button");
|
||||
auto closeBtn = S.button("dialogs.backup-wallet", "close-button");
|
||||
|
||||
if (material::BeginOverlayDialog("Backup Wallet", &s_open, win.width, 0.94f)) {
|
||||
ImGui::TextWrapped(
|
||||
"Create a backup of your wallet.dat file. This file contains all your "
|
||||
"private keys and transaction history. Store the backup in a secure location."
|
||||
);
|
||||
if (material::BeginOverlayDialog(TR("backup_title"), &s_open, win.width, 0.94f)) {
|
||||
ImGui::TextWrapped("%s", TR("backup_description"));
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
@@ -76,7 +73,7 @@ void BackupWalletDialog::render(App* app)
|
||||
}
|
||||
|
||||
// Destination path
|
||||
ImGui::Text("Backup destination:");
|
||||
ImGui::Text("%s", TR("backup_destination"));
|
||||
ImGui::SetNextItemWidth(-1);
|
||||
ImGui::InputText("##Destination", s_destination, sizeof(s_destination));
|
||||
|
||||
@@ -84,13 +81,13 @@ void BackupWalletDialog::render(App* app)
|
||||
|
||||
// Show wallet.dat location
|
||||
std::string walletPath = util::Platform::getDataDir() + "/wallet.dat";
|
||||
ImGui::TextDisabled("Source: %s", walletPath.c_str());
|
||||
ImGui::TextDisabled(TR("backup_source"), walletPath.c_str());
|
||||
|
||||
// Check if source exists
|
||||
bool sourceExists = fs::exists(walletPath);
|
||||
if (!sourceExists) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.4f, 0.4f, 1.0f));
|
||||
ImGui::Text("Warning: wallet.dat not found at expected location");
|
||||
ImGui::Text("%s", TR("backup_wallet_not_found"));
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
|
||||
@@ -107,7 +104,7 @@ void BackupWalletDialog::render(App* app)
|
||||
ImGui::BeginDisabled();
|
||||
}
|
||||
|
||||
if (material::StyledButton("Create Backup", ImVec2(backupBtn.width, 0), S.resolveFont(backupBtn.font))) {
|
||||
if (material::StyledButton(TR("backup_create"), ImVec2(backupBtn.width, 0), S.resolveFont(backupBtn.font))) {
|
||||
if (strlen(s_destination) == 0) {
|
||||
Notifications::instance().warning("Please enter a destination path");
|
||||
} else if (!app->rpc() || !app->rpc()->isConnected()) {
|
||||
@@ -147,7 +144,7 @@ void BackupWalletDialog::render(App* app)
|
||||
s_status = statusMsg;
|
||||
s_backing_up = false;
|
||||
if (success) {
|
||||
Notifications::instance().success("Wallet backup created");
|
||||
Notifications::instance().success(TR("backup_created"));
|
||||
} else {
|
||||
Notifications::instance().warning(statusMsg);
|
||||
}
|
||||
@@ -160,11 +157,11 @@ void BackupWalletDialog::render(App* app)
|
||||
if (s_backing_up) {
|
||||
ImGui::EndDisabled();
|
||||
ImGui::SameLine();
|
||||
ImGui::TextDisabled("Backing up...");
|
||||
ImGui::TextDisabled("%s", TR("backup_backing_up"));
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
if (material::StyledButton("Close", ImVec2(closeBtn.width, 0), S.resolveFont(closeBtn.font))) {
|
||||
if (material::StyledButton(TR("close"), ImVec2(closeBtn.width, 0), S.resolveFont(closeBtn.font))) {
|
||||
s_open = false;
|
||||
}
|
||||
|
||||
@@ -179,10 +176,10 @@ void BackupWalletDialog::render(App* app)
|
||||
ImGui::Spacing();
|
||||
|
||||
// Tips
|
||||
ImGui::TextDisabled("Tips:");
|
||||
ImGui::BulletText("Store backups on external drives or cloud storage");
|
||||
ImGui::BulletText("Create multiple backups in different locations");
|
||||
ImGui::BulletText("Test restoring from backup periodically");
|
||||
ImGui::TextDisabled("%s", TR("backup_tips"));
|
||||
ImGui::BulletText("%s", TR("backup_tip_external"));
|
||||
ImGui::BulletText("%s", TR("backup_tip_multiple"));
|
||||
ImGui::BulletText("%s", TR("backup_tip_test"));
|
||||
material::EndOverlayDialog();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,14 @@
|
||||
namespace dragonx {
|
||||
namespace ui {
|
||||
|
||||
// Helper: build "TranslatedLabel##id" for ImGui widgets that use label as ID
|
||||
static std::string TrId(const char* tr_key, const char* id) {
|
||||
std::string s = TR(tr_key);
|
||||
s += "##";
|
||||
s += id;
|
||||
return s;
|
||||
}
|
||||
|
||||
// Case-insensitive substring search
|
||||
static bool containsIgnoreCase(const std::string& str, const std::string& search) {
|
||||
if (search.empty()) return true;
|
||||
@@ -774,13 +782,13 @@ static void RenderBalanceClassic(App* app)
|
||||
ImGui::InputTextWithHint("##AddrSearch", "Filter...", addr_search, sizeof(addr_search));
|
||||
|
||||
ImGui::SameLine(0, Layout::spacingLg());
|
||||
ImGui::Checkbox("Hide 0 Balances", &s_hideZeroBalances);
|
||||
ImGui::Checkbox(TrId("hide_zero_balances", "hide0").c_str(), &s_hideZeroBalances);
|
||||
{
|
||||
int hc = app->getHiddenAddressCount();
|
||||
if (hc > 0) {
|
||||
ImGui::SameLine(0, Layout::spacingLg());
|
||||
char hlbl[64];
|
||||
snprintf(hlbl, sizeof(hlbl), "Show Hidden (%d)", hc);
|
||||
snprintf(hlbl, sizeof(hlbl), TR("show_hidden"), hc);
|
||||
ImGui::Checkbox(hlbl, &s_showHidden);
|
||||
} else {
|
||||
s_showHidden = false;
|
||||
@@ -883,7 +891,8 @@ static void RenderBalanceClassic(App* app)
|
||||
|
||||
ImU32 greenCol = S.resolveColor("var(--accent-shielded)", Success());
|
||||
ImU32 goldCol = S.resolveColor("var(--accent-transparent)", Warning());
|
||||
float rowPadLeft = Layout::spacingLg();
|
||||
float rowPadLeft = Layout::cardInnerPadding();
|
||||
float rowPadRight = Layout::cardInnerPadding();
|
||||
float rowIconSz = std::max(S.drawElement("tabs.balance", "address-icon-min-size").size, S.drawElement("tabs.balance", "address-icon-size").size * hs);
|
||||
float innerW = ImGui::GetContentRegionAvail().x;
|
||||
|
||||
@@ -931,10 +940,10 @@ static void RenderBalanceClassic(App* app)
|
||||
// --- Button zone (right edge): [eye] [star] ---
|
||||
float btnH = rowH - Layout::spacingSm() * 2.0f;
|
||||
float btnW = btnH;
|
||||
float btnGap = Layout::spacingXs();
|
||||
float btnGap = Layout::spacingSm();
|
||||
float btnY = rowPos.y + (rowH - btnH) * 0.5f;
|
||||
float rightEdge = rowPos.x + innerW;
|
||||
float starX = rightEdge - btnW - Layout::spacingSm();
|
||||
float starX = rightEdge - btnW - rowPadRight;
|
||||
float eyeX = starX - btnGap - btnW;
|
||||
float btnRound = 6.0f * dp;
|
||||
bool btnClicked = false;
|
||||
@@ -958,7 +967,7 @@ static void RenderBalanceClassic(App* app)
|
||||
else app->favoriteAddress(addr.address);
|
||||
btnClicked = true;
|
||||
}
|
||||
if (bHov) ImGui::SetTooltip("%s", row.favorite ? "Remove favorite" : "Favorite address");
|
||||
if (bHov) ImGui::SetTooltip("%s", row.favorite ? TR("remove_favorite") : TR("favorite_address"));
|
||||
}
|
||||
|
||||
// Eye button (zero balance or hidden)
|
||||
@@ -980,7 +989,7 @@ static void RenderBalanceClassic(App* app)
|
||||
else app->hideAddress(addr.address);
|
||||
btnClicked = true;
|
||||
}
|
||||
if (bHov) ImGui::SetTooltip("%s", row.hidden ? "Restore address" : "Hide address");
|
||||
if (bHov) ImGui::SetTooltip("%s", row.hidden ? TR("restore_address") : TR("hide_address"));
|
||||
}
|
||||
|
||||
// Content zone ends before buttons
|
||||
@@ -1103,17 +1112,17 @@ static void RenderBalanceClassic(App* app)
|
||||
}
|
||||
ImGui::Separator();
|
||||
if (row.hidden) {
|
||||
if (ImGui::MenuItem("Restore Address"))
|
||||
if (ImGui::MenuItem(TR("restore_address")))
|
||||
app->unhideAddress(addr.address);
|
||||
} else if (addr.balance < 1e-9) {
|
||||
if (ImGui::MenuItem("Hide Address"))
|
||||
if (ImGui::MenuItem(TR("hide_address")))
|
||||
app->hideAddress(addr.address);
|
||||
}
|
||||
if (row.favorite) {
|
||||
if (ImGui::MenuItem("Remove Favorite"))
|
||||
if (ImGui::MenuItem(TR("remove_favorite")))
|
||||
app->unfavoriteAddress(addr.address);
|
||||
} else {
|
||||
if (ImGui::MenuItem("Favorite Address"))
|
||||
if (ImGui::MenuItem(TR("favorite_address")))
|
||||
app->favoriteAddress(addr.address);
|
||||
}
|
||||
effects::ImGuiAcrylic::EndAcrylicPopup();
|
||||
@@ -1387,13 +1396,13 @@ static void RenderSharedAddressList(App* app, float listH, float availW,
|
||||
ImGui::SetNextItemWidth(searchW);
|
||||
ImGui::InputTextWithHint("##AddrSearch", "Filter...", addr_search, sizeof(addr_search));
|
||||
ImGui::SameLine(0, Layout::spacingLg());
|
||||
ImGui::Checkbox("Hide 0 Balances", &s_hideZeroBalances);
|
||||
ImGui::Checkbox(TrId("hide_zero_balances", "hide0_v2").c_str(), &s_hideZeroBalances);
|
||||
{
|
||||
int hc = app->getHiddenAddressCount();
|
||||
if (hc > 0) {
|
||||
ImGui::SameLine(0, Layout::spacingLg());
|
||||
char hlbl[64];
|
||||
snprintf(hlbl, sizeof(hlbl), "Show Hidden (%d)", hc);
|
||||
snprintf(hlbl, sizeof(hlbl), TR("show_hidden"), hc);
|
||||
ImGui::Checkbox(hlbl, &s_showHidden);
|
||||
} else {
|
||||
s_showHidden = false;
|
||||
@@ -1461,10 +1470,10 @@ static void RenderSharedAddressList(App* app, float listH, float availW,
|
||||
float ch = ImGui::GetContentRegionAvail().y;
|
||||
if (ch < 60) ch = 60;
|
||||
if (addr_search[0]) {
|
||||
ImVec2 textSz = ImGui::CalcTextSize("No matching addresses");
|
||||
ImVec2 textSz = ImGui::CalcTextSize(TR("no_addresses_match"));
|
||||
ImGui::SetCursorPosX((cw - textSz.x) * 0.5f);
|
||||
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ch * 0.25f);
|
||||
ImGui::TextDisabled("No matching addresses");
|
||||
ImGui::TextDisabled("%s", TR("no_addresses_match"));
|
||||
} else {
|
||||
const char* msg = "No addresses yet";
|
||||
ImVec2 msgSz = ImGui::CalcTextSize(msg);
|
||||
@@ -1482,7 +1491,8 @@ static void RenderSharedAddressList(App* app, float listH, float availW,
|
||||
addrScrollMaxY = ImGui::GetScrollMaxY();
|
||||
ImU32 greenCol = S.resolveColor("var(--accent-shielded)", Success());
|
||||
ImU32 goldCol = S.resolveColor("var(--accent-transparent)", Warning());
|
||||
float rowPadLeft = Layout::spacingLg();
|
||||
float rowPadLeft = Layout::cardInnerPadding();
|
||||
float rowPadRight = Layout::cardInnerPadding();
|
||||
float rowIconSz = std::max(S.drawElement("tabs.balance", "address-icon-min-size").size,
|
||||
S.drawElement("tabs.balance", "address-icon-size").size * hs);
|
||||
float innerW = ImGui::GetContentRegionAvail().x;
|
||||
@@ -1522,10 +1532,10 @@ static void RenderSharedAddressList(App* app, float listH, float availW,
|
||||
// --- Button zone (right edge): [eye] [star] ---
|
||||
float btnH = rowH - Layout::spacingSm() * 2.0f;
|
||||
float btnW = btnH;
|
||||
float btnGap = Layout::spacingXs();
|
||||
float btnGap = Layout::spacingSm();
|
||||
float btnY = rowPos.y + (rowH - btnH) * 0.5f;
|
||||
float rightEdge = rowPos.x + innerW;
|
||||
float starX = rightEdge - btnW - Layout::spacingSm();
|
||||
float starX = rightEdge - btnW - rowPadRight;
|
||||
float eyeX = starX - btnGap - btnW;
|
||||
float btnRound = 6.0f * dp;
|
||||
bool btnClicked = false;
|
||||
@@ -1549,7 +1559,7 @@ static void RenderSharedAddressList(App* app, float listH, float availW,
|
||||
else app->favoriteAddress(addr.address);
|
||||
btnClicked = true;
|
||||
}
|
||||
if (bHov) ImGui::SetTooltip("%s", row.favorite ? "Remove favorite" : "Favorite address");
|
||||
if (bHov) ImGui::SetTooltip("%s", row.favorite ? TR("remove_favorite") : TR("favorite_address"));
|
||||
}
|
||||
|
||||
// Eye button (zero balance or hidden)
|
||||
@@ -1571,7 +1581,7 @@ static void RenderSharedAddressList(App* app, float listH, float availW,
|
||||
else app->hideAddress(addr.address);
|
||||
btnClicked = true;
|
||||
}
|
||||
if (bHov) ImGui::SetTooltip("%s", row.hidden ? "Restore address" : "Hide address");
|
||||
if (bHov) ImGui::SetTooltip("%s", row.hidden ? TR("restore_address") : TR("hide_address"));
|
||||
}
|
||||
|
||||
// Content zone ends before buttons
|
||||
@@ -1672,17 +1682,17 @@ static void RenderSharedAddressList(App* app, float listH, float availW,
|
||||
QRPopupDialog::show(addr.address, row.isZ ? "Z-Address" : "T-Address");
|
||||
ImGui::Separator();
|
||||
if (row.hidden) {
|
||||
if (ImGui::MenuItem("Restore Address"))
|
||||
if (ImGui::MenuItem(TR("restore_address")))
|
||||
app->unhideAddress(addr.address);
|
||||
} else if (addr.balance < 1e-9) {
|
||||
if (ImGui::MenuItem("Hide Address"))
|
||||
if (ImGui::MenuItem(TR("hide_address")))
|
||||
app->hideAddress(addr.address);
|
||||
}
|
||||
if (row.favorite) {
|
||||
if (ImGui::MenuItem("Remove Favorite"))
|
||||
if (ImGui::MenuItem(TR("remove_favorite")))
|
||||
app->unfavoriteAddress(addr.address);
|
||||
} else {
|
||||
if (ImGui::MenuItem("Favorite Address"))
|
||||
if (ImGui::MenuItem(TR("favorite_address")))
|
||||
app->favoriteAddress(addr.address);
|
||||
}
|
||||
effects::ImGuiAcrylic::EndAcrylicPopup();
|
||||
|
||||
@@ -99,12 +99,12 @@ void BlockInfoDialog::render(App* app)
|
||||
auto hashBackLbl = S.label("dialogs.block-info", "hash-back-label");
|
||||
auto closeBtn = S.button("dialogs.block-info", "close-button");
|
||||
|
||||
if (material::BeginOverlayDialog("Block Information", &s_open, win.width, 0.94f)) {
|
||||
if (material::BeginOverlayDialog(TR("block_info_title"), &s_open, win.width, 0.94f)) {
|
||||
auto* rpc = app->rpc();
|
||||
const auto& state = app->getWalletState();
|
||||
|
||||
// Height input
|
||||
ImGui::Text("Block Height:");
|
||||
ImGui::Text("%s", TR("block_height"));
|
||||
ImGui::SetNextItemWidth(heightInput.width);
|
||||
ImGui::InputInt("##Height", &s_height);
|
||||
if (s_height < 1) s_height = 1;
|
||||
@@ -123,7 +123,7 @@ void BlockInfoDialog::render(App* app)
|
||||
ImGui::BeginDisabled();
|
||||
}
|
||||
|
||||
if (material::StyledButton("Get Block Info", ImVec2(0,0), S.resolveFont(closeBtn.font))) {
|
||||
if (material::StyledButton(TR("block_get_info"), ImVec2(0,0), S.resolveFont(closeBtn.font))) {
|
||||
if (rpc && rpc->isConnected()) {
|
||||
s_loading = true;
|
||||
s_error.clear();
|
||||
@@ -138,7 +138,7 @@ void BlockInfoDialog::render(App* app)
|
||||
if (s_loading) {
|
||||
ImGui::EndDisabled();
|
||||
ImGui::SameLine();
|
||||
ImGui::TextDisabled("Loading...");
|
||||
ImGui::TextDisabled("%s", TR("loading"));
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
@@ -155,23 +155,23 @@ void BlockInfoDialog::render(App* app)
|
||||
// Block info display
|
||||
if (s_has_data) {
|
||||
// Block hash
|
||||
ImGui::Text("Block Hash:");
|
||||
ImGui::Text("%s", TR("block_hash"));
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.6f, 0.8f, 1.0f, 1.0f));
|
||||
ImGui::TextWrapped("%s", s_block_hash.c_str());
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Click to copy");
|
||||
ImGui::SetTooltip("%s", TR("click_to_copy"));
|
||||
}
|
||||
if (ImGui::IsItemClicked()) {
|
||||
ImGui::SetClipboardText(s_block_hash.c_str());
|
||||
Notifications::instance().success("Block hash copied");
|
||||
Notifications::instance().success(TR("block_hash_copied"));
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
// Timestamp
|
||||
ImGui::Text("Timestamp:");
|
||||
ImGui::Text("%s", TR("block_timestamp"));
|
||||
ImGui::SameLine(lbl.position);
|
||||
if (s_block_time > 0) {
|
||||
std::time_t t = static_cast<std::time_t>(s_block_time);
|
||||
@@ -179,21 +179,21 @@ void BlockInfoDialog::render(App* app)
|
||||
std::strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", std::localtime(&t));
|
||||
ImGui::Text("%s", time_buf);
|
||||
} else {
|
||||
ImGui::TextDisabled("Unknown");
|
||||
ImGui::TextDisabled("%s", TR("unknown"));
|
||||
}
|
||||
|
||||
// Confirmations
|
||||
ImGui::Text("Confirmations:");
|
||||
ImGui::Text("%s", TR("confirmations"));
|
||||
ImGui::SameLine(lbl.position);
|
||||
ImGui::Text("%d", s_confirmations);
|
||||
|
||||
// Transaction count
|
||||
ImGui::Text("Transactions:");
|
||||
ImGui::Text("%s", TR("block_transactions"));
|
||||
ImGui::SameLine(lbl.position);
|
||||
ImGui::Text("%d", s_tx_count);
|
||||
|
||||
// Size
|
||||
ImGui::Text("Size:");
|
||||
ImGui::Text("%s", TR("block_size"));
|
||||
ImGui::SameLine(lbl.position);
|
||||
if (s_block_size > 1024 * 1024) {
|
||||
ImGui::Text("%.2f MB", s_block_size / (1024.0 * 1024.0));
|
||||
@@ -204,12 +204,12 @@ void BlockInfoDialog::render(App* app)
|
||||
}
|
||||
|
||||
// Difficulty
|
||||
ImGui::Text("Difficulty:");
|
||||
ImGui::Text("%s", TR("difficulty"));
|
||||
ImGui::SameLine(lbl.position);
|
||||
ImGui::Text("%.4f", s_difficulty);
|
||||
|
||||
// Bits
|
||||
ImGui::Text("Bits:");
|
||||
ImGui::Text("%s", TR("block_bits"));
|
||||
ImGui::SameLine(lbl.position);
|
||||
ImGui::Text("%s", s_bits.c_str());
|
||||
|
||||
@@ -218,7 +218,7 @@ void BlockInfoDialog::render(App* app)
|
||||
ImGui::Spacing();
|
||||
|
||||
// Merkle root
|
||||
ImGui::Text("Merkle Root:");
|
||||
ImGui::Text("%s", TR("block_merkle_root"));
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.7f, 0.7f, 0.7f, 1.0f));
|
||||
ImGui::TextWrapped("%s", s_merkle_root.c_str());
|
||||
ImGui::PopStyleColor();
|
||||
@@ -227,7 +227,7 @@ void BlockInfoDialog::render(App* app)
|
||||
|
||||
// Previous block
|
||||
if (!s_prev_hash.empty()) {
|
||||
ImGui::Text("Previous Block:");
|
||||
ImGui::Text("%s", TR("block_previous"));
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.6f, 0.8f, 1.0f, 1.0f));
|
||||
|
||||
// Truncate for display
|
||||
@@ -239,7 +239,7 @@ void BlockInfoDialog::render(App* app)
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Click to view previous block");
|
||||
ImGui::SetTooltip("%s", TR("block_click_prev"));
|
||||
}
|
||||
if (ImGui::IsItemClicked() && s_height > 1) {
|
||||
s_height--;
|
||||
@@ -249,7 +249,7 @@ void BlockInfoDialog::render(App* app)
|
||||
|
||||
// Next block
|
||||
if (!s_next_hash.empty()) {
|
||||
ImGui::Text("Next Block:");
|
||||
ImGui::Text("%s", TR("block_next"));
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.6f, 0.8f, 1.0f, 1.0f));
|
||||
|
||||
// Truncate for display
|
||||
@@ -261,7 +261,7 @@ void BlockInfoDialog::render(App* app)
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Click to view next block");
|
||||
ImGui::SetTooltip("%s", TR("block_click_next"));
|
||||
}
|
||||
if (ImGui::IsItemClicked()) {
|
||||
s_height++;
|
||||
@@ -276,7 +276,7 @@ void BlockInfoDialog::render(App* app)
|
||||
// Navigation buttons
|
||||
if (s_has_data) {
|
||||
if (s_height > 1) {
|
||||
if (material::StyledButton("<< Previous", ImVec2(0,0), S.resolveFont(closeBtn.font))) {
|
||||
if (material::StyledButton(TR("block_nav_prev"), ImVec2(0,0), S.resolveFont(closeBtn.font))) {
|
||||
s_height--;
|
||||
s_has_data = false;
|
||||
s_error.clear();
|
||||
@@ -285,7 +285,7 @@ void BlockInfoDialog::render(App* app)
|
||||
}
|
||||
|
||||
if (!s_next_hash.empty()) {
|
||||
if (material::StyledButton("Next >>", ImVec2(0,0), S.resolveFont(closeBtn.font))) {
|
||||
if (material::StyledButton(TR("block_nav_next"), ImVec2(0,0), S.resolveFont(closeBtn.font))) {
|
||||
s_height++;
|
||||
s_has_data = false;
|
||||
s_error.clear();
|
||||
@@ -295,7 +295,7 @@ void BlockInfoDialog::render(App* app)
|
||||
|
||||
// Close button at bottom
|
||||
ImGui::SetCursorPosY(ImGui::GetWindowHeight() - 40);
|
||||
if (material::StyledButton("Close", ImVec2(closeBtn.width, 0), S.resolveFont(closeBtn.font))) {
|
||||
if (material::StyledButton(TR("close"), ImVec2(closeBtn.width, 0), S.resolveFont(closeBtn.font))) {
|
||||
s_open = false;
|
||||
}
|
||||
material::EndOverlayDialog();
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "../material/color_theme.h"
|
||||
#include "../theme.h"
|
||||
#include "../../embedded/IconsMaterialDesign.h"
|
||||
#include "../../util/i18n.h"
|
||||
|
||||
#include <imgui.h>
|
||||
#include <cstring>
|
||||
@@ -75,8 +76,8 @@ ConsoleTab::ConsoleTab()
|
||||
refreshColors();
|
||||
|
||||
// Add welcome message
|
||||
addLine("DragonX Wallet Console", COLOR_INFO);
|
||||
addLine("Type 'help' for available commands", COLOR_INFO);
|
||||
addLine(TR("console_welcome"), COLOR_INFO);
|
||||
addLine(TR("console_type_help"), COLOR_INFO);
|
||||
addLine("", COLOR_RESULT);
|
||||
}
|
||||
|
||||
@@ -117,27 +118,38 @@ void ConsoleTab::render(daemon::EmbeddedDaemon* daemon, rpc::RPCClient* rpc, rpc
|
||||
if (current_state == daemon::EmbeddedDaemon::State::Starting &&
|
||||
last_daemon_state_ == daemon::EmbeddedDaemon::State::Stopped) {
|
||||
addLine("", COLOR_RESULT);
|
||||
addLine("=== Starting DragonX Full Node ===", COLOR_INFO);
|
||||
addLine("Capturing daemon output...", COLOR_INFO);
|
||||
addLine(TR("console_starting_node"), COLOR_INFO);
|
||||
addLine(TR("console_capturing_output"), COLOR_INFO);
|
||||
addLine("", COLOR_RESULT);
|
||||
shown_startup_message_ = true;
|
||||
}
|
||||
else if (current_state == daemon::EmbeddedDaemon::State::Running &&
|
||||
last_daemon_state_ != daemon::EmbeddedDaemon::State::Running) {
|
||||
addLine("=== Daemon is running ===", COLOR_INFO);
|
||||
addLine(TR("console_daemon_started"), COLOR_INFO);
|
||||
}
|
||||
else if (current_state == daemon::EmbeddedDaemon::State::Stopped &&
|
||||
last_daemon_state_ == daemon::EmbeddedDaemon::State::Running) {
|
||||
addLine("", COLOR_RESULT);
|
||||
addLine("=== Daemon stopped ===", COLOR_INFO);
|
||||
addLine(TR("console_daemon_stopped"), COLOR_INFO);
|
||||
}
|
||||
else if (current_state == daemon::EmbeddedDaemon::State::Error) {
|
||||
addLine("=== Daemon error: " + daemon->getLastError() + " ===", COLOR_ERROR);
|
||||
addLine(std::string(TR("console_daemon_error")) + daemon->getLastError() + " ===", COLOR_ERROR);
|
||||
}
|
||||
|
||||
last_daemon_state_ = current_state;
|
||||
}
|
||||
|
||||
|
||||
// Track RPC connection state and show a message when connected
|
||||
if (rpc) {
|
||||
bool connected_now = rpc->isConnected();
|
||||
if (connected_now && !last_rpc_connected_) {
|
||||
addLine(TR("console_connected"), COLOR_INFO);
|
||||
} else if (!connected_now && last_rpc_connected_) {
|
||||
addLine(TR("console_disconnected"), COLOR_ERROR);
|
||||
}
|
||||
last_rpc_connected_ = connected_now;
|
||||
}
|
||||
|
||||
// Check for new daemon output — always capture so toggle works as a live filter
|
||||
if (daemon) {
|
||||
std::string new_output = daemon->getOutputSince(last_daemon_output_size_);
|
||||
@@ -372,31 +384,31 @@ void ConsoleTab::renderToolbar(daemon::EmbeddedDaemon* daemon)
|
||||
// Daemon status with colored dot
|
||||
if (daemon) {
|
||||
auto state = daemon->getState();
|
||||
const char* status_text = "Unknown";
|
||||
const char* status_text = TR("console_status_unknown");
|
||||
ImU32 dotCol = IM_COL32(150, 150, 150, 255);
|
||||
bool pulse = false;
|
||||
|
||||
switch (state) {
|
||||
case daemon::EmbeddedDaemon::State::Stopped:
|
||||
status_text = "Stopped";
|
||||
status_text = TR("console_status_stopped");
|
||||
dotCol = IM_COL32(150, 150, 150, 255);
|
||||
break;
|
||||
case daemon::EmbeddedDaemon::State::Starting:
|
||||
status_text = "Starting";
|
||||
status_text = TR("console_status_starting");
|
||||
dotCol = Warning();
|
||||
pulse = true;
|
||||
break;
|
||||
case daemon::EmbeddedDaemon::State::Running:
|
||||
status_text = "Running";
|
||||
status_text = TR("console_status_running");
|
||||
dotCol = Success();
|
||||
break;
|
||||
case daemon::EmbeddedDaemon::State::Stopping:
|
||||
status_text = "Stopping";
|
||||
status_text = TR("console_status_stopping");
|
||||
dotCol = Warning();
|
||||
pulse = true;
|
||||
break;
|
||||
case daemon::EmbeddedDaemon::State::Error:
|
||||
status_text = "Error";
|
||||
status_text = TR("console_status_error");
|
||||
dotCol = Error();
|
||||
break;
|
||||
}
|
||||
@@ -418,7 +430,7 @@ void ConsoleTab::renderToolbar(daemon::EmbeddedDaemon* daemon)
|
||||
ImGui::SameLine();
|
||||
Type().textColored(TypeStyle::Caption, dotCol, status_text);
|
||||
} else {
|
||||
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), "No daemon");
|
||||
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), TR("console_no_daemon"));
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
@@ -426,7 +438,7 @@ void ConsoleTab::renderToolbar(daemon::EmbeddedDaemon* daemon)
|
||||
ImGui::SameLine();
|
||||
|
||||
// Auto-scroll toggle
|
||||
if (ImGui::Checkbox("Auto-scroll", &auto_scroll_)) {
|
||||
if (ImGui::Checkbox(TR("console_auto_scroll"), &auto_scroll_)) {
|
||||
if (auto_scroll_) {
|
||||
scroll_to_bottom_ = true;
|
||||
new_lines_since_scroll_ = 0;
|
||||
@@ -440,7 +452,7 @@ void ConsoleTab::renderToolbar(daemon::EmbeddedDaemon* daemon)
|
||||
// Daemon messages toggle
|
||||
{
|
||||
static bool s_prev_daemon_enabled = true;
|
||||
ImGui::Checkbox("Daemon", &s_daemon_messages_enabled);
|
||||
ImGui::Checkbox(TR("console_daemon"), &s_daemon_messages_enabled);
|
||||
// When toggling daemon filter while auto-scroll is active, scroll to bottom
|
||||
if (s_prev_daemon_enabled != s_daemon_messages_enabled && auto_scroll_) {
|
||||
scroll_to_bottom_ = true;
|
||||
@@ -448,7 +460,7 @@ void ConsoleTab::renderToolbar(daemon::EmbeddedDaemon* daemon)
|
||||
s_prev_daemon_enabled = s_daemon_messages_enabled;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Show daemon output messages");
|
||||
ImGui::SetTooltip("%s", TR("console_show_daemon_output"));
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
@@ -458,7 +470,7 @@ void ConsoleTab::renderToolbar(daemon::EmbeddedDaemon* daemon)
|
||||
// Errors-only toggle
|
||||
{
|
||||
static bool s_prev_errors_only = false;
|
||||
ImGui::Checkbox("Errors", &s_errors_only_enabled);
|
||||
ImGui::Checkbox(TR("console_errors"), &s_errors_only_enabled);
|
||||
// When toggling errors filter while auto-scroll is active, scroll to bottom
|
||||
if (s_prev_errors_only != s_errors_only_enabled && auto_scroll_) {
|
||||
scroll_to_bottom_ = true;
|
||||
@@ -466,7 +478,7 @@ void ConsoleTab::renderToolbar(daemon::EmbeddedDaemon* daemon)
|
||||
s_prev_errors_only = s_errors_only_enabled;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Show only error messages");
|
||||
ImGui::SetTooltip("%s", TR("console_show_errors_only"));
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
@@ -474,7 +486,7 @@ void ConsoleTab::renderToolbar(daemon::EmbeddedDaemon* daemon)
|
||||
ImGui::SameLine();
|
||||
|
||||
// Clear button
|
||||
if (TactileButton("Clear", ImVec2(0, 0), schema::UI().resolveFont("button"))) {
|
||||
if (TactileButton(TR("console_clear"), ImVec2(0, 0), schema::UI().resolveFont("button"))) {
|
||||
clear();
|
||||
clearSelection();
|
||||
}
|
||||
@@ -482,7 +494,7 @@ void ConsoleTab::renderToolbar(daemon::EmbeddedDaemon* daemon)
|
||||
ImGui::SameLine();
|
||||
|
||||
// Copy button — material styled
|
||||
if (TactileButton("Copy", ImVec2(0, 0), schema::UI().resolveFont("button"))) {
|
||||
if (TactileButton(TR("copy"), ImVec2(0, 0), schema::UI().resolveFont("button"))) {
|
||||
std::lock_guard<std::mutex> lock(lines_mutex_);
|
||||
if (has_selection_) {
|
||||
std::string selected = getSelectedText();
|
||||
@@ -501,17 +513,17 @@ void ConsoleTab::renderToolbar(daemon::EmbeddedDaemon* daemon)
|
||||
}
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip(has_selection_ ? "Copy selected text" : "Copy all output");
|
||||
ImGui::SetTooltip("%s", has_selection_ ? TR("console_copy_selected") : TR("console_copy_all"));
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
// Commands reference button
|
||||
if (TactileButton("Commands", ImVec2(0, 0), schema::UI().resolveFont("button"))) {
|
||||
if (TactileButton(TR("console_commands"), ImVec2(0, 0), schema::UI().resolveFont("button"))) {
|
||||
show_commands_popup_ = true;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Show RPC command reference");
|
||||
ImGui::SetTooltip("%s", TR("console_show_rpc_ref"));
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
@@ -519,7 +531,7 @@ void ConsoleTab::renderToolbar(daemon::EmbeddedDaemon* daemon)
|
||||
// Line count
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(lines_mutex_);
|
||||
ImGui::TextDisabled("(%zu lines)", lines_.size());
|
||||
ImGui::TextDisabled(TR("console_line_count"), lines_.size());
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
@@ -531,7 +543,7 @@ void ConsoleTab::renderToolbar(daemon::EmbeddedDaemon* daemon)
|
||||
float filterAvail = ImGui::GetContentRegionAvail().x - zoomBtnSpace;
|
||||
float filterW = std::min(schema::UI().drawElement("tabs.console", "filter-max-width").size, filterAvail * schema::UI().drawElement("tabs.console", "filter-width-ratio").size);
|
||||
ImGui::SetNextItemWidth(filterW);
|
||||
ImGui::InputTextWithHint("##ConsoleFilter", "Filter output...", filter_text_, sizeof(filter_text_));
|
||||
ImGui::InputTextWithHint("##ConsoleFilter", TR("console_filter_hint"), filter_text_, sizeof(filter_text_));
|
||||
|
||||
// Zoom +/- buttons (right side of toolbar)
|
||||
ImGui::SameLine();
|
||||
@@ -548,14 +560,14 @@ void ConsoleTab::renderToolbar(daemon::EmbeddedDaemon* daemon)
|
||||
s_console_zoom = std::max(zoomMin, s_console_zoom - zoomStep);
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Zoom out (%.0f%%)", s_console_zoom * 100.0f);
|
||||
ImGui::SetTooltip(TR("console_zoom_out"), s_console_zoom * 100.0f);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (TactileButton(ICON_MD_ADD, ImVec2(btnSz, btnSz), Type().iconMed())) {
|
||||
s_console_zoom = std::min(zoomMax, s_console_zoom + zoomStep);
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Zoom in (%.0f%%)", s_console_zoom * 100.0f);
|
||||
ImGui::SetTooltip(TR("console_zoom_in"), s_console_zoom * 100.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -889,7 +901,7 @@ void ConsoleTab::renderOutput()
|
||||
// Filter indicator (text filter only — daemon toggle is already visible in toolbar)
|
||||
if (has_text_filter) {
|
||||
char filterBuf[128];
|
||||
snprintf(filterBuf, sizeof(filterBuf), "Showing %d of %zu lines",
|
||||
snprintf(filterBuf, sizeof(filterBuf), TR("console_showing_lines"),
|
||||
visible_count, lines_.size());
|
||||
ImVec2 indicatorPos = ImGui::GetCursorScreenPos();
|
||||
ImGui::GetWindowDrawList()->AddText(indicatorPos,
|
||||
@@ -899,13 +911,13 @@ void ConsoleTab::renderOutput()
|
||||
|
||||
// Right-click context menu
|
||||
if (ImGui::BeginPopupContextWindow("ConsoleContextMenu")) {
|
||||
if (ImGui::MenuItem("Copy", "Ctrl+C", false, has_selection_)) {
|
||||
if (ImGui::MenuItem(TR("copy"), "Ctrl+C", false, has_selection_)) {
|
||||
std::string selected = getSelectedText();
|
||||
if (!selected.empty()) {
|
||||
ImGui::SetClipboardText(selected.c_str());
|
||||
}
|
||||
}
|
||||
if (ImGui::MenuItem("Select All", "Ctrl+A")) {
|
||||
if (ImGui::MenuItem(TR("console_select_all"), "Ctrl+A")) {
|
||||
if (!lines_.empty()) {
|
||||
sel_anchor_ = {0, 0};
|
||||
sel_end_ = {static_cast<int>(lines_.size()) - 1,
|
||||
@@ -914,7 +926,7 @@ void ConsoleTab::renderOutput()
|
||||
}
|
||||
}
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem("Clear Console")) {
|
||||
if (ImGui::MenuItem(TR("console_clear_console"))) {
|
||||
// Can't call clear() here (already holding lock), just mark for clearing
|
||||
lines_.clear();
|
||||
has_selection_ = false;
|
||||
@@ -938,7 +950,7 @@ void ConsoleTab::renderOutput()
|
||||
dlInd->AddRect(iMin, iMax, IM_COL32(255, 218, 0, 120), 12.0f);
|
||||
|
||||
char buf[48];
|
||||
snprintf(buf, sizeof(buf), " %d new line%s",
|
||||
snprintf(buf, sizeof(buf), TR("console_new_lines"),
|
||||
new_lines_since_scroll_, new_lines_since_scroll_ != 1 ? "s" : "");
|
||||
ImFont* capFont = Type().caption();
|
||||
if (!capFont) capFont = ImGui::GetFont();
|
||||
@@ -1229,7 +1241,7 @@ void ConsoleTab::renderInput(rpc::RPCClient* rpc, rpc::RPCWorker* worker)
|
||||
data->InsertChars(0, matches[0]);
|
||||
} else if (matches.size() > 1) {
|
||||
// Multiple matches — show list in console and complete common prefix
|
||||
console->addLine("Completions:", ConsoleTab::COLOR_INFO);
|
||||
console->addLine(TR("console_completions"), ConsoleTab::COLOR_INFO);
|
||||
std::string line = " ";
|
||||
for (size_t m = 0; m < matches.size(); m++) {
|
||||
if (m > 0) line += " ";
|
||||
@@ -1284,17 +1296,17 @@ void ConsoleTab::renderCommandsPopup()
|
||||
using namespace material;
|
||||
|
||||
float popW = std::min(schema::UI().drawElement("tabs.console", "popup-max-width").size, ImGui::GetMainViewport()->Size.x * schema::UI().drawElement("tabs.console", "popup-width-ratio").size);
|
||||
if (!material::BeginOverlayDialog("RPC Command Reference", &show_commands_popup_, popW, 0.94f)) {
|
||||
if (!material::BeginOverlayDialog(TR("console_rpc_reference"), &show_commands_popup_, popW, 0.94f)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Type().text(TypeStyle::H6, "RPC Command Reference");
|
||||
Type().text(TypeStyle::H6, TR("console_rpc_reference"));
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
|
||||
// Search filter
|
||||
static char cmdFilter[128] = {0};
|
||||
ImGui::SetNextItemWidth(-1);
|
||||
ImGui::InputTextWithHint("##CmdSearch", "Search commands...", cmdFilter, sizeof(cmdFilter));
|
||||
ImGui::InputTextWithHint("##CmdSearch", TR("console_search_commands"), cmdFilter, sizeof(cmdFilter));
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
|
||||
// Command entries
|
||||
@@ -1507,9 +1519,9 @@ void ConsoleTab::renderCommandsPopup()
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
||||
if (cmd.params[0] != '\0')
|
||||
ImGui::SetTooltip("Click to insert '%s %s' into input", cmd.name, cmd.params);
|
||||
ImGui::SetTooltip(TR("console_click_insert_params"), cmd.name, cmd.params);
|
||||
else
|
||||
ImGui::SetTooltip("Click to insert '%s' into input", cmd.name);
|
||||
ImGui::SetTooltip(TR("console_click_insert"), cmd.name);
|
||||
}
|
||||
ImGui::PopStyleColor(3);
|
||||
if (showParams) {
|
||||
@@ -1575,7 +1587,7 @@ void ConsoleTab::renderCommandsPopup()
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
|
||||
// Close button
|
||||
if (ImGui::Button("Close", ImVec2(-1, 0))) {
|
||||
if (ImGui::Button(TR("console_close"), ImVec2(-1, 0))) {
|
||||
cmdFilter[0] = '\0';
|
||||
show_commands_popup_ = false;
|
||||
}
|
||||
@@ -1605,28 +1617,28 @@ void ConsoleTab::executeCommand(const std::string& cmd, rpc::RPCClient* rpc, rpc
|
||||
}
|
||||
|
||||
if (cmd == "help") {
|
||||
addLine("Available commands:", COLOR_INFO);
|
||||
addLine(" clear - Clear console output", COLOR_RESULT);
|
||||
addLine(" help - Show this help", COLOR_RESULT);
|
||||
addLine(TR("console_available_commands"), COLOR_INFO);
|
||||
addLine(TR("console_help_clear"), COLOR_RESULT);
|
||||
addLine(TR("console_help_help"), COLOR_RESULT);
|
||||
addLine("", COLOR_RESULT);
|
||||
addLine("Common RPC commands (when connected):", COLOR_INFO);
|
||||
addLine(" getinfo - Get daemon info", COLOR_RESULT);
|
||||
addLine(" getbalance - Get transparent balance", COLOR_RESULT);
|
||||
addLine(" z_gettotalbalance - Get total balance", COLOR_RESULT);
|
||||
addLine(" getblockcount - Get current block height", COLOR_RESULT);
|
||||
addLine(" getpeerinfo - Get connected peers", COLOR_RESULT);
|
||||
addLine(" setgenerate true/false [threads] - Mining on/off", COLOR_RESULT);
|
||||
addLine(" getmininginfo - Get mining status", COLOR_RESULT);
|
||||
addLine(" stop - Stop the daemon", COLOR_RESULT);
|
||||
addLine(TR("console_common_rpc"), COLOR_INFO);
|
||||
addLine(TR("console_help_getinfo"), COLOR_RESULT);
|
||||
addLine(TR("console_help_getbalance"), COLOR_RESULT);
|
||||
addLine(TR("console_help_gettotalbalance"), COLOR_RESULT);
|
||||
addLine(TR("console_help_getblockcount"), COLOR_RESULT);
|
||||
addLine(TR("console_help_getpeerinfo"), COLOR_RESULT);
|
||||
addLine(TR("console_help_setgenerate"), COLOR_RESULT);
|
||||
addLine(TR("console_help_getmininginfo"), COLOR_RESULT);
|
||||
addLine(TR("console_help_stop"), COLOR_RESULT);
|
||||
addLine("", COLOR_RESULT);
|
||||
addLine("Click 'Commands' in the toolbar for full RPC reference", COLOR_INFO);
|
||||
addLine("Use Tab for command completion, Up/Down for history", COLOR_INFO);
|
||||
addLine(TR("console_click_commands"), COLOR_INFO);
|
||||
addLine(TR("console_tab_completion"), COLOR_INFO);
|
||||
return;
|
||||
}
|
||||
|
||||
// Execute RPC command
|
||||
if (!rpc || !rpc->isConnected()) {
|
||||
addLine("Error: Not connected to daemon", COLOR_ERROR);
|
||||
addLine(TR("console_not_connected"), COLOR_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1839,7 +1851,7 @@ void ConsoleTab::clear()
|
||||
last_xmrig_output_size_ = 0;
|
||||
}
|
||||
// addLine() takes the lock itself, so call it outside the locked scope
|
||||
addLine("Console cleared", COLOR_INFO);
|
||||
addLine(TR("console_cleared"), COLOR_INFO);
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
|
||||
@@ -119,6 +119,7 @@ private:
|
||||
size_t last_xmrig_output_size_ = 0;
|
||||
bool shown_startup_message_ = false;
|
||||
daemon::EmbeddedDaemon::State last_daemon_state_ = daemon::EmbeddedDaemon::State::Stopped;
|
||||
bool last_rpc_connected_ = false;
|
||||
|
||||
// Text selection state
|
||||
bool is_selecting_ = false;
|
||||
|
||||
@@ -69,16 +69,14 @@ void ExportAllKeysDialog::render(App* app)
|
||||
auto exportBtn = S.button("dialogs.export-all-keys", "export-button");
|
||||
auto closeBtn = S.button("dialogs.export-all-keys", "close-button");
|
||||
|
||||
if (material::BeginOverlayDialog("Export All Private Keys", &s_open, win.width, 0.94f)) {
|
||||
if (material::BeginOverlayDialog(TR("export_keys_title"), &s_open, win.width, 0.94f)) {
|
||||
// Warning
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.4f, 0.4f, 1.0f));
|
||||
ImGui::PushFont(material::Type().iconSmall());
|
||||
ImGui::Text(ICON_MD_WARNING);
|
||||
ImGui::PopFont();
|
||||
ImGui::SameLine(0, 4.0f);
|
||||
ImGui::TextWrapped("DANGER: This will export ALL private keys from your wallet! "
|
||||
"Anyone with access to this file can steal your funds. "
|
||||
"Store it securely and delete after use.");
|
||||
ImGui::TextWrapped("%s", TR("export_keys_danger"));
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
ImGui::Spacing();
|
||||
@@ -90,19 +88,19 @@ void ExportAllKeysDialog::render(App* app)
|
||||
}
|
||||
|
||||
// Options
|
||||
ImGui::Text("Export options:");
|
||||
ImGui::Checkbox("Include Z-addresses (shielded)", &s_include_z);
|
||||
ImGui::Checkbox("Include T-addresses (transparent)", &s_include_t);
|
||||
ImGui::Text("%s", TR("export_keys_options"));
|
||||
ImGui::Checkbox(TR("export_keys_include_z"), &s_include_z);
|
||||
ImGui::Checkbox(TR("export_keys_include_t"), &s_include_t);
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
// Filename
|
||||
ImGui::Text("Output filename:");
|
||||
ImGui::Text("%s", TR("output_filename"));
|
||||
ImGui::SetNextItemWidth(-1);
|
||||
ImGui::InputText("##Filename", s_filename, sizeof(s_filename));
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::TextDisabled("File will be saved in: ~/.config/ObsidianDragon/");
|
||||
ImGui::TextDisabled("%s", TR("file_save_location"));
|
||||
|
||||
if (s_exporting) {
|
||||
ImGui::EndDisabled();
|
||||
@@ -117,7 +115,7 @@ void ExportAllKeysDialog::render(App* app)
|
||||
ImGui::BeginDisabled();
|
||||
}
|
||||
|
||||
if (material::StyledButton("Export Keys", ImVec2(exportBtn.width, 0), S.resolveFont(exportBtn.font))) {
|
||||
if (material::StyledButton(TR("export_keys_btn"), ImVec2(exportBtn.width, 0), S.resolveFont(exportBtn.font))) {
|
||||
if (!s_include_z && !s_include_t) {
|
||||
Notifications::instance().warning("Select at least one address type");
|
||||
} else if (!app->rpc() || !app->rpc()->isConnected()) {
|
||||
@@ -215,7 +213,7 @@ void ExportAllKeysDialog::render(App* app)
|
||||
s_exporting = false;
|
||||
if (writeOk) {
|
||||
s_status = "Exported to: " + filepath;
|
||||
Notifications::instance().success("Keys exported successfully");
|
||||
Notifications::instance().success(TR("export_keys_success"));
|
||||
} else {
|
||||
s_status = "Failed to write file";
|
||||
Notifications::instance().error("Failed to save key file");
|
||||
@@ -229,11 +227,11 @@ void ExportAllKeysDialog::render(App* app)
|
||||
if (s_exporting) {
|
||||
ImGui::EndDisabled();
|
||||
ImGui::SameLine();
|
||||
ImGui::TextDisabled("Exporting %d/%d...", s_exported_count, s_total_addresses);
|
||||
ImGui::TextDisabled(TR("export_keys_progress"), s_exported_count, s_total_addresses);
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
if (material::StyledButton("Close", ImVec2(closeBtn.width, 0), S.resolveFont(closeBtn.font))) {
|
||||
if (material::StyledButton(TR("close"), ImVec2(closeBtn.width, 0), S.resolveFont(closeBtn.font))) {
|
||||
s_open = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -72,31 +72,31 @@ void ExportTransactionsDialog::render(App* app)
|
||||
auto exportBtn = S.button("dialogs.export-transactions", "export-button");
|
||||
auto closeBtn = S.button("dialogs.export-transactions", "close-button");
|
||||
|
||||
if (material::BeginOverlayDialog("Export Transactions to CSV", &s_open, win.width, 0.94f)) {
|
||||
if (material::BeginOverlayDialog(TR("export_tx_title"), &s_open, win.width, 0.94f)) {
|
||||
const auto& state = app->getWalletState();
|
||||
|
||||
ImGui::Text("Export %zu transactions to CSV file.", state.transactions.size());
|
||||
ImGui::Text(TR("export_tx_count"), state.transactions.size());
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
// Filename
|
||||
ImGui::Text("Output filename:");
|
||||
ImGui::Text("%s", TR("output_filename"));
|
||||
ImGui::SetNextItemWidth(-1);
|
||||
ImGui::InputText("##Filename", s_filename, sizeof(s_filename));
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::TextDisabled("File will be saved in: ~/.config/ObsidianDragon/");
|
||||
ImGui::TextDisabled("%s", TR("file_save_location"));
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
// Export button
|
||||
if (material::StyledButton("Export", ImVec2(exportBtn.width, 0), S.resolveFont(exportBtn.font))) {
|
||||
if (material::StyledButton(TR("export"), ImVec2(exportBtn.width, 0), S.resolveFont(exportBtn.font))) {
|
||||
if (state.transactions.empty()) {
|
||||
Notifications::instance().warning("No transactions to export");
|
||||
Notifications::instance().warning(TR("export_tx_none"));
|
||||
} else {
|
||||
std::string configDir = util::Platform::getConfigDir();
|
||||
std::string filepath = configDir + "/" + s_filename;
|
||||
@@ -104,7 +104,7 @@ void ExportTransactionsDialog::render(App* app)
|
||||
std::ofstream file(filepath);
|
||||
if (!file.is_open()) {
|
||||
s_status = "Failed to create file";
|
||||
Notifications::instance().error("Failed to create CSV file");
|
||||
Notifications::instance().error(TR("export_tx_file_fail"));
|
||||
} else {
|
||||
// Write CSV header
|
||||
file << "Date,Type,Amount,Address,TXID,Confirmations,Memo\n";
|
||||
@@ -142,7 +142,7 @@ void ExportTransactionsDialog::render(App* app)
|
||||
|
||||
s_status = "Exported " + std::to_string(state.transactions.size()) +
|
||||
" transactions to: " + filepath;
|
||||
Notifications::instance().success("Transactions exported successfully");
|
||||
Notifications::instance().success(TR("export_tx_success"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,15 +113,14 @@ void ImportKeyDialog::render(App* app)
|
||||
auto importBtn = S.button("dialogs.import-key", "import-button");
|
||||
auto closeBtn = S.button("dialogs.import-key", "close-button");
|
||||
|
||||
if (material::BeginOverlayDialog("Import Private Key", &s_open, win.width, 0.94f)) {
|
||||
if (material::BeginOverlayDialog(TR("import_key_title"), &s_open, win.width, 0.94f)) {
|
||||
// Warning
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.8f, 0.0f, 1.0f));
|
||||
ImGui::PushFont(material::Type().iconSmall());
|
||||
ImGui::Text(ICON_MD_WARNING);
|
||||
ImGui::PopFont();
|
||||
ImGui::SameLine(0, 4.0f);
|
||||
ImGui::TextWrapped("Warning: Never share your private keys! "
|
||||
"Importing keys from untrusted sources can compromise your wallet.");
|
||||
ImGui::TextWrapped("%s", TR("import_key_warning"));
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
ImGui::Spacing();
|
||||
@@ -129,13 +128,11 @@ void ImportKeyDialog::render(App* app)
|
||||
ImGui::Spacing();
|
||||
|
||||
// Key input
|
||||
ImGui::Text("Private Key(s):");
|
||||
ImGui::Text("%s", TR("import_key_label"));
|
||||
ImGui::SameLine();
|
||||
ImGui::TextDisabled("(?)");
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Enter one or more private keys, one per line.\n"
|
||||
"Supports both z-address and t-address keys.\n"
|
||||
"Lines starting with # are treated as comments.");
|
||||
ImGui::SetTooltip("%s", TR("import_key_tooltip"));
|
||||
}
|
||||
|
||||
if (s_importing) {
|
||||
@@ -147,7 +144,7 @@ void ImportKeyDialog::render(App* app)
|
||||
ImVec2(-1, keyInput.height > 0 ? keyInput.height : 150), ImGuiInputTextFlags_AllowTabInput);
|
||||
|
||||
// Paste button
|
||||
if (material::StyledButton("Paste from Clipboard", ImVec2(0,0), S.resolveFont(importBtn.font))) {
|
||||
if (material::StyledButton(TR("paste_from_clipboard"), ImVec2(0,0), S.resolveFont(importBtn.font))) {
|
||||
const char* clipboard = ImGui::GetClipboardText();
|
||||
if (clipboard) {
|
||||
strncpy(s_key_input, clipboard, sizeof(s_key_input) - 1);
|
||||
@@ -155,24 +152,24 @@ void ImportKeyDialog::render(App* app)
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
if (material::StyledButton("Clear", ImVec2(0,0), S.resolveFont(importBtn.font))) {
|
||||
if (material::StyledButton(TR("clear"), ImVec2(0,0), S.resolveFont(importBtn.font))) {
|
||||
s_key_input[0] = '\0';
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
// Rescan options
|
||||
ImGui::Checkbox("Rescan blockchain after import", &s_rescan);
|
||||
ImGui::Checkbox(TR("import_key_rescan"), &s_rescan);
|
||||
|
||||
if (s_rescan) {
|
||||
ImGui::Indent();
|
||||
ImGui::Text("Start height:");
|
||||
ImGui::Text("%s", TR("import_key_start_height"));
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(rescanInput.width);
|
||||
ImGui::InputInt("##RescanHeight", &s_rescan_height);
|
||||
if (s_rescan_height < 0) s_rescan_height = 0;
|
||||
ImGui::SameLine();
|
||||
ImGui::TextDisabled("(0 = full rescan)");
|
||||
ImGui::TextDisabled("%s", TR("import_key_full_rescan"));
|
||||
ImGui::Unindent();
|
||||
}
|
||||
|
||||
@@ -189,13 +186,13 @@ void ImportKeyDialog::render(App* app)
|
||||
ImGui::BeginDisabled();
|
||||
}
|
||||
|
||||
if (material::StyledButton("Import Key(s)", ImVec2(importBtn.width, 0), S.resolveFont(importBtn.font))) {
|
||||
if (material::StyledButton(TR("import_key_btn"), ImVec2(importBtn.width, 0), S.resolveFont(importBtn.font))) {
|
||||
auto keys = splitKeys(s_key_input);
|
||||
|
||||
if (keys.empty()) {
|
||||
Notifications::instance().warning("No valid keys found in input");
|
||||
Notifications::instance().warning(TR("import_key_no_valid"));
|
||||
} else if (!app->rpc() || !app->rpc()->isConnected()) {
|
||||
Notifications::instance().error("Not connected to daemon");
|
||||
Notifications::instance().error(TR("not_connected"));
|
||||
} else {
|
||||
s_importing = true;
|
||||
s_total_keys = static_cast<int>(keys.size());
|
||||
@@ -237,7 +234,7 @@ void ImportKeyDialog::render(App* app)
|
||||
imported, failed);
|
||||
s_status = buf;
|
||||
if (imported > 0) {
|
||||
Notifications::instance().success("Keys imported successfully");
|
||||
Notifications::instance().success(TR("import_key_success"));
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -248,11 +245,11 @@ void ImportKeyDialog::render(App* app)
|
||||
if (s_importing) {
|
||||
ImGui::EndDisabled();
|
||||
ImGui::SameLine();
|
||||
ImGui::TextDisabled("Importing %d/%d...", s_imported_keys + s_failed_keys, s_total_keys);
|
||||
ImGui::TextDisabled(TR("import_key_progress"), s_imported_keys + s_failed_keys, s_total_keys);
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
if (material::StyledButton("Close", ImVec2(closeBtn.width, 0), S.resolveFont(closeBtn.font))) {
|
||||
if (material::StyledButton(TR("close"), ImVec2(closeBtn.width, 0), S.resolveFont(closeBtn.font))) {
|
||||
s_open = false;
|
||||
}
|
||||
|
||||
@@ -271,9 +268,9 @@ void ImportKeyDialog::render(App* app)
|
||||
ImGui::Spacing();
|
||||
|
||||
// Help text
|
||||
ImGui::TextDisabled("Supported key formats:");
|
||||
ImGui::BulletText("Z-address spending keys (secret-extended-key-...)");
|
||||
ImGui::BulletText("T-address WIF private keys");
|
||||
ImGui::TextDisabled("%s", TR("import_key_formats"));
|
||||
ImGui::BulletText("%s", TR("import_key_z_format"));
|
||||
ImGui::BulletText("%s", TR("import_key_t_format"));
|
||||
material::EndOverlayDialog();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,15 +63,13 @@ void KeyExportDialog::render(App* app)
|
||||
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.6f, 0.2f, 0.2f, 0.3f));
|
||||
ImGui::BeginChild("WarningBox", ImVec2(-1, warningBox.height > 0 ? warningBox.height : 80), true);
|
||||
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.4f, 0.4f, 1.0f), " WARNING!");
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.4f, 0.4f, 1.0f), " %s", TR("warning_upper"));
|
||||
ImGui::Spacing();
|
||||
|
||||
if (s_key_type == KeyType::Private) {
|
||||
ImGui::TextWrapped(" Keep this key SECRET! Anyone with this key can spend your "
|
||||
"funds. Never share it online or with untrusted parties.");
|
||||
ImGui::TextWrapped(" %s", TR("key_export_private_warning"));
|
||||
} else {
|
||||
ImGui::TextWrapped(" This viewing key allows others to see your incoming transactions "
|
||||
"and balance, but NOT spend your funds. Share only with trusted parties.");
|
||||
ImGui::TextWrapped(" %s", TR("key_export_viewing_warning"));
|
||||
}
|
||||
|
||||
ImGui::EndChild();
|
||||
@@ -82,7 +80,7 @@ void KeyExportDialog::render(App* app)
|
||||
ImGui::Spacing();
|
||||
|
||||
// Address display
|
||||
ImGui::Text("Address:");
|
||||
ImGui::Text("%s", TR("address_label"));
|
||||
|
||||
// Determine if it's a z-address (longer) or t-address
|
||||
bool is_zaddr = s_address.length() > 50;
|
||||
@@ -105,16 +103,16 @@ void KeyExportDialog::render(App* app)
|
||||
ImGui::Spacing();
|
||||
|
||||
// Key display section
|
||||
const char* key_label = (s_key_type == KeyType::Private) ? "Private Key:" : "Viewing Key:";
|
||||
const char* key_label = (s_key_type == KeyType::Private) ? TR("key_export_private_key") : TR("key_export_viewing_key");
|
||||
ImGui::Text("%s", key_label);
|
||||
|
||||
if (s_fetching) {
|
||||
ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), "Fetching key from wallet...");
|
||||
ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), "%s", TR("key_export_fetching"));
|
||||
} else if (!s_error.empty()) {
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), "Error: %s", s_error.c_str());
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), TR("error_format"), s_error.c_str());
|
||||
} else if (s_key.empty()) {
|
||||
// Show button to fetch key
|
||||
if (material::StyledButton("Reveal Key", ImVec2(revealBtn.width, 0), S.resolveFont(revealBtn.font))) {
|
||||
if (material::StyledButton(TR("key_export_reveal"), ImVec2(revealBtn.width, 0), S.resolveFont(revealBtn.font))) {
|
||||
s_fetching = true;
|
||||
|
||||
// Check if z-address or t-address
|
||||
@@ -171,13 +169,13 @@ void KeyExportDialog::render(App* app)
|
||||
});
|
||||
}
|
||||
} else {
|
||||
s_error = "Viewing keys are only available for shielded (z) addresses";
|
||||
s_error = TR("key_export_viewing_keys_zonly");
|
||||
s_fetching = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::TextDisabled("Click to retrieve the key from your wallet");
|
||||
ImGui::TextDisabled("%s", TR("key_export_click_retrieve"));
|
||||
} else {
|
||||
// Key has been fetched - display it
|
||||
|
||||
@@ -201,7 +199,7 @@ void KeyExportDialog::render(App* app)
|
||||
// Show/Hide and Copy buttons
|
||||
ImGui::Spacing();
|
||||
|
||||
if (material::StyledButton(s_show_key ? "Hide" : "Show", ImVec2(toggleBtn.width, 0), S.resolveFont(toggleBtn.font))) {
|
||||
if (material::StyledButton(s_show_key ? TR("hide") : TR("show"), ImVec2(toggleBtn.width, 0), S.resolveFont(toggleBtn.font))) {
|
||||
s_show_key = !s_show_key;
|
||||
}
|
||||
|
||||
@@ -222,7 +220,7 @@ void KeyExportDialog::render(App* app)
|
||||
float avail_width = ImGui::GetContentRegionAvail().x;
|
||||
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (avail_width - button_width) / 2.0f);
|
||||
|
||||
if (material::StyledButton("Close", ImVec2(button_width, 0), S.resolveFont(closeBtn.font))) {
|
||||
if (material::StyledButton(TR("close"), ImVec2(button_width, 0), S.resolveFont(closeBtn.font))) {
|
||||
s_open = false;
|
||||
// Clear sensitive data
|
||||
s_key.clear();
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "../../data/wallet_state.h"
|
||||
#include "../../config/settings.h"
|
||||
#include "../../data/exchange_info.h"
|
||||
#include "../../util/i18n.h"
|
||||
#include "../schema/ui_schema.h"
|
||||
#include "../material/type.h"
|
||||
#include "../material/draw_helpers.h"
|
||||
@@ -147,15 +148,17 @@ void RenderMarketTab(App* app)
|
||||
float mkCardBudget = std::max(200.0f, marketAvail.y - mkOverhead);
|
||||
|
||||
Layout::SectionBudget mb(mkCardBudget);
|
||||
float statsMarketBudH = mb.allocate(0.14f, S.drawElement("tabs.market", "stats-card-min-height").size);
|
||||
float portfolioBudgetH = mb.allocate(0.18f, 50.0f);
|
||||
|
||||
// ================================================================
|
||||
// PRICE HERO — Large glass card with price + change badge
|
||||
// PRICE SUMMARY — Combined hero card with price, stats, and exchange
|
||||
// ================================================================
|
||||
{
|
||||
float dp = Layout::dpiScale();
|
||||
ImVec2 cardMin = ImGui::GetCursorScreenPos();
|
||||
float cardH = std::max(S.drawElement("tabs.market", "hero-card-min-height").size, S.drawElement("tabs.market", "hero-card-height").size * vs);
|
||||
float heroMinH = S.drawElement("tabs.market", "hero-card-min-height").size;
|
||||
float statsRowH = ovFont->LegacySize + Layout::spacingXs() + sub1->LegacySize + pad;
|
||||
float cardH = std::max(heroMinH + statsRowH + pad, (S.drawElement("tabs.market", "hero-card-height").size + statsRowH + pad) * vs);
|
||||
ImVec2 cardMax(cardMin.x + availWidth, cardMin.y + cardH);
|
||||
DrawGlassPanel(dl, cardMin, cardMax, glassSpec);
|
||||
|
||||
@@ -172,36 +175,118 @@ void RenderMarketTab(App* app)
|
||||
float cy = cardMin.y + Layout::spacingLg();
|
||||
|
||||
if (market.price_usd > 0) {
|
||||
// Large price with text shadow
|
||||
// ---- HERO PRICE (large, prominent) ----
|
||||
ImFont* h3 = Type().h3();
|
||||
std::string priceStr = FormatPrice(market.price_usd);
|
||||
ImU32 priceCol = Success();
|
||||
DrawTextShadow(dl, h4, h4->LegacySize, ImVec2(cx, cy), priceCol, priceStr.c_str());
|
||||
DrawTextShadow(dl, h3, h3->LegacySize, ImVec2(cx, cy), priceCol, priceStr.c_str());
|
||||
|
||||
// BTC price beside it
|
||||
float priceW = h4->CalcTextSizeA(h4->LegacySize, FLT_MAX, 0, priceStr.c_str()).x;
|
||||
snprintf(buf, sizeof(buf), "%.10f BTC", market.price_btc);
|
||||
dl->AddText(capFont, capFont->LegacySize,
|
||||
ImVec2(cx + priceW + Layout::spacingXl(), cy + (h4->LegacySize - capFont->LegacySize)),
|
||||
OnSurfaceMedium(), buf);
|
||||
// Ticker label after price
|
||||
float priceW = h3->CalcTextSizeA(h3->LegacySize, FLT_MAX, 0, priceStr.c_str()).x;
|
||||
dl->AddText(body2, body2->LegacySize,
|
||||
ImVec2(cx + priceW + Layout::spacingSm(), cy + (h3->LegacySize - body2->LegacySize)),
|
||||
OnSurfaceMedium(), DRAGONX_TICKER);
|
||||
|
||||
// 24h change badge
|
||||
float badgeY = cy + h4->LegacySize + 8;
|
||||
// 24h change badge — to the right of ticker
|
||||
float tickerW = body2->CalcTextSizeA(body2->LegacySize, FLT_MAX, 0, DRAGONX_TICKER).x;
|
||||
float badgeX = cx + priceW + Layout::spacingSm() + tickerW + Layout::spacingMd();
|
||||
ImU32 chgCol = market.change_24h >= 0 ? Success() : Error();
|
||||
snprintf(buf, sizeof(buf), "%s%.2f%%", market.change_24h >= 0 ? "+" : "", market.change_24h);
|
||||
|
||||
ImVec2 txtSz = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, buf);
|
||||
float badgePad = Layout::spacingSm() + Layout::spacingXs();
|
||||
ImVec2 bMin(cx, badgeY);
|
||||
ImVec2 bMax(cx + txtSz.x + badgePad * 2, badgeY + txtSz.y + badgePad);
|
||||
snprintf(buf, sizeof(buf), "%s%.2f%% 24h", market.change_24h >= 0 ? "+" : "", market.change_24h);
|
||||
ImVec2 chgSz = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, buf);
|
||||
float badgePadH = Layout::spacingSm();
|
||||
float badgePadV = Layout::spacingXs();
|
||||
ImVec2 bMin(badgeX, cy + (h3->LegacySize - chgSz.y - badgePadV * 2) * 0.5f);
|
||||
ImVec2 bMax(badgeX + chgSz.x + badgePadH * 2, bMin.y + chgSz.y + badgePadV * 2);
|
||||
ImU32 badgeBg = market.change_24h >= 0 ? WithAlpha(Success(), 30) : WithAlpha(Error(), 30);
|
||||
dl->AddRectFilled(bMin, bMax, badgeBg, 4.0f);
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(cx + badgePad, badgeY + badgePad * 0.5f), chgCol, buf);
|
||||
dl->AddRectFilled(bMin, bMax, badgeBg, 4.0f * dp);
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(bMin.x + badgePadH, bMin.y + badgePadV), chgCol, buf);
|
||||
|
||||
// "24h" label after badge
|
||||
dl->AddText(capFont, capFont->LegacySize,
|
||||
ImVec2(bMax.x + 6, badgeY + badgePad * 0.5f), OnSurfaceDisabled(), "24h");
|
||||
// ---- SEPARATOR ----
|
||||
float sepY = cy + h3->LegacySize + Layout::spacingMd();
|
||||
float rnd = glassSpec.rounding;
|
||||
dl->AddLine(ImVec2(cardMin.x + rnd * 0.5f, sepY),
|
||||
ImVec2(cardMax.x - rnd * 0.5f, sepY),
|
||||
WithAlpha(OnSurface(), 15), 1.0f * dp);
|
||||
|
||||
// ---- STATS ROW (BTC Price | Volume | Market Cap) ----
|
||||
float statsY = sepY + Layout::spacingSm();
|
||||
float colW = (availWidth - Layout::spacingLg() * 2) / 3.0f;
|
||||
|
||||
struct StatItem { const char* label; std::string value; ImU32 valueCol; };
|
||||
StatItem stats[3] = {
|
||||
{TR("market_btc_price"), "", OnSurface()},
|
||||
{TR("market_24h_volume"), FormatCompactUSD(market.volume_24h), OnSurface()},
|
||||
{TR("market_cap"), FormatCompactUSD(market.market_cap), OnSurface()},
|
||||
};
|
||||
snprintf(buf, sizeof(buf), "%.10f", market.price_btc);
|
||||
stats[0].value = buf;
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
float sx = cardMin.x + Layout::spacingLg() + i * colW;
|
||||
float centerX = sx + colW * 0.5f;
|
||||
|
||||
// Label (overline, centered)
|
||||
ImVec2 lblSz = ovFont->CalcTextSizeA(ovFont->LegacySize, 10000, 0, stats[i].label);
|
||||
dl->AddText(ovFont, ovFont->LegacySize,
|
||||
ImVec2(centerX - lblSz.x * 0.5f, statsY), OnSurfaceMedium(), stats[i].label);
|
||||
|
||||
// Value (subtitle1, centered)
|
||||
float valY = statsY + ovFont->LegacySize + Layout::spacingXs();
|
||||
ImVec2 valSz = sub1->CalcTextSizeA(sub1->LegacySize, 10000, 0, stats[i].value.c_str());
|
||||
dl->AddText(sub1, sub1->LegacySize,
|
||||
ImVec2(centerX - valSz.x * 0.5f, valY), stats[i].valueCol, stats[i].value.c_str());
|
||||
}
|
||||
|
||||
// ---- TRADE BUTTON (top-right of card) ----
|
||||
if (!currentExchange.pairs.empty()) {
|
||||
const char* pairName = currentExchange.pairs[s_pair_idx].displayName.c_str();
|
||||
ImFont* iconFont = Type().iconSmall();
|
||||
ImVec2 textSz = body2->CalcTextSizeA(body2->LegacySize, FLT_MAX, 0, pairName);
|
||||
ImVec2 iconSz = iconFont->CalcTextSizeA(iconFont->LegacySize, FLT_MAX, 0, ICON_MD_OPEN_IN_NEW);
|
||||
float iconGap = Layout::spacingSm();
|
||||
float tradePadH = Layout::spacingMd();
|
||||
float tradePadV = Layout::spacingSm();
|
||||
float tradeBtnW = textSz.x + iconGap + iconSz.x + tradePadH * 2;
|
||||
float tradeBtnH = std::max(textSz.y, iconSz.y) + tradePadV * 2;
|
||||
float tradeBtnX = cardMax.x - pad - tradeBtnW;
|
||||
float tradeBtnY = cardMin.y + Layout::spacingSm();
|
||||
|
||||
ImVec2 tMin(tradeBtnX, tradeBtnY), tMax(tradeBtnX + tradeBtnW, tradeBtnY + tradeBtnH);
|
||||
bool tradeHov = material::IsRectHovered(tMin, tMax);
|
||||
|
||||
// Glass pill background
|
||||
GlassPanelSpec tradeBtnGlass;
|
||||
tradeBtnGlass.rounding = tradeBtnH * 0.5f;
|
||||
tradeBtnGlass.fillAlpha = tradeHov ? 35 : 20;
|
||||
DrawGlassPanel(dl, tMin, tMax, tradeBtnGlass);
|
||||
if (tradeHov)
|
||||
dl->AddRectFilled(tMin, tMax, WithAlpha(Primary(), 20), tradeBtnH * 0.5f);
|
||||
|
||||
// Text (pair name with body2, icon with icon font)
|
||||
ImU32 tradeCol = tradeHov ? OnSurface() : OnSurfaceMedium();
|
||||
float contentY = tradeBtnY + tradePadV;
|
||||
float curX = tradeBtnX + tradePadH;
|
||||
dl->AddText(body2, body2->LegacySize,
|
||||
ImVec2(curX, contentY), tradeCol, pairName);
|
||||
curX += textSz.x + iconGap;
|
||||
dl->AddText(iconFont, iconFont->LegacySize,
|
||||
ImVec2(curX, contentY + (textSz.y - iconSz.y) * 0.5f), tradeCol, ICON_MD_OPEN_IN_NEW);
|
||||
|
||||
// Click
|
||||
ImVec2 savedCur = ImGui::GetCursorScreenPos();
|
||||
ImGui::SetCursorScreenPos(tMin);
|
||||
ImGui::InvisibleButton("##TradeOnExchange", ImVec2(tradeBtnW, tradeBtnH));
|
||||
if (ImGui::IsItemClicked()) {
|
||||
util::Platform::openUrl(currentExchange.pairs[s_pair_idx].tradeUrl);
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
||||
ImGui::SetTooltip(TR("market_trade_on"), currentExchange.name.c_str());
|
||||
}
|
||||
ImGui::SetCursorScreenPos(savedCur);
|
||||
}
|
||||
} else {
|
||||
DrawTextShadow(dl, sub1, sub1->LegacySize, ImVec2(cx, cy + 10), OnSurfaceDisabled(), "Price data unavailable");
|
||||
DrawTextShadow(dl, sub1, sub1->LegacySize, ImVec2(cx, cy + 10), OnSurfaceDisabled(), TR("market_price_unavailable"));
|
||||
}
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMax.y));
|
||||
@@ -209,42 +294,6 @@ void RenderMarketTab(App* app)
|
||||
ImGui::Dummy(ImVec2(0, gap));
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// STATS — Three glass cards (Price | Volume | Market Cap)
|
||||
// ================================================================
|
||||
{
|
||||
float cardW = (availWidth - 2 * gap) / 3.0f;
|
||||
float cardH = std::min(StatCardHeight(vs), statsMarketBudH);
|
||||
ImVec2 origin = ImGui::GetCursorScreenPos();
|
||||
|
||||
struct StatInfo { const char* label; std::string value; ImU32 col; ImU32 accent; };
|
||||
StatInfo cards[3] = {
|
||||
{"PRICE", market.price_usd > 0 ? FormatPrice(market.price_usd) : "N/A",
|
||||
OnSurface(), WithAlpha(Success(), 200)},
|
||||
{"24H VOLUME", FormatCompactUSD(market.volume_24h),
|
||||
OnSurface(), WithAlpha(Secondary(), 200)},
|
||||
{"MARKET CAP", FormatCompactUSD(market.market_cap),
|
||||
OnSurface(), WithAlpha(Warning(), 200)},
|
||||
};
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
float xOff = i * (cardW + gap);
|
||||
ImVec2 cMin(origin.x + xOff, origin.y);
|
||||
ImVec2 cMax(cMin.x + cardW, cMin.y + cardH);
|
||||
|
||||
StatCardSpec sc;
|
||||
sc.overline = cards[i].label;
|
||||
sc.value = cards[i].value.c_str();
|
||||
sc.valueCol = cards[i].col;
|
||||
sc.accentCol = cards[i].accent;
|
||||
sc.centered = true;
|
||||
DrawStatCard(dl, cMin, cMax, sc, glassSpec);
|
||||
}
|
||||
|
||||
ImGui::Dummy(ImVec2(availWidth, cardH));
|
||||
ImGui::Dummy(ImVec2(0, gap));
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// PRICE CHART — Custom drawn inside glass panel (matches app design)
|
||||
// ================================================================
|
||||
@@ -340,7 +389,7 @@ void RenderMarketTab(App* app)
|
||||
|
||||
// X-axis labels
|
||||
const int xlabels[] = {0, 6, 12, 18, 23};
|
||||
const char* xlabelText[] = {"24h", "18h", "12h", "6h", "Now"};
|
||||
const char* xlabelText[] = {TR("market_24h"), TR("market_18h"), TR("market_12h"), TR("market_6h"), TR("market_now")};
|
||||
for (int xi = 0; xi < 5; xi++) {
|
||||
int idx = xlabels[xi];
|
||||
float t = (float)idx / (float)(n - 1);
|
||||
@@ -395,7 +444,7 @@ void RenderMarketTab(App* app)
|
||||
ImVec2(tipX + tipPad, tipY + tipPad), dotCol, buf);
|
||||
}
|
||||
} else {
|
||||
const char* msg = "No price history available";
|
||||
const char* msg = TR("market_no_history");
|
||||
ImVec2 ts = sub1->CalcTextSizeA(sub1->LegacySize, FLT_MAX, 0, msg);
|
||||
dl->AddText(sub1, sub1->LegacySize,
|
||||
ImVec2(chartMin.x + (availWidth - ts.x) * 0.5f, chartMin.y + chartH * 0.45f),
|
||||
@@ -429,7 +478,7 @@ void RenderMarketTab(App* app)
|
||||
s_history_initialized = false;
|
||||
s_last_refresh_time = ImGui::GetTime();
|
||||
}
|
||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("Refresh price data");
|
||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("market_refresh_price"));
|
||||
|
||||
// Timestamp text to the left of refresh button
|
||||
if (s_last_refresh_time > 0.0) {
|
||||
@@ -452,7 +501,7 @@ void RenderMarketTab(App* app)
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// EXCHANGE SELECTOR — Combo dropdown + pair name + trade link
|
||||
// EXCHANGE SELECTOR — Combo dropdown + attribution
|
||||
// ================================================================
|
||||
ImGui::Dummy(ImVec2(0, S.drawElement("tabs.market", "exchange-top-gap").size));
|
||||
{
|
||||
@@ -483,28 +532,9 @@ void RenderMarketTab(App* app)
|
||||
}
|
||||
ImGui::PopItemWidth();
|
||||
|
||||
// Show current pair name beside combo
|
||||
if (!currentExchange.pairs.empty()) {
|
||||
ImGui::SameLine(0, Layout::spacingLg());
|
||||
Type().textColored(TypeStyle::Subtitle1, OnSurface(),
|
||||
currentExchange.pairs[s_pair_idx].displayName.c_str());
|
||||
|
||||
// "Open on exchange" button
|
||||
ImGui::SameLine(0, Layout::spacingSm());
|
||||
ImGui::PushFont(material::Typography::instance().iconSmall());
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4, 4));
|
||||
snprintf(buf, sizeof(buf), ICON_MD_OPEN_IN_NEW "##TradeLink");
|
||||
if (ImGui::Button(buf)) {
|
||||
util::Platform::openUrl(currentExchange.pairs[s_pair_idx].tradeUrl);
|
||||
}
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::PopFont();
|
||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("Open on %s", currentExchange.name.c_str());
|
||||
}
|
||||
|
||||
// Attribution
|
||||
ImGui::SameLine(0, Layout::spacingLg());
|
||||
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), "Price data from CoinGecko API");
|
||||
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), TR("market_attribution"));
|
||||
|
||||
if (!market.last_updated.empty()) {
|
||||
ImGui::SameLine(0, 12);
|
||||
@@ -722,7 +752,7 @@ void RenderMarketTab(App* app)
|
||||
// PORTFOLIO — Glass card with balance breakdown
|
||||
// ================================================================
|
||||
{
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), "PORTFOLIO");
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("market_portfolio"));
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
|
||||
double total_balance = state.totalBalance;
|
||||
@@ -760,7 +790,7 @@ void RenderMarketTab(App* app)
|
||||
dl->AddText(capFont, capFont->LegacySize,
|
||||
ImVec2(cardMax.x - valW - pad, cy + 2), OnSurfaceMedium(), buf);
|
||||
} else {
|
||||
dl->AddText(sub1, sub1->LegacySize, ImVec2(cx, cy), OnSurfaceDisabled(), "No price data");
|
||||
dl->AddText(sub1, sub1->LegacySize, ImVec2(cx, cy), OnSurfaceDisabled(), TR("market_no_price"));
|
||||
}
|
||||
|
||||
cy += sub1->LegacySize + 8;
|
||||
@@ -799,7 +829,7 @@ void RenderMarketTab(App* app)
|
||||
shieldedW > 0.5f ? ImDrawFlags_RoundCornersRight : ImDrawFlags_RoundCornersAll, 3.0f);
|
||||
|
||||
int pct = (int)(shieldedRatio * 100.0f + 0.5f);
|
||||
snprintf(buf, sizeof(buf), "%d%% Shielded", pct);
|
||||
snprintf(buf, sizeof(buf), TR("market_pct_shielded"), pct);
|
||||
dl->AddText(capFont, capFont->LegacySize,
|
||||
ImVec2(cx, cy + barH + 2), OnSurfaceDisabled(), buf);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "mining_tab.h"
|
||||
#include "../../app.h"
|
||||
#include "../../util/i18n.h"
|
||||
#include "../../config/version.h"
|
||||
#include "../../data/wallet_state.h"
|
||||
#include "../../config/settings.h"
|
||||
@@ -38,6 +39,9 @@ static bool s_threads_initialized = false;
|
||||
static bool s_drag_active = false;
|
||||
static int s_drag_anchor_thread = 0; // thread# where drag started
|
||||
|
||||
// Earnings filter: 0 = All, 1 = Solo, 2 = Pool
|
||||
static int s_earnings_filter = 0;
|
||||
|
||||
// Pool mode state
|
||||
static bool s_pool_mode = false;
|
||||
static char s_pool_url[256] = "pool.dragonx.is:3433";
|
||||
@@ -126,7 +130,13 @@ void RenderMiningTab(App* app)
|
||||
int max_threads = GetMaxMiningThreads();
|
||||
|
||||
if (!s_threads_initialized) {
|
||||
s_selected_threads = mining.generate ? std::max(1, mining.genproclimit) : 1;
|
||||
int saved = app->settings()->getPoolThreads();
|
||||
if (mining.generate)
|
||||
s_selected_threads = std::max(1, mining.genproclimit);
|
||||
else if (saved > 0)
|
||||
s_selected_threads = std::min(saved, max_threads);
|
||||
else
|
||||
s_selected_threads = 1;
|
||||
s_threads_initialized = true;
|
||||
}
|
||||
|
||||
@@ -234,7 +244,7 @@ void RenderMiningTab(App* app)
|
||||
dl->AddRectFilled(soloMin, soloMax, WithAlpha(OnSurface(), 20), toggleRnd);
|
||||
}
|
||||
{
|
||||
const char* label = "SOLO";
|
||||
const char* label = TR("mining_solo");
|
||||
ImVec2 sz = ovFont->CalcTextSizeA(ovFont->LegacySize, FLT_MAX, 0, label);
|
||||
float lx = soloMin.x + (toggleW - sz.x) * 0.5f;
|
||||
float ly = soloMin.y + (toggleH - sz.y) * 0.5f;
|
||||
@@ -255,7 +265,7 @@ void RenderMiningTab(App* app)
|
||||
dl->AddRectFilled(poolMin, poolMax, WithAlpha(OnSurface(), 20), toggleRnd);
|
||||
}
|
||||
{
|
||||
const char* label = "POOL";
|
||||
const char* label = TR("mining_pool");
|
||||
ImVec2 sz = ovFont->CalcTextSizeA(ovFont->LegacySize, FLT_MAX, 0, label);
|
||||
float lx = poolMin.x + (toggleW - sz.x) * 0.5f;
|
||||
float ly = poolMin.y + (toggleH - sz.y) * 0.5f;
|
||||
@@ -287,7 +297,7 @@ void RenderMiningTab(App* app)
|
||||
}
|
||||
if (poolHov && !soloMiningActive) ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
||||
if (poolHov && soloMiningActive && !s_pool_mode) {
|
||||
ImGui::SetTooltip("Stop solo mining to use pool mining");
|
||||
ImGui::SetTooltip("%s", TR("mining_stop_solo_for_pool"));
|
||||
}
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(tMin.x, tMax.y));
|
||||
@@ -307,7 +317,7 @@ void RenderMiningTab(App* app)
|
||||
ImGui::PopFont();
|
||||
ImGui::SameLine(0, Layout::spacingSm());
|
||||
ImGui::PushFont(capFont);
|
||||
ImGui::TextUnformatted("Stop solo mining to configure pool settings");
|
||||
ImGui::TextUnformatted(TR("mining_stop_solo_for_pool_settings"));
|
||||
ImGui::PopFont();
|
||||
ImGui::PopStyleColor();
|
||||
} else if (s_pool_mode) {
|
||||
@@ -339,7 +349,7 @@ void RenderMiningTab(App* app)
|
||||
|
||||
// === Pool URL input ===
|
||||
ImGui::SetNextItemWidth(urlW);
|
||||
if (ImGui::InputTextWithHint("##PoolURL", "Pool URL", s_pool_url, sizeof(s_pool_url))) {
|
||||
if (ImGui::InputTextWithHint("##PoolURL", TR("mining_pool_url"), s_pool_url, sizeof(s_pool_url))) {
|
||||
s_pool_settings_dirty = true;
|
||||
}
|
||||
|
||||
@@ -357,7 +367,7 @@ void RenderMiningTab(App* app)
|
||||
dl2->AddRectFilled(btnPos, ImVec2(btnPos.x + btnSize.x, btnPos.y + btnSize.y),
|
||||
StateHover(), 4.0f * dp);
|
||||
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
||||
ImGui::SetTooltip("Saved pools");
|
||||
ImGui::SetTooltip("%s", TR("mining_saved_pools"));
|
||||
}
|
||||
ImFont* icoFont = Type().iconSmall();
|
||||
const char* dropIcon = ICON_MD_ARROW_DROP_DOWN;
|
||||
@@ -389,7 +399,7 @@ void RenderMiningTab(App* app)
|
||||
dl2->AddRectFilled(btnPos, ImVec2(btnPos.x + btnSize.x, btnPos.y + btnSize.y),
|
||||
StateHover(), 4.0f * dp);
|
||||
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
||||
ImGui::SetTooltip(alreadySaved ? "Already saved" : "Save pool URL");
|
||||
ImGui::SetTooltip("%s", alreadySaved ? TR("mining_already_saved") : TR("mining_save_pool_url"));
|
||||
}
|
||||
ImFont* icoFont = Type().iconSmall();
|
||||
const char* saveIcon = alreadySaved ? ICON_MD_BOOKMARK : ICON_MD_BOOKMARK_BORDER;
|
||||
@@ -416,11 +426,19 @@ void RenderMiningTab(App* app)
|
||||
if (savedUrls.empty()) {
|
||||
ImGui::SetCursorPosX(8 * dp);
|
||||
ImGui::PushFont(Type().caption());
|
||||
ImGui::TextDisabled("No saved pools");
|
||||
ImGui::TextDisabled("%s", TR("mining_no_saved_pools"));
|
||||
ImGui::PopFont();
|
||||
ImGui::SetCursorPosX(8 * dp);
|
||||
ImGui::PushFont(Type().caption());
|
||||
ImGui::TextDisabled("Click " ICON_MD_BOOKMARK_BORDER " to save");
|
||||
ImGui::TextDisabled("%s", TR("mining_click"));
|
||||
ImGui::PopFont();
|
||||
ImGui::SameLine(0, 2 * dp);
|
||||
ImGui::PushFont(Type().iconSmall());
|
||||
ImGui::TextDisabled(ICON_MD_BOOKMARK_BORDER);
|
||||
ImGui::PopFont();
|
||||
ImGui::SameLine(0, 2 * dp);
|
||||
ImGui::PushFont(Type().caption());
|
||||
ImGui::TextDisabled("%s", TR("mining_to_save"));
|
||||
ImGui::PopFont();
|
||||
} else {
|
||||
std::string urlToRemove;
|
||||
@@ -457,7 +475,7 @@ void RenderMiningTab(App* app)
|
||||
if (inXZone) {
|
||||
pdl->AddRectFilled(xMin, xMax, IM_COL32(255, 80, 80, 30));
|
||||
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
||||
ImGui::SetTooltip("Remove");
|
||||
ImGui::SetTooltip("%s", TR("mining_remove"));
|
||||
} else if (rowHov) {
|
||||
// Show faint X when row is hovered
|
||||
ImFont* icoF = Type().iconSmall();
|
||||
@@ -508,11 +526,11 @@ void RenderMiningTab(App* app)
|
||||
float wrkGroupW = wrkW + perGroupExtra;
|
||||
|
||||
ImGui::SetNextItemWidth(wrkW);
|
||||
if (ImGui::InputTextWithHint("##PoolWorker", "Payout Address", s_pool_worker, sizeof(s_pool_worker))) {
|
||||
if (ImGui::InputTextWithHint("##PoolWorker", TR("mining_payout_address"), s_pool_worker, sizeof(s_pool_worker))) {
|
||||
s_pool_settings_dirty = true;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Your DRAGONX address for receiving pool payouts");
|
||||
ImGui::SetTooltip("%s", TR("mining_payout_tooltip"));
|
||||
}
|
||||
|
||||
// --- Worker: Dropdown arrow button ---
|
||||
@@ -529,7 +547,7 @@ void RenderMiningTab(App* app)
|
||||
dl2->AddRectFilled(btnPos, ImVec2(btnPos.x + btnSize.x, btnPos.y + btnSize.y),
|
||||
StateHover(), 4.0f * dp);
|
||||
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
||||
ImGui::SetTooltip("Saved addresses");
|
||||
ImGui::SetTooltip("%s", TR("mining_saved_addresses"));
|
||||
}
|
||||
ImFont* icoFont = Type().iconSmall();
|
||||
const char* dropIcon = ICON_MD_ARROW_DROP_DOWN;
|
||||
@@ -561,7 +579,7 @@ void RenderMiningTab(App* app)
|
||||
dl2->AddRectFilled(btnPos, ImVec2(btnPos.x + btnSize.x, btnPos.y + btnSize.y),
|
||||
StateHover(), 4.0f * dp);
|
||||
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
||||
ImGui::SetTooltip(alreadySaved ? "Already saved" : "Save payout address");
|
||||
ImGui::SetTooltip("%s", alreadySaved ? TR("mining_already_saved") : TR("mining_save_payout_address"));
|
||||
}
|
||||
ImFont* icoFont = Type().iconSmall();
|
||||
const char* saveIcon = alreadySaved ? ICON_MD_BOOKMARK : ICON_MD_BOOKMARK_BORDER;
|
||||
@@ -589,11 +607,19 @@ void RenderMiningTab(App* app)
|
||||
if (savedWorkers.empty()) {
|
||||
ImGui::SetCursorPosX(8 * dp);
|
||||
ImGui::PushFont(Type().caption());
|
||||
ImGui::TextDisabled("No saved addresses");
|
||||
ImGui::TextDisabled("%s", TR("mining_no_saved_addresses"));
|
||||
ImGui::PopFont();
|
||||
ImGui::SetCursorPosX(8 * dp);
|
||||
ImGui::PushFont(Type().caption());
|
||||
ImGui::TextDisabled("Click " ICON_MD_BOOKMARK_BORDER " to save");
|
||||
ImGui::TextDisabled("%s", TR("mining_click"));
|
||||
ImGui::PopFont();
|
||||
ImGui::SameLine(0, 2 * dp);
|
||||
ImGui::PushFont(Type().iconSmall());
|
||||
ImGui::TextDisabled(ICON_MD_BOOKMARK_BORDER);
|
||||
ImGui::PopFont();
|
||||
ImGui::SameLine(0, 2 * dp);
|
||||
ImGui::PushFont(Type().caption());
|
||||
ImGui::TextDisabled("%s", TR("mining_to_save"));
|
||||
ImGui::PopFont();
|
||||
} else {
|
||||
std::string addrToRemove;
|
||||
@@ -633,7 +659,7 @@ void RenderMiningTab(App* app)
|
||||
if (inXZone) {
|
||||
pdl->AddRectFilled(xMin, xMax, IM_COL32(255, 80, 80, 30));
|
||||
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
||||
ImGui::SetTooltip("Remove");
|
||||
ImGui::SetTooltip("%s", TR("mining_remove"));
|
||||
} else if (rowHov) {
|
||||
ImFont* icoF = Type().iconSmall();
|
||||
const char* xIcon = ICON_MD_CLOSE;
|
||||
@@ -694,7 +720,7 @@ void RenderMiningTab(App* app)
|
||||
dl2->AddRectFilled(btnPos, ImVec2(btnPos.x + btnSize.x, btnPos.y + btnSize.y),
|
||||
StateHover(), 4.0f * dp);
|
||||
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
||||
ImGui::SetTooltip("Reset to defaults");
|
||||
ImGui::SetTooltip("%s", TR("mining_reset_defaults"));
|
||||
}
|
||||
|
||||
// Icon
|
||||
@@ -781,9 +807,9 @@ void RenderMiningTab(App* app)
|
||||
// --- Header row: "THREADS 4 / 16" + RAM est + active indicator ---
|
||||
{
|
||||
ImVec2 labelPos(cardMin.x + pad, curY);
|
||||
dl->AddText(ovFont, ovFont->LegacySize, labelPos, OnSurfaceMedium(), "THREADS");
|
||||
dl->AddText(ovFont, ovFont->LegacySize, labelPos, OnSurfaceMedium(), TR("mining_threads"));
|
||||
|
||||
float labelW = ovFont->CalcTextSizeA(ovFont->LegacySize, FLT_MAX, 0, "THREADS").x;
|
||||
float labelW = ovFont->CalcTextSizeA(ovFont->LegacySize, FLT_MAX, 0, TR("mining_threads")).x;
|
||||
snprintf(buf, sizeof(buf), " %d / %d", s_selected_threads, max_threads);
|
||||
ImVec2 countPos(labelPos.x + labelW, curY);
|
||||
dl->AddText(sub1, sub1->LegacySize, countPos, OnSurface(), buf);
|
||||
@@ -804,17 +830,58 @@ void RenderMiningTab(App* app)
|
||||
OnSurfaceDisabled(), buf);
|
||||
}
|
||||
|
||||
// Active mining indicator (top-right)
|
||||
// Idle mining toggle (top-right corner of card)
|
||||
float idleRightEdge = cardMax.x - pad;
|
||||
{
|
||||
bool idleOn = app->settings()->getMineWhenIdle();
|
||||
ImFont* icoFont = Type().iconSmall();
|
||||
const char* idleIcon = ICON_MD_SCHEDULE;
|
||||
float icoH = icoFont->LegacySize;
|
||||
float btnSz = icoH + 8.0f * dp;
|
||||
float btnX = idleRightEdge - btnSz;
|
||||
float btnY = curY + (headerH - btnSz) * 0.5f;
|
||||
|
||||
// Pill background when active
|
||||
if (idleOn) {
|
||||
dl->AddRectFilled(ImVec2(btnX, btnY), ImVec2(btnX + btnSz, btnY + btnSz),
|
||||
WithAlpha(Primary(), 60), btnSz * 0.5f);
|
||||
}
|
||||
|
||||
// Icon centered in button
|
||||
ImVec2 icoSz = icoFont->CalcTextSizeA(icoFont->LegacySize, FLT_MAX, 0, idleIcon);
|
||||
ImU32 icoCol = idleOn ? Primary() : OnSurfaceDisabled();
|
||||
dl->AddText(icoFont, icoFont->LegacySize,
|
||||
ImVec2(btnX + (btnSz - icoSz.x) * 0.5f, btnY + (btnSz - icoSz.y) * 0.5f),
|
||||
icoCol, idleIcon);
|
||||
|
||||
// Click target (save/restore cursor so layout is unaffected)
|
||||
ImVec2 savedCur = ImGui::GetCursorScreenPos();
|
||||
ImGui::SetCursorScreenPos(ImVec2(btnX, btnY));
|
||||
ImGui::InvisibleButton("##IdleMining", ImVec2(btnSz, btnSz));
|
||||
if (ImGui::IsItemClicked()) {
|
||||
app->settings()->setMineWhenIdle(!idleOn);
|
||||
app->settings()->save();
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
||||
ImGui::SetTooltip("%s", idleOn ? TR("mining_idle_on_tooltip") : TR("mining_idle_off_tooltip"));
|
||||
}
|
||||
ImGui::SetCursorScreenPos(savedCur);
|
||||
|
||||
idleRightEdge = btnX - 4.0f * dp;
|
||||
}
|
||||
|
||||
// Active mining indicator (left of idle toggle)
|
||||
if (mining.generate) {
|
||||
float pulse = effects::isLowSpecMode()
|
||||
? schema::UI().drawElement("animations", "pulse-base-normal").size
|
||||
: schema::UI().drawElement("animations", "pulse-base-normal").size + schema::UI().drawElement("animations", "pulse-amp-normal").size * (float)std::sin((double)ImGui::GetTime() * schema::UI().drawElement("animations", "pulse-speed-normal").size);
|
||||
ImU32 pulseCol = WithAlpha(Success(), (int)(255 * pulse));
|
||||
float dotR = schema::UI().drawElement("tabs.mining", "active-dot-radius").size + 2.0f * hs;
|
||||
dl->AddCircleFilled(ImVec2(cardMax.x - pad - dotR * 2, curY + dotR + 1 * dp), dotR, pulseCol);
|
||||
dl->AddCircleFilled(ImVec2(idleRightEdge - dotR, curY + dotR + 1 * dp), dotR, pulseCol);
|
||||
dl->AddText(capFont, capFont->LegacySize,
|
||||
ImVec2(cardMax.x - pad - dotR * 2 - 60 * hs, curY),
|
||||
WithAlpha(Success(), 200), "Mining");
|
||||
ImVec2(idleRightEdge - dotR - dotR - 60 * hs, curY),
|
||||
WithAlpha(Success(), 200), TR("mining_active"));
|
||||
}
|
||||
curY += headerH + secGap;
|
||||
}
|
||||
@@ -930,12 +997,16 @@ void RenderMiningTab(App* app)
|
||||
dl->AddText(capFont, capFont->LegacySize, txtPos, txtCol, buf);
|
||||
}
|
||||
|
||||
if (threads_changed && mining.generate) {
|
||||
app->startMining(s_selected_threads);
|
||||
}
|
||||
if (threads_changed && s_pool_mode && state.pool_mining.xmrig_running) {
|
||||
app->stopPoolMining();
|
||||
app->startPoolMining(s_selected_threads);
|
||||
if (threads_changed) {
|
||||
app->settings()->setPoolThreads(s_selected_threads);
|
||||
app->settings()->save();
|
||||
if (mining.generate) {
|
||||
app->startMining(s_selected_threads);
|
||||
}
|
||||
if (s_pool_mode && state.pool_mining.xmrig_running) {
|
||||
app->stopPoolMining();
|
||||
app->startPoolMining(s_selected_threads);
|
||||
}
|
||||
}
|
||||
|
||||
curY += gridH + secGap;
|
||||
@@ -1059,20 +1130,20 @@ void RenderMiningTab(App* app)
|
||||
const char* label;
|
||||
ImU32 lblCol;
|
||||
if (isToggling) {
|
||||
label = isMiningActive ? "STOPPING" : "STARTING";
|
||||
label = isMiningActive ? TR("mining_stopping") : TR("mining_starting");
|
||||
// Animated dots effect via alpha pulse
|
||||
float pulse = effects::isLowSpecMode()
|
||||
? 0.7f
|
||||
: 0.5f + 0.5f * (float)std::sin((double)ImGui::GetTime() * 3.0);
|
||||
lblCol = WithAlpha(Primary(), (int)(120 + 135 * pulse));
|
||||
} else if (isMiningActive) {
|
||||
label = "STOP";
|
||||
label = TR("mining_stop");
|
||||
lblCol = WithAlpha(Error(), 220);
|
||||
} else if (disabled) {
|
||||
label = "MINE";
|
||||
label = TR("mining_mine");
|
||||
lblCol = WithAlpha(OnSurface(), 50);
|
||||
} else {
|
||||
label = "MINE";
|
||||
label = TR("mining_mine");
|
||||
lblCol = WithAlpha(OnSurface(), 160);
|
||||
}
|
||||
ImVec2 lblSz = ovFont->CalcTextSizeA(ovFont->LegacySize, FLT_MAX, 0, label);
|
||||
@@ -1086,13 +1157,13 @@ void RenderMiningTab(App* app)
|
||||
if (!disabled)
|
||||
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
||||
if (isToggling)
|
||||
ImGui::SetTooltip(isMiningActive ? "Stopping miner..." : "Starting miner...");
|
||||
ImGui::SetTooltip("%s", isMiningActive ? TR("mining_stopping_tooltip") : TR("mining_starting_tooltip"));
|
||||
else if (isSyncing && !s_pool_mode)
|
||||
ImGui::SetTooltip("Syncing blockchain... (%.1f%%)", state.sync.verification_progress * 100.0);
|
||||
ImGui::SetTooltip(TR("mining_syncing_tooltip"), state.sync.verification_progress * 100.0);
|
||||
else if (poolBlockedBySolo)
|
||||
ImGui::SetTooltip("Stop solo mining before starting pool mining");
|
||||
ImGui::SetTooltip("%s", TR("mining_stop_solo_for_pool"));
|
||||
else
|
||||
ImGui::SetTooltip(isMiningActive ? "Stop Mining" : "Start Mining");
|
||||
ImGui::SetTooltip("%s", isMiningActive ? TR("stop_mining") : TR("start_mining"));
|
||||
}
|
||||
|
||||
// Click action — pool or solo
|
||||
@@ -1201,23 +1272,15 @@ void RenderMiningTab(App* app)
|
||||
int numStats = 3;
|
||||
|
||||
if (s_pool_mode) {
|
||||
col1Label = "POOL HASHRATE";
|
||||
col1Label = TR("mining_local_hashrate");
|
||||
col1Str = FormatHashrate(state.pool_mining.hashrate_10s);
|
||||
col1Col = state.pool_mining.xmrig_running ? greenCol : OnSurfaceDisabled();
|
||||
|
||||
col2Label = "THREADS / MEM";
|
||||
{
|
||||
char buf[64];
|
||||
int64_t memMB = state.pool_mining.memory_used / (1024 * 1024);
|
||||
if (memMB > 0)
|
||||
snprintf(buf, sizeof(buf), "%d / %lld MB", state.pool_mining.threads_active, (long long)memMB);
|
||||
else
|
||||
snprintf(buf, sizeof(buf), "%d / --", state.pool_mining.threads_active);
|
||||
col2Str = buf;
|
||||
}
|
||||
col2Col = OnSurface();
|
||||
col2Label = TR("mining_pool_hashrate");
|
||||
col2Str = FormatHashrate(state.pool_mining.pool_hashrate);
|
||||
col2Col = state.pool_mining.pool_hashrate > 0 ? OnSurface() : OnSurfaceDisabled();
|
||||
|
||||
col3Label = "SHARES";
|
||||
col3Label = TR("mining_shares");
|
||||
char sharesBuf[64];
|
||||
snprintf(sharesBuf, sizeof(sharesBuf), "%lld / %lld",
|
||||
(long long)state.pool_mining.accepted,
|
||||
@@ -1225,7 +1288,7 @@ void RenderMiningTab(App* app)
|
||||
col3Str = sharesBuf;
|
||||
col3Col = OnSurface();
|
||||
|
||||
col4Label = "UPTIME";
|
||||
col4Label = TR("mining_uptime");
|
||||
int64_t up = state.pool_mining.uptime_sec;
|
||||
char uptBuf[64];
|
||||
if (up <= 0)
|
||||
@@ -1240,15 +1303,15 @@ void RenderMiningTab(App* app)
|
||||
} else {
|
||||
double est_hours = EstimateHoursToBlock(mining.localHashrate, mining.networkHashrate, mining.difficulty);
|
||||
|
||||
col1Label = "LOCAL HASHRATE";
|
||||
col1Label = TR("mining_local_hashrate");
|
||||
col1Str = FormatHashrate(mining.localHashrate);
|
||||
col1Col = mining.generate ? greenCol : OnSurfaceDisabled();
|
||||
|
||||
col2Label = "NETWORK";
|
||||
col2Label = TR("mining_network");
|
||||
col2Str = FormatHashrate(mining.networkHashrate);
|
||||
col2Col = OnSurface();
|
||||
|
||||
col3Label = "EST. BLOCK";
|
||||
col3Label = TR("mining_est_block");
|
||||
col3Str = FormatEstTime(est_hours);
|
||||
col3Col = OnSurface();
|
||||
}
|
||||
@@ -1402,9 +1465,9 @@ void RenderMiningTab(App* app)
|
||||
dl->AddText(capFont, capFont->LegacySize,
|
||||
ImVec2(plotLeft, chartBot - capFont->LegacySize - 2 * dp),
|
||||
OnSurfaceDisabled(),
|
||||
chartHistory.size() >= 300 ? "5m ago" :
|
||||
chartHistory.size() >= 60 ? "1m ago" : "start");
|
||||
std::string nowLbl = "now";
|
||||
chartHistory.size() >= 300 ? TR("mining_chart_5m_ago") :
|
||||
chartHistory.size() >= 60 ? TR("mining_chart_1m_ago") : TR("mining_chart_start"));
|
||||
std::string nowLbl = TR("mining_chart_now");
|
||||
ImVec2 nowSz = capFont->CalcTextSizeA(capFont->LegacySize, 10000, 0, nowLbl.c_str());
|
||||
dl->AddText(capFont, capFont->LegacySize,
|
||||
ImVec2(plotRight - nowSz.x, chartBot - capFont->LegacySize - 2 * dp),
|
||||
@@ -1421,7 +1484,7 @@ void RenderMiningTab(App* app)
|
||||
if (hasLogContent || hasChartContent) {
|
||||
ImFont* iconFont = Type().iconSmall();
|
||||
const char* toggleIcon = showLogFlag ? ICON_MD_SHOW_CHART : ICON_MD_ARTICLE;
|
||||
const char* toggleTip = showLogFlag ? "Show hashrate chart" : "Show mining log";
|
||||
const char* toggleTip = showLogFlag ? TR("mining_show_chart") : TR("mining_show_log");
|
||||
ImVec2 iconSz = iconFont->CalcTextSizeA(iconFont->LegacySize, 1000.0f, 0.0f, toggleIcon);
|
||||
float btnSize = iconSz.y + 8 * dp;
|
||||
float btnX = cardMax.x - pad - btnSize;
|
||||
@@ -1486,6 +1549,10 @@ void RenderMiningTab(App* app)
|
||||
&& !tx.memo.empty()
|
||||
&& tx.memo.find("Mining Pool payout") != std::string::npos);
|
||||
if (isSoloMined || isPoolPayout) {
|
||||
// Apply earnings filter
|
||||
if (s_earnings_filter == 1 && !isSoloMined) continue;
|
||||
if (s_earnings_filter == 2 && !isPoolPayout) continue;
|
||||
|
||||
double amt = std::abs(tx.amount);
|
||||
minedAllTime += amt;
|
||||
minedAllTimeCount++;
|
||||
@@ -1526,6 +1593,63 @@ void RenderMiningTab(App* app)
|
||||
ImVec2 cardMax(cardMin.x + availWidth, cardMin.y + combinedCardH);
|
||||
DrawGlassPanel(dl, cardMin, cardMax, glassSpec);
|
||||
|
||||
// === Earnings filter toggle button (top-right of card) ===
|
||||
{
|
||||
const char* filterLabels[] = { TR("mining_filter_all"), TR("mining_solo"), TR("mining_pool") };
|
||||
const char* filterIcons[] = { ICON_MD_FUNCTIONS, ICON_MD_MEMORY, ICON_MD_CLOUD };
|
||||
const char* curIcon = filterIcons[s_earnings_filter];
|
||||
const char* curLabel = filterLabels[s_earnings_filter];
|
||||
|
||||
ImFont* icoFont = Type().iconSmall();
|
||||
float icoH = icoFont->LegacySize;
|
||||
ImVec2 icoSz = icoFont->CalcTextSizeA(icoFont->LegacySize, FLT_MAX, 0, curIcon);
|
||||
ImVec2 lblSz = ovFont->CalcTextSizeA(ovFont->LegacySize, FLT_MAX, 0, curLabel);
|
||||
float padH = Layout::spacingSm();
|
||||
float btnW = padH + icoSz.x + Layout::spacingXs() + lblSz.x + padH;
|
||||
float btnH = icoH + 8.0f * dp;
|
||||
float btnX = cardMax.x - pad - btnW;
|
||||
float btnY = cardMin.y + (earningsRowH - btnH) * 0.5f;
|
||||
|
||||
ImVec2 bMin(btnX, btnY), bMax(btnX + btnW, btnY + btnH);
|
||||
bool hov = material::IsRectHovered(bMin, bMax);
|
||||
|
||||
// Pill background
|
||||
ImU32 pillBg = s_earnings_filter != 0
|
||||
? WithAlpha(Primary(), 60)
|
||||
: WithAlpha(OnSurface(), hov ? 25 : 12);
|
||||
dl->AddRectFilled(bMin, bMax, pillBg, btnH * 0.5f);
|
||||
|
||||
// Icon
|
||||
ImU32 icoCol = s_earnings_filter != 0 ? Primary() : (hov ? OnSurface() : OnSurfaceDisabled());
|
||||
float cx = bMin.x + padH;
|
||||
float cy = bMin.y + (btnH - icoSz.y) * 0.5f;
|
||||
dl->AddText(icoFont, icoFont->LegacySize, ImVec2(cx, cy), icoCol, curIcon);
|
||||
|
||||
// Label
|
||||
ImU32 lblCol = s_earnings_filter != 0 ? Primary() : (hov ? OnSurface() : OnSurfaceMedium());
|
||||
float lx = cx + icoSz.x + Layout::spacingXs();
|
||||
float ly = bMin.y + (btnH - lblSz.y) * 0.5f;
|
||||
dl->AddText(ovFont, ovFont->LegacySize, ImVec2(lx, ly), lblCol, curLabel);
|
||||
|
||||
// Click target
|
||||
ImVec2 savedCur = ImGui::GetCursorScreenPos();
|
||||
ImGui::SetCursorScreenPos(bMin);
|
||||
ImGui::InvisibleButton("##EarningsFilter", ImVec2(btnW, btnH));
|
||||
if (ImGui::IsItemClicked()) {
|
||||
s_earnings_filter = (s_earnings_filter + 1) % 3;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
||||
const char* tips[] = {
|
||||
TR("mining_filter_tip_all"),
|
||||
TR("mining_filter_tip_solo"),
|
||||
TR("mining_filter_tip_pool")
|
||||
};
|
||||
ImGui::SetTooltip("%s", tips[s_earnings_filter]);
|
||||
}
|
||||
ImGui::SetCursorScreenPos(savedCur);
|
||||
}
|
||||
|
||||
// === Earnings section (top of combined card) ===
|
||||
{
|
||||
const int numCols = 4;
|
||||
@@ -1561,10 +1685,10 @@ void RenderMiningTab(App* app)
|
||||
snprintf(estVal, sizeof(estVal), "N/A");
|
||||
|
||||
EarningsEntry entries[] = {
|
||||
{ "TODAY", todayVal, todaySub, greenCol2 },
|
||||
{ "YESTERDAY", yesterdayVal, yesterdaySub, OnSurface() },
|
||||
{ "ALL TIME", allVal, allSub, OnSurface() },
|
||||
{ "EST. DAILY", estVal, nullptr, estActive ? greenCol2 : OnSurfaceDisabled() },
|
||||
{ TR("mining_today"), todayVal, todaySub, greenCol2 },
|
||||
{ TR("mining_yesterday"), yesterdayVal, yesterdaySub, OnSurface() },
|
||||
{ TR("mining_all_time"), allVal, allSub, OnSurface() },
|
||||
{ TR("mining_est_daily"), estVal, nullptr, estActive ? greenCol2 : OnSurfaceDisabled() },
|
||||
};
|
||||
|
||||
for (int ei = 0; ei < numCols; ei++) {
|
||||
@@ -1619,7 +1743,7 @@ void RenderMiningTab(App* app)
|
||||
float col3X = cx + colW * 2.0f;
|
||||
|
||||
// -- Difficulty --
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(col1X, cy), OnSurfaceMedium(), "Difficulty");
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(col1X, cy), OnSurfaceMedium(), TR("difficulty"));
|
||||
if (mining.difficulty > 0) {
|
||||
snprintf(buf, sizeof(buf), "%.4f", mining.difficulty);
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(col1X + valOffX, cy), OnSurface(), buf);
|
||||
@@ -1628,19 +1752,19 @@ void RenderMiningTab(App* app)
|
||||
ImGui::InvisibleButton("##DiffCopy", ImVec2(diffSz.x + Layout::spacingMd(), capFont->LegacySize + 4 * dp));
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
||||
ImGui::SetTooltip("Click to copy difficulty");
|
||||
ImGui::SetTooltip("%s", TR("mining_click_copy_difficulty"));
|
||||
dl->AddLine(ImVec2(col1X + valOffX, cy + capFont->LegacySize + 1 * dp),
|
||||
ImVec2(col1X + valOffX + diffSz.x, cy + capFont->LegacySize + 1 * dp),
|
||||
WithAlpha(OnSurface(), 60), 1.0f * dp);
|
||||
}
|
||||
if (ImGui::IsItemClicked()) {
|
||||
ImGui::SetClipboardText(buf);
|
||||
Notifications::instance().info("Difficulty copied");
|
||||
Notifications::instance().info(TR("mining_difficulty_copied"));
|
||||
}
|
||||
}
|
||||
|
||||
// -- Block --
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(col2X, cy), OnSurfaceMedium(), "Block");
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(col2X, cy), OnSurfaceMedium(), TR("block"));
|
||||
if (mining.blocks > 0) {
|
||||
snprintf(buf, sizeof(buf), "%d", mining.blocks);
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(col2X + valOffX, cy), OnSurface(), buf);
|
||||
@@ -1649,19 +1773,19 @@ void RenderMiningTab(App* app)
|
||||
ImGui::InvisibleButton("##BlockCopy", ImVec2(blkSz.x + Layout::spacingMd(), capFont->LegacySize + 4 * dp));
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
||||
ImGui::SetTooltip("Click to copy block height");
|
||||
ImGui::SetTooltip("%s", TR("mining_click_copy_block"));
|
||||
dl->AddLine(ImVec2(col2X + valOffX, cy + capFont->LegacySize + 1 * dp),
|
||||
ImVec2(col2X + valOffX + blkSz.x, cy + capFont->LegacySize + 1 * dp),
|
||||
WithAlpha(OnSurface(), 60), 1.0f * dp);
|
||||
}
|
||||
if (ImGui::IsItemClicked()) {
|
||||
ImGui::SetClipboardText(buf);
|
||||
Notifications::instance().info("Block height copied");
|
||||
Notifications::instance().info(TR("mining_block_copied"));
|
||||
}
|
||||
}
|
||||
|
||||
// -- Mining Address --
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(col3X, cy), OnSurfaceMedium(), "Mining Addr");
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(col3X, cy), OnSurfaceMedium(), TR("mining_mining_addr"));
|
||||
std::string mining_address = "";
|
||||
for (const auto& addr : state.addresses) {
|
||||
if (addr.type == "transparent" && !addr.address.empty()) {
|
||||
@@ -1685,14 +1809,14 @@ void RenderMiningTab(App* app)
|
||||
ImGui::InvisibleButton("##MiningAddrCopy", ImVec2(addrTextSz.x + Layout::spacingMd(), capFont->LegacySize + 4 * dp));
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
||||
ImGui::SetTooltip("Click to copy mining address");
|
||||
ImGui::SetTooltip("%s", TR("mining_click_copy_address"));
|
||||
dl->AddLine(ImVec2(col3X + valOffX, cy + capFont->LegacySize + 1 * dp),
|
||||
ImVec2(col3X + valOffX + addrTextSz.x, cy + capFont->LegacySize + 1 * dp),
|
||||
WithAlpha(OnSurface(), 60), 1.0f * dp);
|
||||
}
|
||||
if (ImGui::IsItemClicked()) {
|
||||
ImGui::SetClipboardText(mining_address.c_str());
|
||||
Notifications::instance().info("Mining address copied");
|
||||
Notifications::instance().info(TR("mining_address_copied"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1859,15 +1983,15 @@ void RenderMiningTab(App* app)
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
if (selfRAM >= 1024.0)
|
||||
ImGui::Text("Wallet: %.1f GB", selfRAM / 1024.0);
|
||||
ImGui::Text(TR("ram_wallet_gb"), selfRAM / 1024.0);
|
||||
else
|
||||
ImGui::Text("Wallet: %.0f MB", selfRAM);
|
||||
ImGui::Text(TR("ram_wallet_mb"), selfRAM);
|
||||
if (daemonRAM >= 1024.0)
|
||||
ImGui::Text("Daemon: %.1f GB (%s)", daemonRAM / 1024.0, app->getDaemonMemDiag().c_str());
|
||||
ImGui::Text(TR("ram_daemon_gb"), daemonRAM / 1024.0, app->getDaemonMemDiag().c_str());
|
||||
else
|
||||
ImGui::Text("Daemon: %.0f MB (%s)", daemonRAM, app->getDaemonMemDiag().c_str());
|
||||
ImGui::Text(TR("ram_daemon_mb"), daemonRAM, app->getDaemonMemDiag().c_str());
|
||||
ImGui::Separator();
|
||||
ImGui::Text("System: %.1f / %.0f GB", usedRAM / 1024.0, totalRAM / 1024.0);
|
||||
ImGui::Text(TR("ram_system_gb"), usedRAM / 1024.0, totalRAM / 1024.0);
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
}
|
||||
@@ -1881,7 +2005,7 @@ void RenderMiningTab(App* app)
|
||||
// ============================================================
|
||||
if (!recentMined.empty() || s_pool_mode) {
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(),
|
||||
s_pool_mode ? "RECENT POOL PAYOUTS" : "RECENT BLOCKS");
|
||||
s_pool_mode ? TR("mining_recent_payouts") : TR("mining_recent_blocks"));
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
|
||||
float rowH_blocks = std::max(schema::UI().drawElement("tabs.mining", "recent-row-min-height").size, schema::UI().drawElement("tabs.mining", "recent-row-height").size * vs);
|
||||
@@ -1924,8 +2048,8 @@ void RenderMiningTab(App* app)
|
||||
ImVec2(centerX - iSz.x * 0.5f, emptyY),
|
||||
OnSurfaceDisabled(), emptyIcon);
|
||||
const char* emptyMsg = s_pool_mode
|
||||
? "No pool payouts yet"
|
||||
: "No blocks found yet";
|
||||
? TR("mining_no_payouts_yet")
|
||||
: TR("mining_no_blocks_yet");
|
||||
ImVec2 msgSz = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, emptyMsg);
|
||||
miningChildDL->AddText(capFont, capFont->LegacySize,
|
||||
ImVec2(centerX - msgSz.x * 0.5f, emptyY + iSz.y + Layout::spacingXs()),
|
||||
@@ -1966,13 +2090,13 @@ void RenderMiningTab(App* app)
|
||||
// Time
|
||||
int64_t diff = now - mtx.timestamp;
|
||||
if (diff < 60)
|
||||
snprintf(buf, sizeof(buf), "%llds ago", (long long)diff);
|
||||
snprintf(buf, sizeof(buf), TR("time_seconds_ago"), (long long)diff);
|
||||
else if (diff < 3600)
|
||||
snprintf(buf, sizeof(buf), "%lldm ago", (long long)(diff / 60));
|
||||
snprintf(buf, sizeof(buf), TR("time_minutes_ago"), (long long)(diff / 60));
|
||||
else if (diff < 86400)
|
||||
snprintf(buf, sizeof(buf), "%lldh ago", (long long)(diff / 3600));
|
||||
snprintf(buf, sizeof(buf), TR("time_hours_ago"), (long long)(diff / 3600));
|
||||
else
|
||||
snprintf(buf, sizeof(buf), "%lldd ago", (long long)(diff / 86400));
|
||||
snprintf(buf, sizeof(buf), TR("time_days_ago"), (long long)(diff / 86400));
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(rx + iSz.x + 8 * dp, ry), OnSurfaceDisabled(), buf);
|
||||
|
||||
// Amount
|
||||
@@ -1984,9 +2108,9 @@ void RenderMiningTab(App* app)
|
||||
float badgeX = rMax.x - pad - Layout::spacingXl() * 3.5f;
|
||||
if (mtx.mature) {
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(badgeX, ry),
|
||||
WithAlpha(Success(), 180), "Mature");
|
||||
WithAlpha(Success(), 180), TR("mature"));
|
||||
} else {
|
||||
snprintf(buf, sizeof(buf), "%d conf", mtx.confirmations);
|
||||
snprintf(buf, sizeof(buf), TR("conf_count"), mtx.confirmations);
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(badgeX, ry),
|
||||
WithAlpha(Warning(), 200), buf);
|
||||
}
|
||||
@@ -2001,7 +2125,7 @@ void RenderMiningTab(App* app)
|
||||
dragonx::util::Platform::openUrl(url);
|
||||
}
|
||||
if (ImGui::IsItemHovered() && !mtx.txid.empty()) {
|
||||
ImGui::SetTooltip("Open in explorer");
|
||||
ImGui::SetTooltip("%s", TR("mining_open_in_explorer"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2026,8 +2150,8 @@ void RenderMiningTab(App* app)
|
||||
const char* dotIcon = ICON_MD_CIRCLE;
|
||||
ImU32 dotCol = state.pool_mining.connected ? WithAlpha(Success(), 200) : WithAlpha(Error(), 200);
|
||||
const char* statusText = state.pool_mining.connected
|
||||
? (state.pool_mining.pool_url.empty() ? "Connected" : state.pool_mining.pool_url.c_str())
|
||||
: "Connecting...";
|
||||
? (state.pool_mining.pool_url.empty() ? TR("mining_connected") : state.pool_mining.pool_url.c_str())
|
||||
: TR("mining_connecting");
|
||||
|
||||
ImVec2 pos = ImGui::GetCursorScreenPos();
|
||||
ImVec2 dotSz = iconFont->CalcTextSizeA(iconFont->LegacySize, 1000.0f, 0.0f, dotIcon);
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "peers_tab.h"
|
||||
#include "../../app.h"
|
||||
#include "../../data/wallet_state.h"
|
||||
#include "../../util/i18n.h"
|
||||
#include "../theme.h"
|
||||
#include "../effects/imgui_acrylic.h"
|
||||
#include "../effects/low_spec.h"
|
||||
@@ -148,7 +149,7 @@ void RenderPeersTab(App* app)
|
||||
DrawGlassPanel(dl, cardMin, cardMax, glassSpec);
|
||||
|
||||
// Card header
|
||||
dl->AddText(ovFont, ovFont->LegacySize, ImVec2(cardMin.x + pad, cardMin.y + pad * 0.5f), Primary(), "BLOCKCHAIN");
|
||||
dl->AddText(ovFont, ovFont->LegacySize, ImVec2(cardMin.x + pad, cardMin.y + pad * 0.5f), Primary(), TR("peers_blockchain"));
|
||||
|
||||
float colW = (cardW - pad * 2) / 2.0f;
|
||||
float ry = cardMin.y + pad * 0.5f + headerH;
|
||||
@@ -165,10 +166,11 @@ void RenderPeersTab(App* app)
|
||||
{
|
||||
// Blocks
|
||||
float cx = cardMin.x + pad;
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(cx, ry), OnSurfaceMedium(), "Blocks");
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(cx, ry), OnSurfaceMedium(), TR("peers_blocks"));
|
||||
int blocks = state.sync.blocks;
|
||||
if (blocks > 0) {
|
||||
int blocksLeft = state.sync.headers - blocks;
|
||||
int chainTip = state.longestchain > 0 ? state.longestchain : state.sync.headers;
|
||||
int blocksLeft = chainTip - blocks;
|
||||
if (blocksLeft < 0) blocksLeft = 0;
|
||||
if (blocksLeft > 0) {
|
||||
snprintf(buf, sizeof(buf), "%d (%d left)", blocks, blocksLeft);
|
||||
@@ -180,7 +182,7 @@ void RenderPeersTab(App* app)
|
||||
dl->AddText(sub1, sub1->LegacySize, ImVec2(cx, valY), OnSurface(), blockStr);
|
||||
// Draw "(X left)" in warning color
|
||||
char leftStr[32];
|
||||
snprintf(leftStr, sizeof(leftStr), "(%d left)", blocksLeft);
|
||||
snprintf(leftStr, sizeof(leftStr), TR("peers_blocks_left"), blocksLeft);
|
||||
dl->AddText(capFont, capFont->LegacySize,
|
||||
ImVec2(cx + numSz.x, valY + (sub1->LegacySize - capFont->LegacySize) * 0.5f),
|
||||
Warning(), leftStr);
|
||||
@@ -194,7 +196,7 @@ void RenderPeersTab(App* app)
|
||||
|
||||
// Longest Chain
|
||||
cx = cardMin.x + pad + colW;
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(cx, ry), OnSurfaceMedium(), "Longest Chain");
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(cx, ry), OnSurfaceMedium(), TR("peers_longest_chain"));
|
||||
if (state.longestchain > 0) {
|
||||
snprintf(buf, sizeof(buf), "%d", state.longestchain);
|
||||
int localHeight = mining.blocks > 0 ? mining.blocks : state.sync.blocks;
|
||||
@@ -213,7 +215,7 @@ void RenderPeersTab(App* app)
|
||||
{
|
||||
// Hashrate
|
||||
float cx = cardMin.x + pad;
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(cx, ry), OnSurfaceMedium(), "Hashrate");
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(cx, ry), OnSurfaceMedium(), TR("peers_hashrate"));
|
||||
float valY = ry + capFont->LegacySize + Layout::spacingXs();
|
||||
if (mining.networkHashrate > 0) {
|
||||
if (mining.networkHashrate >= 1e12)
|
||||
@@ -233,7 +235,7 @@ void RenderPeersTab(App* app)
|
||||
|
||||
// Difficulty
|
||||
cx = cardMin.x + pad + colW;
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(cx, ry), OnSurfaceMedium(), "Difficulty");
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(cx, ry), OnSurfaceMedium(), TR("difficulty"));
|
||||
valY = ry + capFont->LegacySize + Layout::spacingXs();
|
||||
if (mining.difficulty > 0) {
|
||||
snprintf(buf, sizeof(buf), "%.4f", mining.difficulty);
|
||||
@@ -251,7 +253,7 @@ void RenderPeersTab(App* app)
|
||||
{
|
||||
// Notarized
|
||||
float cx = cardMin.x + pad;
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(cx, ry), OnSurfaceMedium(), "Notarized");
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(cx, ry), OnSurfaceMedium(), TR("peers_notarized"));
|
||||
float valY = ry + capFont->LegacySize + Layout::spacingXs();
|
||||
if (state.notarized > 0) {
|
||||
snprintf(buf, sizeof(buf), "%d", state.notarized);
|
||||
@@ -262,7 +264,7 @@ void RenderPeersTab(App* app)
|
||||
|
||||
// Protocol
|
||||
cx = cardMin.x + pad + colW;
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(cx, ry), OnSurfaceMedium(), "Protocol");
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(cx, ry), OnSurfaceMedium(), TR("peers_protocol"));
|
||||
valY = ry + capFont->LegacySize + Layout::spacingXs();
|
||||
if (state.protocol_version > 0) {
|
||||
snprintf(buf, sizeof(buf), "%d", state.protocol_version);
|
||||
@@ -280,7 +282,7 @@ void RenderPeersTab(App* app)
|
||||
{
|
||||
// Version
|
||||
float cx = cardMin.x + pad;
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(cx, ry), OnSurfaceMedium(), "Version");
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(cx, ry), OnSurfaceMedium(), TR("peers_version"));
|
||||
float valY = ry + capFont->LegacySize + Layout::spacingXs();
|
||||
if (state.daemon_version > 0) {
|
||||
int major = state.daemon_version / 1000000;
|
||||
@@ -294,7 +296,7 @@ void RenderPeersTab(App* app)
|
||||
|
||||
// Memory
|
||||
cx = cardMin.x + pad + colW;
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(cx, ry), OnSurfaceMedium(), "Memory");
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(cx, ry), OnSurfaceMedium(), TR("peers_memory"));
|
||||
valY = ry + capFont->LegacySize + Layout::spacingXs();
|
||||
double memMb = state.mining.daemon_memory_mb;
|
||||
if (memMb > 0) {
|
||||
@@ -316,7 +318,7 @@ void RenderPeersTab(App* app)
|
||||
{
|
||||
// Longest Chain
|
||||
float cx = cardMin.x + pad;
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(cx, ry), OnSurfaceMedium(), "Longest");
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(cx, ry), OnSurfaceMedium(), TR("peers_longest"));
|
||||
float valY = ry + capFont->LegacySize + Layout::spacingXs();
|
||||
if (state.longestchain > 0) {
|
||||
snprintf(buf, sizeof(buf), "%d", state.longestchain);
|
||||
@@ -330,7 +332,7 @@ void RenderPeersTab(App* app)
|
||||
|
||||
// Best Block (truncated hash)
|
||||
cx = cardMin.x + pad + colW;
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(cx, ry), OnSurfaceMedium(), "Best Block");
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(cx, ry), OnSurfaceMedium(), TR("peers_best_block"));
|
||||
valY = ry + capFont->LegacySize + Layout::spacingXs();
|
||||
if (!state.sync.best_blockhash.empty()) {
|
||||
// Truncate hash to fit: first 6 + "..." + last 6
|
||||
@@ -349,14 +351,14 @@ void RenderPeersTab(App* app)
|
||||
ImGui::InvisibleButton("##BestBlockCopy", ImVec2(hashSz.x + Layout::spacingSm(), sub1->LegacySize + 2 * dp));
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
||||
ImGui::SetTooltip("Click to copy: %s", hash.c_str());
|
||||
ImGui::SetTooltip("%s %s", TR("peers_click_copy"), hash.c_str());
|
||||
dl->AddLine(ImVec2(cx, valY + sub1->LegacySize + 1 * dp),
|
||||
ImVec2(cx + hashSz.x, valY + sub1->LegacySize + 1 * dp),
|
||||
WithAlpha(OnSurface(), 60), 1.0f * dp);
|
||||
}
|
||||
if (ImGui::IsItemClicked()) {
|
||||
ImGui::SetClipboardText(hash.c_str());
|
||||
ui::Notifications::instance().info("Block hash copied");
|
||||
ui::Notifications::instance().info(TR("peers_hash_copied"));
|
||||
}
|
||||
} else {
|
||||
dl->AddText(sub1, sub1->LegacySize, ImVec2(cx, valY), OnSurfaceDisabled(), "\xE2\x80\x94");
|
||||
@@ -373,7 +375,7 @@ void RenderPeersTab(App* app)
|
||||
DrawGlassPanel(dl, cardMin, cardMax, glassSpec);
|
||||
|
||||
// Card header
|
||||
dl->AddText(ovFont, ovFont->LegacySize, ImVec2(cardMin.x + pad, cardMin.y + pad * 0.5f), Primary(), "PEERS");
|
||||
dl->AddText(ovFont, ovFont->LegacySize, ImVec2(cardMin.x + pad, cardMin.y + pad * 0.5f), Primary(), TR("peers_upper"));
|
||||
|
||||
float colW = (cardW - pad * 2) / 2.0f;
|
||||
float ry = cardMin.y + pad * 0.5f + headerH;
|
||||
@@ -390,13 +392,13 @@ void RenderPeersTab(App* app)
|
||||
{
|
||||
// Connected
|
||||
float cx = cardMin.x + pad;
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(cx, ry), OnSurfaceMedium(), "Connected");
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(cx, ry), OnSurfaceMedium(), TR("peers_connected"));
|
||||
snprintf(buf, sizeof(buf), "%d", totalPeers);
|
||||
dl->AddText(sub1, sub1->LegacySize, ImVec2(cx, ry + capFont->LegacySize + Layout::spacingXs()), OnSurface(), buf);
|
||||
|
||||
// In / Out
|
||||
cx = cardMin.x + pad + colW;
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(cx, ry), OnSurfaceMedium(), "In / Out");
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(cx, ry), OnSurfaceMedium(), TR("peers_in_out"));
|
||||
snprintf(buf, sizeof(buf), "%d / %d", inboundCount, outboundCount);
|
||||
dl->AddText(sub1, sub1->LegacySize, ImVec2(cx, ry + capFont->LegacySize + Layout::spacingXs()), OnSurface(), buf);
|
||||
}
|
||||
@@ -426,7 +428,7 @@ void RenderPeersTab(App* app)
|
||||
|
||||
// Avg Ping
|
||||
cx = cardMin.x + pad + colW;
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(cx, ry), OnSurfaceMedium(), "Avg Ping");
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(cx, ry), OnSurfaceMedium(), TR("peers_avg_ping"));
|
||||
ImU32 pingCol;
|
||||
if (avgPing < 100) pingCol = Success();
|
||||
else if (avgPing < 500) pingCol = Warning();
|
||||
@@ -443,13 +445,13 @@ void RenderPeersTab(App* app)
|
||||
{
|
||||
// Received
|
||||
float cx = cardMin.x + pad;
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(cx, ry), OnSurfaceMedium(), "Received");
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(cx, ry), OnSurfaceMedium(), TR("peers_received"));
|
||||
std::string recvStr = fmtBytes(totalBytesRecv);
|
||||
dl->AddText(sub1, sub1->LegacySize, ImVec2(cx, ry + capFont->LegacySize + Layout::spacingXs()), OnSurface(), recvStr.c_str());
|
||||
|
||||
// Sent
|
||||
cx = cardMin.x + pad + colW;
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(cx, ry), OnSurfaceMedium(), "Sent");
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(cx, ry), OnSurfaceMedium(), TR("peers_sent"));
|
||||
std::string sentStr = fmtBytes(totalBytesSent);
|
||||
dl->AddText(sub1, sub1->LegacySize, ImVec2(cx, ry + capFont->LegacySize + Layout::spacingXs()), OnSurface(), sentStr.c_str());
|
||||
}
|
||||
@@ -462,7 +464,7 @@ void RenderPeersTab(App* app)
|
||||
{
|
||||
// P2P Port
|
||||
float cx = cardMin.x + pad;
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(cx, ry), OnSurfaceMedium(), "P2P Port");
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(cx, ry), OnSurfaceMedium(), TR("peers_p2p_port"));
|
||||
float valY = ry + capFont->LegacySize + Layout::spacingXs();
|
||||
if (state.p2p_port > 0) {
|
||||
snprintf(buf, sizeof(buf), "%d", state.p2p_port);
|
||||
@@ -472,7 +474,7 @@ void RenderPeersTab(App* app)
|
||||
}
|
||||
// Banned count
|
||||
cx = cardMin.x + pad + colW;
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(cx, ry), OnSurfaceMedium(), "Banned");
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(cx, ry), OnSurfaceMedium(), TR("peers_banned"));
|
||||
valY = ry + capFont->LegacySize + Layout::spacingXs();
|
||||
size_t bannedCount = state.bannedPeers.size();
|
||||
snprintf(buf, sizeof(buf), "%zu", bannedCount);
|
||||
@@ -488,7 +490,7 @@ void RenderPeersTab(App* app)
|
||||
// Compute remaining space for peer list + footer
|
||||
// ================================================================
|
||||
float footerH = ImGui::GetFrameHeight() + Layout::spacingSm();
|
||||
float toggleH = body2->LegacySize + Layout::spacingMd() * 2;
|
||||
float toggleH = body2->LegacySize + Layout::spacingMd() * 2 + Layout::spacingSm();
|
||||
float remainForPeers = std::max(60.0f, peersAvail.y - (ImGui::GetCursorScreenPos().y - ImGui::GetWindowPos().y) - footerH - Layout::spacingSm());
|
||||
float peerPanelHeight = remainForPeers - toggleH;
|
||||
peerPanelHeight = std::max(S.drawElement("tabs.peers", "peer-panel-min-height").size, peerPanelHeight);
|
||||
@@ -502,8 +504,8 @@ void RenderPeersTab(App* app)
|
||||
float toggleY = ImGui::GetCursorScreenPos().y;
|
||||
{
|
||||
char connLabel[64], banLabel[64];
|
||||
snprintf(connLabel, sizeof(connLabel), "Connected (%zu)", state.peers.size());
|
||||
snprintf(banLabel, sizeof(banLabel), "Banned (%zu)", state.bannedPeers.size());
|
||||
snprintf(connLabel, sizeof(connLabel), TR("peers_connected_count"), state.peers.size());
|
||||
snprintf(banLabel, sizeof(banLabel), TR("peers_banned_count"), state.bannedPeers.size());
|
||||
|
||||
ImVec2 connSz = body2->CalcTextSizeA(body2->LegacySize, FLT_MAX, 0, connLabel);
|
||||
ImVec2 banSz = body2->CalcTextSizeA(body2->LegacySize, FLT_MAX, 0, banLabel);
|
||||
@@ -544,9 +546,14 @@ void RenderPeersTab(App* app)
|
||||
// Refresh button — top-right, glass panel style (similar to mining button)
|
||||
{
|
||||
bool isRefreshing = app->isPeerRefreshInProgress();
|
||||
auto refreshBtn = S.drawElement("tabs.peers", "refresh-button");
|
||||
float btnW = refreshBtn.size;
|
||||
float btnH = toggleH - 4.0f * Layout::dpiScale();
|
||||
ImFont* iconFont = Type().iconMed();
|
||||
float iconSz = iconFont->LegacySize;
|
||||
const char* label = isRefreshing ? TR("peers_refreshing") : TR("peers_refresh");
|
||||
ImVec2 lblSz = ovFont->CalcTextSizeA(ovFont->LegacySize, FLT_MAX, 0, label);
|
||||
float padH = Layout::spacingSm();
|
||||
float padV = Layout::spacingSm();
|
||||
float btnW = padH + iconSz + Layout::spacingXs() + lblSz.x + padH;
|
||||
float btnH = std::max(iconSz, lblSz.y) + padV * 2;
|
||||
float btnX = ImGui::GetWindowPos().x + availWidth - btnW - Layout::spacingSm();
|
||||
float btnY = toggleY + (toggleH - btnH) * 0.5f;
|
||||
ImVec2 bMin(btnX, btnY);
|
||||
@@ -574,10 +581,8 @@ void RenderPeersTab(App* app)
|
||||
}
|
||||
|
||||
// Icon: spinner while refreshing, refresh icon otherwise
|
||||
float cx = bMin.x + btnW * 0.35f;
|
||||
float cx = bMin.x + padH + iconSz * 0.5f;
|
||||
float cy = bMin.y + btnH * 0.5f;
|
||||
ImFont* iconFont = Type().iconMed();
|
||||
float iconSz = iconFont->LegacySize;
|
||||
|
||||
if (isRefreshing) {
|
||||
// Spinning arc spinner (same style as mining toggle)
|
||||
@@ -617,7 +622,6 @@ void RenderPeersTab(App* app)
|
||||
|
||||
// Label to the right of icon
|
||||
{
|
||||
const char* label = isRefreshing ? "REFRESHING" : "REFRESH";
|
||||
ImU32 lblCol;
|
||||
if (isRefreshing) {
|
||||
float pulse = effects::isLowSpecMode()
|
||||
@@ -627,7 +631,6 @@ void RenderPeersTab(App* app)
|
||||
} else {
|
||||
lblCol = btnHovered ? OnSurface() : WithAlpha(OnSurface(), 160);
|
||||
}
|
||||
ImVec2 lblSz = ovFont->CalcTextSizeA(ovFont->LegacySize, FLT_MAX, 0, label);
|
||||
float lblX = cx + iconSz * 0.5f + Layout::spacingXs();
|
||||
float lblY = cy - lblSz.y * 0.5f;
|
||||
dl->AddText(ovFont, ovFont->LegacySize, ImVec2(lblX, lblY), lblCol, label);
|
||||
@@ -645,7 +648,7 @@ void RenderPeersTab(App* app)
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
||||
if (!isRefreshing)
|
||||
ImGui::SetTooltip("Refresh peers & blockchain");
|
||||
ImGui::SetTooltip("%s", TR("peers_refresh_tooltip"));
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
@@ -673,10 +676,10 @@ void RenderPeersTab(App* app)
|
||||
// ---- Connected Peers ----
|
||||
if (!app->isConnected()) {
|
||||
ImGui::Dummy(ImVec2(0, 20));
|
||||
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), " Not connected to daemon...");
|
||||
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), TR("not_connected"));
|
||||
} else if (state.peers.empty()) {
|
||||
ImGui::Dummy(ImVec2(0, 20));
|
||||
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), " No connected peers");
|
||||
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), TR("peers_no_connected"));
|
||||
} else {
|
||||
float rowH = body2->LegacySize + capFont->LegacySize + Layout::spacingLg();
|
||||
float rowInset = Layout::spacingLg();
|
||||
@@ -727,7 +730,7 @@ void RenderPeersTab(App* app)
|
||||
}
|
||||
|
||||
{
|
||||
const char* dirLabel = peer.inbound ? "In" : "Out";
|
||||
const char* dirLabel = peer.inbound ? TR("peers_dir_in") : TR("peers_dir_out");
|
||||
ImU32 dirBg = peer.inbound ? WithAlpha(Success(), 30) : WithAlpha(Secondary(), 30);
|
||||
ImU32 dirFg = peer.inbound ? WithAlpha(Success(), 200) : WithAlpha(Secondary(), 200);
|
||||
ImVec2 dirSz = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, dirLabel);
|
||||
@@ -761,12 +764,12 @@ void RenderPeersTab(App* app)
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(tlsMin.x + 4, cy2 + 1), tlsFg, "TLS");
|
||||
} else {
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(cx + S.drawElement("tabs.peers", "address-x-offset").size + verW + Layout::spacingSm(), cy2),
|
||||
WithAlpha(Error(), 140), "No TLS");
|
||||
WithAlpha(Error(), 140), TR("peers_no_tls"));
|
||||
}
|
||||
|
||||
if (peer.banscore > 0) {
|
||||
char banBuf[16];
|
||||
snprintf(banBuf, sizeof(banBuf), "Ban: %d", peer.banscore);
|
||||
snprintf(banBuf, sizeof(banBuf), TR("peers_ban_score"), peer.banscore);
|
||||
ImU32 banCol = peer.banscore > 50 ? Error() : Warning();
|
||||
ImVec2 banSz = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, banBuf);
|
||||
dl->AddText(capFont, capFont->LegacySize,
|
||||
@@ -780,16 +783,16 @@ void RenderPeersTab(App* app)
|
||||
|
||||
const auto& acrylicTheme = GetCurrentAcrylicTheme();
|
||||
if (effects::ImGuiAcrylic::BeginAcrylicContextItem(nullptr, 0, acrylicTheme.menu)) {
|
||||
ImGui::Text("Peer: %s", peer.addr.c_str());
|
||||
ImGui::Text(TR("peers_peer_label"), peer.addr.c_str());
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem("Copy Address")) {
|
||||
if (ImGui::MenuItem(TR("copy_address"))) {
|
||||
ImGui::SetClipboardText(peer.addr.c_str());
|
||||
}
|
||||
if (ImGui::MenuItem("Copy IP")) {
|
||||
if (ImGui::MenuItem(TR("peers_copy_ip"))) {
|
||||
ImGui::SetClipboardText(ExtractIP(peer.addr).c_str());
|
||||
}
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem("Ban Peer (24h)")) {
|
||||
if (ImGui::MenuItem(TR("peers_ban_24h"))) {
|
||||
app->banPeer(ExtractIP(peer.addr), 86400);
|
||||
}
|
||||
effects::ImGuiAcrylic::EndAcrylicPopup();
|
||||
@@ -808,18 +811,18 @@ void RenderPeersTab(App* app)
|
||||
};
|
||||
char ttBuf[128];
|
||||
snprintf(ttBuf, sizeof(ttBuf), "%d", peer.id);
|
||||
TTRow("ID", ttBuf);
|
||||
TTRow("Services", peer.services.c_str());
|
||||
TTRow(TR("peers_tt_id"), ttBuf);
|
||||
TTRow(TR("peers_tt_services"), peer.services.c_str());
|
||||
snprintf(ttBuf, sizeof(ttBuf), "%d", peer.startingheight);
|
||||
TTRow("Start Height", ttBuf);
|
||||
TTRow(TR("peers_tt_start_height"), ttBuf);
|
||||
snprintf(ttBuf, sizeof(ttBuf), "%ld bytes", peer.bytessent);
|
||||
TTRow("Sent", ttBuf);
|
||||
TTRow(TR("peers_tt_sent"), ttBuf);
|
||||
snprintf(ttBuf, sizeof(ttBuf), "%ld bytes", peer.bytesrecv);
|
||||
TTRow("Received", ttBuf);
|
||||
TTRow(TR("peers_tt_received"), ttBuf);
|
||||
snprintf(ttBuf, sizeof(ttBuf), "%d / %d", peer.synced_headers, peer.synced_blocks);
|
||||
TTRow("Synced H/B", ttBuf);
|
||||
TTRow(TR("peers_tt_synced"), ttBuf);
|
||||
if (!peer.tls_cipher.empty())
|
||||
TTRow("TLS Cipher", peer.tls_cipher.c_str());
|
||||
TTRow(TR("peers_tt_tls_cipher"), peer.tls_cipher.c_str());
|
||||
ImGui::EndTable();
|
||||
}
|
||||
ImGui::PopStyleVar();
|
||||
@@ -840,10 +843,10 @@ void RenderPeersTab(App* app)
|
||||
// ---- Banned Peers ----
|
||||
if (!app->isConnected()) {
|
||||
ImGui::Dummy(ImVec2(0, 20));
|
||||
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), " Not connected to daemon...");
|
||||
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), TR("not_connected"));
|
||||
} else if (state.bannedPeers.empty()) {
|
||||
ImGui::Dummy(ImVec2(0, 20));
|
||||
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), " No banned peers");
|
||||
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), TR("peers_no_banned"));
|
||||
} else {
|
||||
float rowH = capFont->LegacySize + S.drawElement("tabs.peers", "banned-row-height-padding").size;
|
||||
float rowInsetB = pad;
|
||||
@@ -886,7 +889,7 @@ void RenderPeersTab(App* app)
|
||||
|
||||
float btnX = rowPos.x + innerW - Layout::spacingXl() * S.drawElement("tabs.peers", "unban-btn-right-offset-multiplier").size;
|
||||
ImGui::SetCursorScreenPos(ImVec2(btnX, cy - 1));
|
||||
if (TactileSmallButton("Unban", S.resolveFont("button"))) {
|
||||
if (TactileSmallButton(TR("peers_unban"), S.resolveFont("button"))) {
|
||||
app->unbanPeer(banned.address);
|
||||
}
|
||||
|
||||
@@ -898,10 +901,10 @@ void RenderPeersTab(App* app)
|
||||
|
||||
const auto& acrylicTheme2 = GetCurrentAcrylicTheme();
|
||||
if (effects::ImGuiAcrylic::BeginAcrylicContextItem(nullptr, 0, acrylicTheme2.menu)) {
|
||||
if (ImGui::MenuItem("Copy Address")) {
|
||||
if (ImGui::MenuItem(TR("copy_address"))) {
|
||||
ImGui::SetClipboardText(banned.address.c_str());
|
||||
}
|
||||
if (ImGui::MenuItem("Unban")) {
|
||||
if (ImGui::MenuItem(TR("peers_unban"))) {
|
||||
app->unbanPeer(banned.address);
|
||||
}
|
||||
effects::ImGuiAcrylic::EndAcrylicPopup();
|
||||
@@ -940,7 +943,7 @@ void RenderPeersTab(App* app)
|
||||
// ================================================================
|
||||
if (s_show_banned && !state.bannedPeers.empty()) {
|
||||
ImGui::BeginDisabled(!app->isConnected());
|
||||
if (TactileSmallButton("Clear All Bans", S.resolveFont("button"))) {
|
||||
if (TactileSmallButton(TR("peers_clear_all_bans"), S.resolveFont("button"))) {
|
||||
app->clearBans();
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "qr_popup_dialog.h"
|
||||
#include "../../app.h"
|
||||
#include "../../util/i18n.h"
|
||||
#include "../widgets/qr_code.h"
|
||||
#include "../schema/ui_schema.h"
|
||||
#include "../material/draw_helpers.h"
|
||||
@@ -63,7 +64,7 @@ void QRPopupDialog::render(App* app)
|
||||
auto addrInput = S.input("dialogs.qr-popup", "address-input");
|
||||
auto actionBtn = S.button("dialogs.qr-popup", "action-button");
|
||||
|
||||
if (material::BeginOverlayDialog("QR Code", &s_open, win.width, 0.94f)) {
|
||||
if (material::BeginOverlayDialog(TR("qr_title"), &s_open, win.width, 0.94f)) {
|
||||
|
||||
// Label if present
|
||||
if (!s_label.empty()) {
|
||||
@@ -86,7 +87,7 @@ void QRPopupDialog::render(App* app)
|
||||
} else {
|
||||
// Fallback: show error
|
||||
ImGui::BeginChild("QRPlaceholder", ImVec2(qr_size, qr_size), true);
|
||||
ImGui::TextWrapped("Failed to generate QR code");
|
||||
ImGui::TextWrapped("%s", TR("qr_failed"));
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
@@ -95,7 +96,7 @@ void QRPopupDialog::render(App* app)
|
||||
ImGui::Spacing();
|
||||
|
||||
// Address display
|
||||
ImGui::Text("Address:");
|
||||
ImGui::Text("%s", TR("address_label"));
|
||||
|
||||
// Use multiline for z-addresses
|
||||
if (s_address.length() > 50) {
|
||||
@@ -120,13 +121,13 @@ void QRPopupDialog::render(App* app)
|
||||
float start_x = (window_width - total_width) / 2.0f;
|
||||
ImGui::SetCursorPosX(start_x);
|
||||
|
||||
if (material::StyledButton("Copy Address", ImVec2(button_width, 0), S.resolveFont(actionBtn.font))) {
|
||||
if (material::StyledButton(TR("copy_address"), ImVec2(button_width, 0), S.resolveFont(actionBtn.font))) {
|
||||
ImGui::SetClipboardText(s_address.c_str());
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (material::StyledButton("Close", ImVec2(button_width, 0), S.resolveFont(actionBtn.font))) {
|
||||
if (material::StyledButton(TR("close"), ImVec2(button_width, 0), S.resolveFont(actionBtn.font))) {
|
||||
close();
|
||||
}
|
||||
material::EndOverlayDialog();
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "receive_tab.h"
|
||||
#include "send_tab.h"
|
||||
#include "../../app.h"
|
||||
#include "../../util/i18n.h"
|
||||
#include "../../config/version.h"
|
||||
#include "../../data/wallet_state.h"
|
||||
#include "../../ui/widgets/qr_code.h"
|
||||
@@ -35,6 +36,10 @@ namespace ui {
|
||||
|
||||
using namespace material;
|
||||
|
||||
static std::string TrId(const char* key, const char* id) {
|
||||
return std::string(TR(key)) + "##" + id;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// State
|
||||
// ============================================================================
|
||||
@@ -110,7 +115,7 @@ static void RenderSyncBanner(const WalletState& state) {
|
||||
? (float)state.sync.blocks / state.sync.headers * 100.0f : 0.0f;
|
||||
char syncBuf[128];
|
||||
snprintf(syncBuf, sizeof(syncBuf),
|
||||
"Blockchain syncing (%.1f%%)... Balances may be inaccurate.", syncPct);
|
||||
TR("blockchain_syncing"), syncPct);
|
||||
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImGui::ColorConvertU32ToFloat4(schema::UI().resolveColor(schema::UI().drawElement("tabs.receive", "sync-banner-bg-color").color)));
|
||||
float syncH = std::max(schema::UI().drawElement("tabs.receive", "sync-banner-min-height").size, schema::UI().drawElement("tabs.receive", "sync-banner-height").size * Layout::vScale());
|
||||
ImGui::BeginChild("##SyncBannerRecv", ImVec2(ImGui::GetContentRegionAvail().x, syncH),
|
||||
@@ -129,13 +134,13 @@ static void RenderAddressDropdown(App* app, float width) {
|
||||
char buf[256];
|
||||
|
||||
// Header row: label + address type toggle buttons
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), "ADDRESS");
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("address_upper"));
|
||||
|
||||
float toggleBtnW = std::max(schema::UI().drawElement("tabs.receive", "toggle-btn-min-width").size, schema::UI().drawElement("tabs.receive", "toggle-btn-width").size * Layout::hScale(width));
|
||||
float toggleGap = schema::UI().drawElement("tabs.receive", "toggle-gap").size;
|
||||
float toggleTotalW = toggleBtnW * 3 + toggleGap * 2;
|
||||
ImGui::SameLine(width - toggleTotalW);
|
||||
const char* filterLabels[] = { "All", "Z", "T" };
|
||||
const char* filterLabels[] = { TR("all_filter"), "Z", "T" };
|
||||
for (int i = 0; i < 3; i++) {
|
||||
bool isActive = (s_addr_type_filter == i);
|
||||
if (isActive) {
|
||||
@@ -190,7 +195,7 @@ static void RenderAddressDropdown(App* app, float width) {
|
||||
|
||||
// Build preview string
|
||||
if (!app->isConnected()) {
|
||||
s_source_preview = "Not connected to daemon";
|
||||
s_source_preview = TR("not_connected");
|
||||
} else if (s_selected_address_idx >= 0 &&
|
||||
s_selected_address_idx < (int)state.addresses.size()) {
|
||||
const auto& addr = state.addresses[s_selected_address_idx];
|
||||
@@ -202,7 +207,7 @@ static void RenderAddressDropdown(App* app, float width) {
|
||||
tag, trunc.c_str(), addr.balance, DRAGONX_TICKER);
|
||||
s_source_preview = buf;
|
||||
} else {
|
||||
s_source_preview = "Select a receiving address...";
|
||||
s_source_preview = TR("select_receiving_address");
|
||||
}
|
||||
|
||||
float copyBtnW = std::max(schema::UI().drawElement("tabs.receive", "copy-btn-min-width").size, schema::UI().drawElement("tabs.receive", "copy-btn-width").size * Layout::hScale(width));
|
||||
@@ -212,7 +217,7 @@ static void RenderAddressDropdown(App* app, float width) {
|
||||
ImGui::PushFont(Type().getFont(TypeStyle::Body2));
|
||||
if (ImGui::BeginCombo("##RecvAddr", s_source_preview.c_str())) {
|
||||
if (!app->isConnected() || state.addresses.empty()) {
|
||||
ImGui::TextDisabled("No addresses available");
|
||||
ImGui::TextDisabled("%s", TR("no_addresses_available"));
|
||||
} else {
|
||||
// Build filtered and sorted list
|
||||
std::vector<size_t> sortedIdx;
|
||||
@@ -229,7 +234,7 @@ static void RenderAddressDropdown(App* app, float width) {
|
||||
});
|
||||
|
||||
if (sortedIdx.empty()) {
|
||||
ImGui::TextDisabled("No addresses match filter");
|
||||
ImGui::TextDisabled("%s", TR("no_addresses_match"));
|
||||
} else {
|
||||
size_t addrTruncLen = static_cast<size_t>(std::max(schema::UI().drawElement("tabs.receive", "addr-dropdown-trunc-min").size, width / schema::UI().drawElement("tabs.receive", "addr-dropdown-trunc-divisor").size));
|
||||
double now = ImGui::GetTime();
|
||||
@@ -287,10 +292,10 @@ static void RenderAddressDropdown(App* app, float width) {
|
||||
ImGui::SameLine(0, Layout::spacingSm());
|
||||
ImGui::BeginDisabled(!app->isConnected() || s_selected_address_idx < 0 ||
|
||||
s_selected_address_idx >= (int)state.addresses.size());
|
||||
if (TactileButton("Copy##recvAddr", ImVec2(copyBtnW, 0), schema::UI().resolveFont("button"))) {
|
||||
if (TactileButton(TrId("copy", "recvAddr").c_str(), ImVec2(copyBtnW, 0), schema::UI().resolveFont("button"))) {
|
||||
if (s_selected_address_idx >= 0 && s_selected_address_idx < (int)state.addresses.size()) {
|
||||
ImGui::SetClipboardText(state.addresses[s_selected_address_idx].address.c_str());
|
||||
Notifications::instance().info("Address copied to clipboard");
|
||||
Notifications::instance().info(TR("address_copied"));
|
||||
}
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
@@ -298,23 +303,23 @@ static void RenderAddressDropdown(App* app, float width) {
|
||||
// New address button on same line
|
||||
ImGui::SameLine(0, Layout::spacingSm());
|
||||
ImGui::BeginDisabled(!app->isConnected());
|
||||
if (TactileButton("+ New##recv", ImVec2(newBtnW, 0), schema::UI().resolveFont("button"))) {
|
||||
if (TactileButton(TrId("new", "recv").c_str(), ImVec2(newBtnW, 0), schema::UI().resolveFont("button"))) {
|
||||
if (s_addr_type_filter != 2) {
|
||||
app->createNewZAddress([](const std::string& addr) {
|
||||
if (addr.empty())
|
||||
Notifications::instance().error("Failed to create new shielded address");
|
||||
Notifications::instance().error(TR("failed_create_shielded"));
|
||||
else {
|
||||
s_pending_select_address = addr;
|
||||
Notifications::instance().success("New shielded address created");
|
||||
Notifications::instance().success(TR("new_shielded_created"));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
app->createNewTAddress([](const std::string& addr) {
|
||||
if (addr.empty())
|
||||
Notifications::instance().error("Failed to create new transparent address");
|
||||
Notifications::instance().error(TR("failed_create_transparent"));
|
||||
else {
|
||||
s_pending_select_address = addr;
|
||||
Notifications::instance().success("New transparent address created");
|
||||
Notifications::instance().success(TR("new_transparent_created"));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -330,10 +335,11 @@ static std::string recvTimeAgo(int64_t timestamp) {
|
||||
int64_t now = (int64_t)std::time(nullptr);
|
||||
int64_t diff = now - timestamp;
|
||||
if (diff < 0) diff = 0;
|
||||
if (diff < 60) return std::to_string(diff) + "s ago";
|
||||
if (diff < 3600) return std::to_string(diff / 60) + "m ago";
|
||||
if (diff < 86400) return std::to_string(diff / 3600) + "h ago";
|
||||
return std::to_string(diff / 86400) + "d ago";
|
||||
char buf[32];
|
||||
if (diff < 60) { snprintf(buf, sizeof(buf), TR("time_seconds_ago"), (long long)diff); return buf; }
|
||||
if (diff < 3600) { snprintf(buf, sizeof(buf), TR("time_minutes_ago"), (long long)(diff / 60)); return buf; }
|
||||
if (diff < 86400) { snprintf(buf, sizeof(buf), TR("time_hours_ago"), (long long)(diff / 3600)); return buf; }
|
||||
snprintf(buf, sizeof(buf), TR("time_days_ago"), (long long)(diff / 86400)); return buf;
|
||||
}
|
||||
|
||||
static void DrawRecvIcon(ImDrawList* dl, float cx, float cy, float s, ImU32 col) {
|
||||
@@ -353,7 +359,7 @@ static void RenderRecentReceived(ImDrawList* dl, const AddressInfo& /* addr */,
|
||||
const WalletState& state, float width,
|
||||
ImFont* capFont, App* app) {
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingLg()));
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), "RECENT RECEIVED");
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("recent_received"));
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
|
||||
float hs = Layout::hScale(width);
|
||||
@@ -375,7 +381,7 @@ static void RenderRecentReceived(ImDrawList* dl, const AddressInfo& /* addr */,
|
||||
}
|
||||
|
||||
if (recvs.empty()) {
|
||||
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), "No recent receives");
|
||||
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), TR("no_recent_receives"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -416,11 +422,11 @@ static void RenderRecentReceived(ImDrawList* dl, const AddressInfo& /* addr */,
|
||||
|
||||
// Type label (first line)
|
||||
float labelX = cx + iconSz * 2.0f + Layout::spacingSm();
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(labelX, cy), recvCol, "Received");
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(labelX, cy), recvCol, TR("received_label"));
|
||||
|
||||
// Time (next to type)
|
||||
std::string ago = recvTimeAgo(tx.timestamp);
|
||||
float typeW = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, "Received").x;
|
||||
float typeW = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, TR("received_label")).x;
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(labelX + typeW + Layout::spacingLg(), cy),
|
||||
OnSurfaceDisabled(), ago.c_str());
|
||||
|
||||
@@ -457,12 +463,12 @@ static void RenderRecentReceived(ImDrawList* dl, const AddressInfo& /* addr */,
|
||||
const char* statusStr;
|
||||
ImU32 statusCol;
|
||||
if (tx.confirmations == 0) {
|
||||
statusStr = "Pending"; statusCol = Warning();
|
||||
statusStr = TR("pending"); statusCol = Warning();
|
||||
} else if (tx.confirmations < (int)schema::UI().drawElement("tabs.receive", "confirmed-threshold").size) {
|
||||
snprintf(buf, sizeof(buf), "%d conf", tx.confirmations);
|
||||
snprintf(buf, sizeof(buf), TR("conf_count"), tx.confirmations);
|
||||
statusStr = buf; statusCol = Warning();
|
||||
} else {
|
||||
statusStr = "Confirmed"; statusCol = greenCol;
|
||||
statusStr = TR("confirmed"); statusCol = greenCol;
|
||||
}
|
||||
ImVec2 sSz = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, statusStr);
|
||||
float statusX = amtX - sSz.x - Layout::spacingXxl();
|
||||
@@ -545,10 +551,10 @@ void RenderReceiveTab(App* app)
|
||||
DrawGlassPanel(dl, emptyMin, emptyMax, glassSpec);
|
||||
dl->AddText(sub1, sub1->LegacySize,
|
||||
ImVec2(emptyMin.x + Layout::spacingXl(), emptyMin.y + Layout::spacingXl()),
|
||||
OnSurfaceDisabled(), "Waiting for daemon connection...");
|
||||
OnSurfaceDisabled(), TR("waiting_for_daemon"));
|
||||
dl->AddText(capFont, capFont->LegacySize,
|
||||
ImVec2(emptyMin.x + Layout::spacingXl(), emptyMin.y + Layout::spacingXl() + sub1->LegacySize + S.drawElement("tabs.receive", "empty-state-subtitle-gap").size),
|
||||
OnSurfaceDisabled(), "Your receiving addresses will appear here once connected.");
|
||||
OnSurfaceDisabled(), TR("addresses_appear_here"));
|
||||
ImGui::Dummy(ImVec2(formW, emptyH));
|
||||
ImGui::EndGroup();
|
||||
ImGui::EndChild();
|
||||
@@ -572,7 +578,7 @@ void RenderReceiveTab(App* app)
|
||||
skelCol, schema::UI().drawElement("tabs.receive", "skeleton-rounding").size);
|
||||
dl->AddText(capFont, capFont->LegacySize,
|
||||
ImVec2(emptyMin.x + Layout::spacingLg(), emptyMin.y + emptyH - S.drawElement("tabs.receive", "skeleton-text-bottom-offset").size),
|
||||
OnSurfaceDisabled(), "Loading addresses...");
|
||||
OnSurfaceDisabled(), TR("loading_addresses"));
|
||||
ImGui::Dummy(ImVec2(formW, emptyH));
|
||||
ImGui::EndGroup();
|
||||
ImGui::EndChild();
|
||||
@@ -639,7 +645,7 @@ void RenderReceiveTab(App* app)
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingMd()));
|
||||
|
||||
// ---- PAYMENT REQUEST ----
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), "PAYMENT REQUEST");
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("payment_request"));
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
|
||||
// Amount input with currency toggle
|
||||
@@ -805,7 +811,7 @@ void RenderReceiveTab(App* app)
|
||||
// Memo (z-addresses only)
|
||||
if (isZ) {
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), "MEMO (OPTIONAL)");
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("memo_optional"));
|
||||
ImGui::Dummy(ImVec2(0, S.drawElement("tabs.receive", "memo-label-gap").size));
|
||||
|
||||
float memoInputH = std::max(schema::UI().drawElement("tabs.receive", "memo-input-min-height").size, schema::UI().drawElement("tabs.receive", "memo-input-height").size * vScale);
|
||||
@@ -841,7 +847,7 @@ void RenderReceiveTab(App* app)
|
||||
ImGui::PushStyleColor(ImGuiCol_Text,
|
||||
ImGui::ColorConvertU32ToFloat4(hasData ? OnSurfaceMedium() : OnSurfaceDisabled()));
|
||||
ImGui::BeginDisabled(!hasData);
|
||||
if (TactileSmallButton("Clear Request##recv", S.resolveFont("button"))) {
|
||||
if (TactileSmallButton(TrId("clear_request", "recv").c_str(), S.resolveFont("button"))) {
|
||||
s_request_amount = 0.0;
|
||||
s_request_usd_amount = 0.0;
|
||||
s_request_memo[0] = '\0';
|
||||
@@ -880,20 +886,20 @@ void RenderReceiveTab(App* app)
|
||||
ImVec2 textPos(qrPanelMin.x + totalQrSize * 0.5f - S.drawElement("tabs.receive", "qr-unavailable-text-offset").size,
|
||||
qrPanelMin.y + totalQrSize * 0.5f);
|
||||
dl->AddText(capFont, capFont->LegacySize, textPos,
|
||||
OnSurfaceDisabled(), "QR unavailable");
|
||||
OnSurfaceDisabled(), TR("qr_unavailable"));
|
||||
}
|
||||
|
||||
ImGui::SetCursorScreenPos(qrPanelMin);
|
||||
ImGui::InvisibleButton("##QRClickCopy", ImVec2(totalQrSize, totalQrSize));
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
||||
ImGui::SetTooltip("Click to copy %s",
|
||||
s_request_amount > 0 ? "payment URI" : "address");
|
||||
ImGui::SetTooltip("%s",
|
||||
s_request_amount > 0 ? TR("click_copy_uri") : TR("click_copy_address"));
|
||||
}
|
||||
if (ImGui::IsItemClicked()) {
|
||||
ImGui::SetClipboardText(qr_data.c_str());
|
||||
Notifications::instance().info(s_request_amount > 0
|
||||
? "Payment URI copied" : "Address copied");
|
||||
? TR("payment_uri_copied") : TR("address_copied"));
|
||||
}
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(rx, qrPanelMax.y));
|
||||
@@ -926,9 +932,9 @@ void RenderReceiveTab(App* app)
|
||||
if (s_request_amount > 0) {
|
||||
if (!firstBtn) ImGui::SameLine(0, btnGap);
|
||||
firstBtn = false;
|
||||
if (TactileButton("Copy URI##recv", ImVec2(otherBtnW, btnH), S.resolveFont("button"))) {
|
||||
if (TactileButton(TrId("copy_uri", "recv").c_str(), ImVec2(otherBtnW, btnH), S.resolveFont("button"))) {
|
||||
ImGui::SetClipboardText(s_cached_qr_data.c_str());
|
||||
Notifications::instance().info("Payment URI copied");
|
||||
Notifications::instance().info(TR("payment_uri_copied"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -939,14 +945,16 @@ void RenderReceiveTab(App* app)
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered,
|
||||
ImGui::ColorConvertU32ToFloat4(IM_COL32(255, 255, 255, (int)S.drawElement("tabs.receive", "btn-hover-alpha").size)));
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(PrimaryLight()));
|
||||
if (TactileButton("Share##recv", ImVec2(otherBtnW, btnH), S.resolveFont("button"))) {
|
||||
if (TactileButton(TrId("share", "recv").c_str(), ImVec2(otherBtnW, btnH), S.resolveFont("button"))) {
|
||||
char shareBuf[1024];
|
||||
snprintf(shareBuf, sizeof(shareBuf),
|
||||
"Payment Request\nAmount: %.8f %s\nAddress: %s\nURI: %s",
|
||||
"%s\n%s: %.8f %s\n%s: %s\nURI: %s",
|
||||
TR("payment_request"), TR("amount"),
|
||||
s_request_amount, DRAGONX_TICKER,
|
||||
selected.address.c_str(), s_cached_qr_data.c_str());
|
||||
TR("address"), selected.address.c_str(),
|
||||
s_cached_qr_data.c_str());
|
||||
ImGui::SetClipboardText(shareBuf);
|
||||
Notifications::instance().info("Payment request copied");
|
||||
Notifications::instance().info(TR("payment_request_copied"));
|
||||
}
|
||||
ImGui::PopStyleColor(3);
|
||||
}
|
||||
@@ -959,7 +967,7 @@ void RenderReceiveTab(App* app)
|
||||
ImGui::PushStyleColor(ImGuiCol_Border, ImGui::ColorConvertU32ToFloat4(OnSurfaceDisabled()));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, S.drawElement("tabs.receive", "explorer-btn-border-size").size);
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(PrimaryLight()));
|
||||
if (TactileButton("Explorer##recv", ImVec2(otherBtnW, btnH), S.resolveFont("button"))) {
|
||||
if (TactileButton(TrId("explorer", "recv").c_str(), ImVec2(otherBtnW, btnH), S.resolveFont("button"))) {
|
||||
OpenExplorerURL(selected.address);
|
||||
}
|
||||
ImGui::PopStyleVar(); // FrameBorderSize
|
||||
@@ -972,7 +980,7 @@ void RenderReceiveTab(App* app)
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered,
|
||||
ImGui::ColorConvertU32ToFloat4(IM_COL32(255, 255, 255, (int)S.drawElement("tabs.receive", "btn-hover-alpha").size)));
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(OnSurfaceMedium()));
|
||||
if (TactileButton("Send \xe2\x86\x97##recv", ImVec2(otherBtnW, btnH), S.resolveFont("button"))) {
|
||||
if (TactileButton(TrId("send", "recv").c_str(), ImVec2(otherBtnW, btnH), S.resolveFont("button"))) {
|
||||
SetSendFromAddress(selected.address);
|
||||
app->setCurrentPage(NavPage::Send);
|
||||
}
|
||||
@@ -1001,7 +1009,7 @@ void RenderReceiveTab(App* app)
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(containerMin.x, containerMax.y));
|
||||
ImGui::Dummy(ImVec2(formW, 0));
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingMd()));
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
|
||||
@@ -128,22 +128,19 @@ void RequestPaymentDialog::render(App* app)
|
||||
auto qr = S.drawElement("dialogs.request-payment", "qr-code");
|
||||
auto actionBtn = S.button("dialogs.request-payment", "action-button");
|
||||
|
||||
if (material::BeginOverlayDialog("Request Payment", &s_open, win.width, 0.94f)) {
|
||||
if (material::BeginOverlayDialog(TR("request_title"), &s_open, win.width, 0.94f)) {
|
||||
const auto& state = app->getWalletState();
|
||||
|
||||
ImGui::TextWrapped(
|
||||
"Generate a payment request that others can scan or copy. "
|
||||
"The QR code contains your address and optional amount/memo."
|
||||
);
|
||||
ImGui::TextWrapped("%s", TR("request_description"));
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
// Address selection
|
||||
ImGui::Text("Receive Address:");
|
||||
ImGui::Text("%s", TR("request_receive_address"));
|
||||
|
||||
std::string addr_display = s_address[0] ? s_address : "Select address...";
|
||||
std::string addr_display = s_address[0] ? s_address : TR("request_select_address");
|
||||
if (addr_display.length() > static_cast<size_t>(zAddrLbl.truncate)) {
|
||||
addr_display = addr_display.substr(0, zAddrFrontLbl.truncate) + "..." + addr_display.substr(addr_display.length() - zAddrBackLbl.truncate);
|
||||
}
|
||||
@@ -152,7 +149,7 @@ void RequestPaymentDialog::render(App* app)
|
||||
if (ImGui::BeginCombo("##Address", addr_display.c_str())) {
|
||||
// Z-addresses
|
||||
if (!state.z_addresses.empty()) {
|
||||
ImGui::TextDisabled("-- Shielded Addresses --");
|
||||
ImGui::TextDisabled("%s", TR("request_shielded_addrs"));
|
||||
for (size_t i = 0; i < state.z_addresses.size(); i++) {
|
||||
const auto& addr = state.z_addresses[i];
|
||||
std::string label = addr.address;
|
||||
@@ -169,7 +166,7 @@ void RequestPaymentDialog::render(App* app)
|
||||
|
||||
// T-addresses
|
||||
if (!state.t_addresses.empty()) {
|
||||
ImGui::TextDisabled("-- Transparent Addresses --");
|
||||
ImGui::TextDisabled("%s", TR("request_transparent_addrs"));
|
||||
for (size_t i = 0; i < state.t_addresses.size(); i++) {
|
||||
const auto& addr = state.t_addresses[i];
|
||||
std::string label = addr.address;
|
||||
@@ -189,7 +186,7 @@ void RequestPaymentDialog::render(App* app)
|
||||
ImGui::Spacing();
|
||||
|
||||
// Amount (optional)
|
||||
ImGui::Text("Amount (optional):");
|
||||
ImGui::Text("%s", TR("request_amount"));
|
||||
ImGui::SetNextItemWidth(amountInput.width);
|
||||
if (ImGui::InputDouble("##Amount", &s_amount, 0.1, 1.0, "%.8f")) {
|
||||
s_uri_dirty = true;
|
||||
@@ -200,7 +197,7 @@ void RequestPaymentDialog::render(App* app)
|
||||
ImGui::Spacing();
|
||||
|
||||
// Label (optional)
|
||||
ImGui::Text("Label (optional):");
|
||||
ImGui::Text("%s", TR("request_label"));
|
||||
ImGui::SetNextItemWidth(-1);
|
||||
if (ImGui::InputText("##Label", s_label, sizeof(s_label))) {
|
||||
s_uri_dirty = true;
|
||||
@@ -211,7 +208,7 @@ void RequestPaymentDialog::render(App* app)
|
||||
// Memo (optional, only for z-addr)
|
||||
bool is_zaddr = (s_address[0] == 'z');
|
||||
if (is_zaddr) {
|
||||
ImGui::Text("Memo (optional):");
|
||||
ImGui::Text("%s", TR("request_memo"));
|
||||
ImGui::SetNextItemWidth(-1);
|
||||
if (ImGui::InputTextMultiline("##Memo", s_memo, sizeof(s_memo), ImVec2(-1, memoInput.height > 0 ? memoInput.height : 60))) {
|
||||
s_uri_dirty = true;
|
||||
@@ -245,7 +242,7 @@ void RequestPaymentDialog::render(App* app)
|
||||
|
||||
// Payment URI display
|
||||
if (!s_payment_uri.empty()) {
|
||||
ImGui::Text("Payment URI:");
|
||||
ImGui::Text("%s", TR("request_payment_uri"));
|
||||
ImGui::SetNextItemWidth(-1);
|
||||
|
||||
// Use a selectable text area for the URI
|
||||
@@ -256,23 +253,23 @@ void RequestPaymentDialog::render(App* app)
|
||||
ImGui::Spacing();
|
||||
|
||||
// Copy button
|
||||
if (material::StyledButton("Copy URI", ImVec2(actionBtn.width, 0), S.resolveFont(actionBtn.font))) {
|
||||
if (material::StyledButton(TR("request_copy_uri"), ImVec2(actionBtn.width, 0), S.resolveFont(actionBtn.font))) {
|
||||
ImGui::SetClipboardText(s_payment_uri.c_str());
|
||||
Notifications::instance().success("Payment URI copied to clipboard");
|
||||
Notifications::instance().success(TR("request_uri_copied"));
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (material::StyledButton("Copy Address", ImVec2(actionBtn.width, 0), S.resolveFont(actionBtn.font))) {
|
||||
if (material::StyledButton(TR("copy_address"), ImVec2(actionBtn.width, 0), S.resolveFont(actionBtn.font))) {
|
||||
ImGui::SetClipboardText(s_address);
|
||||
Notifications::instance().success("Address copied to clipboard");
|
||||
Notifications::instance().success(TR("address_copied"));
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
// Close button
|
||||
if (material::StyledButton("Close", ImVec2(actionBtn.width, 0), S.resolveFont(actionBtn.font))) {
|
||||
if (material::StyledButton(TR("close"), ImVec2(actionBtn.width, 0), S.resolveFont(actionBtn.font))) {
|
||||
s_open = false;
|
||||
}
|
||||
material::EndOverlayDialog();
|
||||
|
||||
@@ -214,7 +214,7 @@ static void RenderSourceDropdown(App* app, float width) {
|
||||
auto& S = schema::UI();
|
||||
char buf[256];
|
||||
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), "SENDING FROM");
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("send_sending_from"));
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
|
||||
// Auto-select the address with the largest balance on first load
|
||||
@@ -237,7 +237,7 @@ static void RenderSourceDropdown(App* app, float width) {
|
||||
|
||||
// Build preview string for selected address
|
||||
if (!app->isConnected()) {
|
||||
s_source_preview = "Not connected to daemon";
|
||||
s_source_preview = TR("not_connected");
|
||||
} else if (s_selected_from_idx >= 0 &&
|
||||
s_selected_from_idx < (int)state.addresses.size()) {
|
||||
const auto& addr = state.addresses[s_selected_from_idx];
|
||||
@@ -249,7 +249,7 @@ static void RenderSourceDropdown(App* app, float width) {
|
||||
tag, trunc.c_str(), addr.balance, DRAGONX_TICKER);
|
||||
s_source_preview = buf;
|
||||
} else {
|
||||
s_source_preview = "Select a source address...";
|
||||
s_source_preview = TR("send_select_source");
|
||||
}
|
||||
|
||||
ImGui::SetNextItemWidth(width);
|
||||
@@ -257,7 +257,7 @@ static void RenderSourceDropdown(App* app, float width) {
|
||||
ImGui::PushFont(Type().getFont(TypeStyle::Body2));
|
||||
if (ImGui::BeginCombo("##SendFrom", s_source_preview.c_str())) {
|
||||
if (!app->isConnected() || state.addresses.empty()) {
|
||||
ImGui::TextDisabled("No addresses available");
|
||||
ImGui::TextDisabled("%s", TR("no_addresses_available"));
|
||||
} else {
|
||||
// Sort by balance descending, only show addresses with balance
|
||||
std::vector<size_t> sortedIdx;
|
||||
@@ -272,7 +272,7 @@ static void RenderSourceDropdown(App* app, float width) {
|
||||
});
|
||||
|
||||
if (sortedIdx.empty()) {
|
||||
ImGui::TextDisabled("No addresses with balance");
|
||||
ImGui::TextDisabled("%s", TR("send_no_balance"));
|
||||
} else {
|
||||
size_t addrTruncLen = static_cast<size_t>(std::max(S.drawElement("tabs.send", "addr-dropdown-trunc-min").size, width / S.drawElement("tabs.send", "addr-dropdown-trunc-divisor").size));
|
||||
|
||||
@@ -352,7 +352,7 @@ static void RenderAddressSuggestions(const WalletState& state, float width, cons
|
||||
// ============================================================================
|
||||
static void RenderFeeTierSelector(const char* suffix = "") {
|
||||
auto& S = schema::UI();
|
||||
const char* feeLabels[] = { "Low", "Normal", "High" };
|
||||
const char* feeLabels[] = { TR("send_fee_low"), TR("send_fee_normal"), TR("send_fee_high") };
|
||||
const double feeValues[] = { FEE_LOW, FEE_NORMAL, FEE_HIGH };
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
|
||||
@@ -569,12 +569,12 @@ static void RenderTxProgress(ImDrawList* dl, float x, float y, float w,
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImGui::ColorConvertU32ToFloat4(IM_COL32(255, 255, 255, (int)schema::UI().drawElement("tabs.send", "error-btn-bg-alpha").size)));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::ColorConvertU32ToFloat4(IM_COL32(255, 255, 255, (int)schema::UI().drawElement("tabs.send", "error-btn-hover-alpha").size)));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, schema::UI().drawElement("tabs.send", "error-btn-rounding").size);
|
||||
if (TactileSmallButton("Copy Error##txErr", schema::UI().resolveFont("button"))) {
|
||||
if (TactileSmallButton(TR("send_copy_error"), schema::UI().resolveFont("button"))) {
|
||||
ImGui::SetClipboardText(s_tx_status.c_str());
|
||||
Notifications::instance().info("Error copied to clipboard");
|
||||
Notifications::instance().info(TR("send_error_copied"));
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (TactileSmallButton("Dismiss##txErr", schema::UI().resolveFont("button"))) {
|
||||
if (TactileSmallButton(TR("send_dismiss"), schema::UI().resolveFont("button"))) {
|
||||
s_tx_status.clear();
|
||||
s_result_txid.clear();
|
||||
s_status_success = false;
|
||||
@@ -615,7 +615,7 @@ static void RenderTxProgress(ImDrawList* dl, float x, float y, float w,
|
||||
dl->AddText(iconFont, iconFont->LegacySize,
|
||||
ImVec2(ix, iy), Primary(), spinIcon);
|
||||
double elapsed = ImGui::GetTime() - s_send_start_time;
|
||||
snprintf(buf, sizeof(buf), "Submitting transaction... (%.0fs)", elapsed);
|
||||
snprintf(buf, sizeof(buf), TR("send_submitting"), elapsed);
|
||||
dl->AddText(body2, body2->LegacySize, ImVec2(ix + iSz.x + schema::UI().drawElement("tabs.send", "progress-icon-text-gap").size, iy), OnSurface(), buf);
|
||||
} else {
|
||||
// Success checkmark
|
||||
@@ -624,7 +624,7 @@ static void RenderTxProgress(ImDrawList* dl, float x, float y, float w,
|
||||
ImVec2 iSz = iconFont->CalcTextSizeA(iconFont->LegacySize, 1000.0f, 0.0f, checkIcon);
|
||||
dl->AddText(iconFont, iconFont->LegacySize,
|
||||
ImVec2(ix, iy), Success(), checkIcon);
|
||||
dl->AddText(body2, body2->LegacySize, ImVec2(ix + iSz.x + schema::UI().drawElement("tabs.send", "progress-icon-text-gap").size, iy), Success(), "Transaction sent!");
|
||||
dl->AddText(body2, body2->LegacySize, ImVec2(ix + iSz.x + schema::UI().drawElement("tabs.send", "progress-icon-text-gap").size, iy), Success(), TR("send_tx_sent"));
|
||||
|
||||
if (!s_result_txid.empty()) {
|
||||
float txY = iy + body2->LegacySize + schema::UI().drawElement("tabs.send", "txid-y-offset").size;
|
||||
@@ -633,13 +633,13 @@ static void RenderTxProgress(ImDrawList* dl, float x, float y, float w,
|
||||
std::string dispTxid = (int)s_result_txid.length() > txidThreshold
|
||||
? s_result_txid.substr(0, txidTruncLen) + "..." + s_result_txid.substr(s_result_txid.length() - txidTruncLen)
|
||||
: s_result_txid;
|
||||
snprintf(buf, sizeof(buf), "TxID: %s", dispTxid.c_str());
|
||||
snprintf(buf, sizeof(buf), TR("send_txid_label"), dispTxid.c_str());
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(ix + schema::UI().drawElement("tabs.send", "txid-label-x-offset").size, txY),
|
||||
OnSurfaceDisabled(), buf);
|
||||
ImGui::SetCursorScreenPos(ImVec2(pMax.x - schema::UI().drawElement("tabs.send", "txid-copy-btn-right-offset").size, txY - schema::UI().drawElement("tabs.send", "txid-copy-btn-y-offset").size));
|
||||
if (TactileSmallButton("Copy##TxID", schema::UI().resolveFont("button"))) {
|
||||
if (TactileSmallButton(TR("copy"), schema::UI().resolveFont("button"))) {
|
||||
ImGui::SetClipboardText(s_result_txid.c_str());
|
||||
Notifications::instance().info("TxID copied to clipboard");
|
||||
Notifications::instance().info(TR("send_txid_copied"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -688,7 +688,7 @@ void RenderSendConfirmPopup(App* app) {
|
||||
|
||||
// FROM card
|
||||
{
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), "FROM");
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("from_upper"));
|
||||
ImVec2 cMin = ImGui::GetCursorScreenPos();
|
||||
ImVec2 cMax(cMin.x + popW, cMin.y + addrCardH);
|
||||
GlassPanelSpec gs; gs.rounding = popGlassRound;
|
||||
@@ -702,7 +702,7 @@ void RenderSendConfirmPopup(App* app) {
|
||||
|
||||
// TO card
|
||||
{
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), "TO");
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("to_upper"));
|
||||
ImVec2 cMin = ImGui::GetCursorScreenPos();
|
||||
ImVec2 cMax(cMin.x + popW, cMin.y + addrCardH);
|
||||
GlassPanelSpec gs; gs.rounding = popGlassRound;
|
||||
@@ -716,7 +716,7 @@ void RenderSendConfirmPopup(App* app) {
|
||||
|
||||
// Fee tier selector
|
||||
{
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), "NETWORK FEE");
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("send_network_fee"));
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
RenderFeeTierSelector("##confirm");
|
||||
// Recalculate total after potential fee change
|
||||
@@ -729,7 +729,7 @@ void RenderSendConfirmPopup(App* app) {
|
||||
float valX = std::max(schema::UI().drawElement("tabs.send", "confirm-val-col-min-x").size, schema::UI().drawElement("tabs.send", "confirm-val-col-x").size * popVs);
|
||||
float usdX = popW - std::max(schema::UI().drawElement("tabs.send", "confirm-usd-col-min-x").size, schema::UI().drawElement("tabs.send", "confirm-usd-col-x").size * popVs);
|
||||
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), "AMOUNT DETAILS");
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("send_amount_details"));
|
||||
ImVec2 cMin = ImGui::GetCursorScreenPos();
|
||||
float cH = std::max(schema::UI().drawElement("tabs.send", "confirm-amount-card-min-height").size, schema::UI().drawElement("tabs.send", "confirm-amount-card-height").size * popVs);
|
||||
ImVec2 cMax(cMin.x + popW, cMin.y + cH);
|
||||
@@ -740,7 +740,7 @@ void RenderSendConfirmPopup(App* app) {
|
||||
float cy = cMin.y + Layout::spacingSm() + Layout::spacingXs();
|
||||
float rowStep = std::max(schema::UI().drawElement("tabs.send", "confirm-row-step-min").size, schema::UI().drawElement("tabs.send", "confirm-row-step").size * popVs);
|
||||
|
||||
popDl->AddText(capFont, capFont->LegacySize, ImVec2(cx, cy), OnSurfaceMedium(), "Amount");
|
||||
popDl->AddText(capFont, capFont->LegacySize, ImVec2(cx, cy), OnSurfaceMedium(), TR("send_amount"));
|
||||
snprintf(buf, sizeof(buf), "%.8f %s", s_amount, DRAGONX_TICKER);
|
||||
popDl->AddText(capFont, capFont->LegacySize, ImVec2(cx + valX, cy), OnSurface(), buf);
|
||||
if (market.price_usd > 0) {
|
||||
@@ -749,7 +749,7 @@ void RenderSendConfirmPopup(App* app) {
|
||||
}
|
||||
cy += rowStep;
|
||||
|
||||
popDl->AddText(capFont, capFont->LegacySize, ImVec2(cx, cy), OnSurfaceMedium(), "Fee");
|
||||
popDl->AddText(capFont, capFont->LegacySize, ImVec2(cx, cy), OnSurfaceMedium(), TR("send_fee"));
|
||||
snprintf(buf, sizeof(buf), "%.8f %s", s_fee, DRAGONX_TICKER);
|
||||
popDl->AddText(capFont, capFont->LegacySize, ImVec2(cx + valX, cy), OnSurface(), buf);
|
||||
if (market.price_usd > 0) {
|
||||
@@ -762,7 +762,7 @@ void RenderSendConfirmPopup(App* app) {
|
||||
ImGui::GetColorU32(Divider()), S.drawElement("tabs.send", "confirm-divider-thickness").size);
|
||||
cy += rowStep;
|
||||
|
||||
popDl->AddText(sub1, sub1->LegacySize, ImVec2(cx, cy), OnSurfaceMedium(), "Total");
|
||||
popDl->AddText(sub1, sub1->LegacySize, ImVec2(cx, cy), OnSurfaceMedium(), TR("send_total"));
|
||||
snprintf(buf, sizeof(buf), "%.8f %s", total, DRAGONX_TICKER);
|
||||
DrawTextShadow(popDl, sub1, sub1->LegacySize, ImVec2(cx + valX, cy), Primary(), buf);
|
||||
if (market.price_usd > 0) {
|
||||
@@ -774,7 +774,7 @@ void RenderSendConfirmPopup(App* app) {
|
||||
}
|
||||
|
||||
if (s_memo[0] != '\0' && is_valid_z) {
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), "MEMO");
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("memo_upper"));
|
||||
Type().textColored(TypeStyle::Caption, OnSurface(), s_memo);
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
}
|
||||
@@ -782,7 +782,7 @@ void RenderSendConfirmPopup(App* app) {
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
|
||||
if (s_sending) {
|
||||
Type().text(TypeStyle::Body2, "Sending...");
|
||||
Type().text(TypeStyle::Body2, TR("sending"));
|
||||
} else {
|
||||
if (TactileButton(TR("confirm_and_send"), ImVec2(S.button("tabs.send", "confirm-button").width, std::max(schema::UI().drawElement("tabs.send", "confirm-btn-min-height").size, schema::UI().drawElement("tabs.send", "confirm-btn-base-height").size * popVs)), S.resolveFont(S.button("tabs.send", "confirm-button").font))) {
|
||||
s_sending = true;
|
||||
@@ -801,26 +801,26 @@ void RenderSendConfirmPopup(App* app) {
|
||||
s_sending = false;
|
||||
s_status_timestamp = ImGui::GetTime();
|
||||
if (success) {
|
||||
s_tx_status = "Transaction sent!";
|
||||
s_tx_status = TR("send_tx_sent");
|
||||
s_result_txid = result;
|
||||
s_status_success = true;
|
||||
Notifications::instance().success("Transaction sent successfully!");
|
||||
Notifications::instance().success(TR("send_tx_success"));
|
||||
s_to_address[0] = '\0';
|
||||
s_amount = 0.0;
|
||||
s_memo[0] = '\0';
|
||||
s_send_max = false;
|
||||
} else {
|
||||
s_tx_status = "Error: " + result;
|
||||
s_tx_status = std::string(TR("send_error_prefix")) + result;
|
||||
s_result_txid.clear();
|
||||
s_status_success = false;
|
||||
Notifications::instance().error("Transaction failed: " + result);
|
||||
Notifications::instance().error(std::string(TR("send_tx_failed")) + result);
|
||||
}
|
||||
}
|
||||
);
|
||||
s_show_confirm = false;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (TactileButton("Cancel", ImVec2(S.button("tabs.send", "cancel-button").width, std::max(schema::UI().drawElement("tabs.send", "confirm-btn-min-height").size, schema::UI().drawElement("tabs.send", "confirm-btn-base-height").size * popVs)), S.resolveFont(S.button("tabs.send", "cancel-button").font))) {
|
||||
if (TactileButton(TR("cancel"), ImVec2(S.button("tabs.send", "cancel-button").width, std::max(schema::UI().drawElement("tabs.send", "confirm-btn-min-height").size, schema::UI().drawElement("tabs.send", "confirm-btn-base-height").size * popVs)), S.resolveFont(S.button("tabs.send", "cancel-button").font))) {
|
||||
s_show_confirm = false;
|
||||
}
|
||||
}
|
||||
@@ -851,13 +851,13 @@ static bool RenderZeroBalanceCTA(App* app, ImDrawList* dl, float width) {
|
||||
|
||||
float cx = ctaMin.x + Layout::spacingXl();
|
||||
float cy = ctaMin.y + Layout::spacingLg();
|
||||
dl->AddText(sub1, sub1->LegacySize, ImVec2(cx, cy), OnSurface(), "Your wallet is empty");
|
||||
dl->AddText(sub1, sub1->LegacySize, ImVec2(cx, cy), OnSurface(), TR("send_wallet_empty"));
|
||||
cy += sub1->LegacySize + Layout::spacingSm();
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(cx, cy), OnSurfaceMedium(),
|
||||
"Switch to Receive to get your address and start receiving funds.");
|
||||
TR("send_switch_to_receive"));
|
||||
cy += capFont->LegacySize + Layout::spacingMd();
|
||||
ImGui::SetCursorScreenPos(ImVec2(cx, cy));
|
||||
if (TactileButton("Go to Receive", ImVec2(schema::UI().drawElement("tabs.send", "cta-button-width").size, schema::UI().drawElement("tabs.send", "cta-button-height").size), schema::UI().resolveFont("button"))) {
|
||||
if (TactileButton(TR("send_go_to_receive"), ImVec2(schema::UI().drawElement("tabs.send", "cta-button-width").size, schema::UI().drawElement("tabs.send", "cta-button-height").size), schema::UI().resolveFont("button"))) {
|
||||
app->setCurrentPage(NavPage::Receive);
|
||||
}
|
||||
ImGui::SetCursorScreenPos(ImVec2(ctaMin.x, ctaMax.y + Layout::spacingLg()));
|
||||
@@ -904,19 +904,19 @@ static void RenderActionButtons(App* app, float width, float vScale,
|
||||
|
||||
if (!can_send && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
|
||||
if (!app->isConnected())
|
||||
ImGui::SetTooltip("Not connected to daemon");
|
||||
ImGui::SetTooltip("%s", TR("send_tooltip_not_connected"));
|
||||
else if (state.sync.syncing)
|
||||
ImGui::SetTooltip("Blockchain is still syncing");
|
||||
ImGui::SetTooltip("%s", TR("send_tooltip_syncing"));
|
||||
else if (s_from_address[0] == '\0')
|
||||
ImGui::SetTooltip("Select a source address above");
|
||||
ImGui::SetTooltip("%s", TR("send_tooltip_select_source"));
|
||||
else if (!is_valid_address)
|
||||
ImGui::SetTooltip("Enter a valid recipient address");
|
||||
ImGui::SetTooltip("%s", TR("send_tooltip_invalid_address"));
|
||||
else if (s_amount <= 0)
|
||||
ImGui::SetTooltip("Enter an amount to send");
|
||||
ImGui::SetTooltip("%s", TR("send_tooltip_enter_amount"));
|
||||
else if (total > available)
|
||||
ImGui::SetTooltip("Amount exceeds available balance");
|
||||
ImGui::SetTooltip("%s", TR("send_tooltip_exceeds_balance"));
|
||||
else if (s_sending)
|
||||
ImGui::SetTooltip("Transaction in progress...");
|
||||
ImGui::SetTooltip("%s", TR("send_tooltip_in_progress"));
|
||||
}
|
||||
if (!can_send) ImGui::PopStyleColor(3);
|
||||
|
||||
@@ -946,14 +946,14 @@ static void RenderActionButtons(App* app, float width, float vScale,
|
||||
s_clear_confirm_pending = false;
|
||||
}
|
||||
if (ImGui::BeginPopup(confirmClearId)) {
|
||||
ImGui::Text("Clear all form fields?");
|
||||
ImGui::Text("%s", TR("send_clear_fields"));
|
||||
ImGui::Spacing();
|
||||
if (TactileButton("Yes, Clear", ImVec2(schema::UI().drawElement("tabs.send", "clear-confirm-yes-width").size, 0), S.resolveFont("button"))) {
|
||||
if (TactileButton(TR("send_yes_clear"), ImVec2(schema::UI().drawElement("tabs.send", "clear-confirm-yes-width").size, 0), S.resolveFont("button"))) {
|
||||
ClearFormWithUndo();
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (TactileButton("Keep", ImVec2(schema::UI().drawElement("tabs.send", "clear-confirm-keep-width").size, 0), S.resolveFont("button"))) {
|
||||
if (TactileButton(TR("send_keep"), ImVec2(schema::UI().drawElement("tabs.send", "clear-confirm-keep-width").size, 0), S.resolveFont("button"))) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
@@ -972,7 +972,7 @@ static void RenderActionButtons(App* app, float width, float vScale,
|
||||
snprintf(undoId, sizeof(undoId), "Undo Clear%s", suffix);
|
||||
if (TactileButton(undoId, ImVec2(width, btnH), S.resolveFont("button"))) {
|
||||
RestoreFormSnapshot();
|
||||
Notifications::instance().info("Form restored");
|
||||
Notifications::instance().info(TR("send_form_restored"));
|
||||
}
|
||||
ImGui::PopStyleColor(2);
|
||||
ImGui::PopStyleVar();
|
||||
@@ -989,7 +989,7 @@ static void RenderRecentSends(ImDrawList* dl, const WalletState& state,
|
||||
float width, ImFont* capFont, App* app) {
|
||||
auto& S = schema::UI();
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingLg()));
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), "RECENT SENDS");
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("send_recent_sends"));
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
|
||||
ImVec2 avail = ImGui::GetContentRegionAvail();
|
||||
@@ -1012,7 +1012,7 @@ static void RenderRecentSends(ImDrawList* dl, const WalletState& state,
|
||||
}
|
||||
|
||||
if (sends.empty()) {
|
||||
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), "No recent sends");
|
||||
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), TR("send_no_recent"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1055,11 +1055,11 @@ static void RenderRecentSends(ImDrawList* dl, const WalletState& state,
|
||||
|
||||
// Type label (first line)
|
||||
float labelX = cx + iconSz * 2.0f + Layout::spacingSm();
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(labelX, cy), sendCol, "Sent");
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(labelX, cy), sendCol, TR("sent_upper"));
|
||||
|
||||
// Time (next to type)
|
||||
std::string ago = timeAgo(tx.timestamp);
|
||||
float typeW = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, "Sent").x;
|
||||
float typeW = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, TR("sent_upper")).x;
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(labelX + typeW + Layout::spacingLg(), cy),
|
||||
OnSurfaceDisabled(), ago.c_str());
|
||||
|
||||
@@ -1096,12 +1096,12 @@ static void RenderRecentSends(ImDrawList* dl, const WalletState& state,
|
||||
const char* statusStr;
|
||||
ImU32 statusCol;
|
||||
if (tx.confirmations == 0) {
|
||||
statusStr = "Pending"; statusCol = Warning();
|
||||
statusStr = TR("pending"); statusCol = Warning();
|
||||
} else if (tx.confirmations < (int)S.drawElement("tabs.send", "confirmed-threshold").size) {
|
||||
snprintf(buf, sizeof(buf), "%d conf", tx.confirmations);
|
||||
snprintf(buf, sizeof(buf), TR("conf_count"), tx.confirmations);
|
||||
statusStr = buf; statusCol = Warning();
|
||||
} else {
|
||||
statusStr = "Confirmed"; statusCol = greenCol;
|
||||
statusStr = TR("confirmed"); statusCol = greenCol;
|
||||
}
|
||||
ImVec2 sSz = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, statusStr);
|
||||
float statusX = amtX - sSz.x - Layout::spacingXxl();
|
||||
@@ -1238,7 +1238,7 @@ void RenderSendTab(App* app)
|
||||
static bool s_paste_previewing = false;
|
||||
static std::string s_preview_text;
|
||||
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), "RECIPIENT");
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("send_recipient"));
|
||||
|
||||
// Validation indicator — inline next to title (no height change)
|
||||
// Check the preview text during hover, otherwise check actual address
|
||||
@@ -1254,9 +1254,9 @@ void RenderSendTab(App* app)
|
||||
if (vz || vt) {
|
||||
ImGui::SameLine();
|
||||
if (vz)
|
||||
Type().textColored(TypeStyle::Caption, Success(), "Valid shielded address");
|
||||
Type().textColored(TypeStyle::Caption, Success(), TR("send_valid_shielded"));
|
||||
else
|
||||
Type().textColored(TypeStyle::Caption, Warning(), "Valid transparent address");
|
||||
Type().textColored(TypeStyle::Caption, Warning(), TR("send_valid_transparent"));
|
||||
} else if (!checkPreview) {
|
||||
ImGui::SameLine();
|
||||
Type().textColored(TypeStyle::Caption, Error(), TR("invalid_address"));
|
||||
@@ -1341,7 +1341,7 @@ void RenderSendTab(App* app)
|
||||
|
||||
// ---- AMOUNT ----
|
||||
{
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), "AMOUNT");
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("send_amount_upper"));
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
|
||||
// Toggle between DRGX and USD input
|
||||
@@ -1439,7 +1439,7 @@ void RenderSendTab(App* app)
|
||||
|
||||
// Amount error
|
||||
if (s_amount > 0 && s_amount + s_fee > available && available > 0) {
|
||||
snprintf(buf, sizeof(buf), "Exceeds available (%.8f)", available - s_fee);
|
||||
snprintf(buf, sizeof(buf), TR("send_exceeds_available"), available - s_fee);
|
||||
Type().textColored(TypeStyle::Caption, Error(), buf);
|
||||
}
|
||||
}
|
||||
@@ -1455,7 +1455,7 @@ void RenderSendTab(App* app)
|
||||
}
|
||||
ImGui::Dummy(ImVec2(0, innerGap * 0.5f));
|
||||
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), "MEMO (OPTIONAL)");
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("memo_optional"));
|
||||
ImGui::Dummy(ImVec2(0, S.drawElement("tabs.send", "memo-label-gap").size));
|
||||
|
||||
float memoInputH = std::max(S.drawElement("tabs.send", "memo-min-height").size, S.drawElement("tabs.send", "memo-base-height").size * vScale);
|
||||
@@ -1502,7 +1502,7 @@ void RenderSendTab(App* app)
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(containerMin.x, containerMax.y));
|
||||
ImGui::Dummy(ImVec2(formW, 0));
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingMd()));
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
|
||||
// Pass card bottom Y so error overlay can anchor to it
|
||||
float cardBottom = containerMax.y;
|
||||
|
||||
@@ -27,6 +27,14 @@
|
||||
namespace dragonx {
|
||||
namespace ui {
|
||||
|
||||
// Helper: build "TranslatedLabel##id" for ImGui widgets that use label as ID
|
||||
static std::string TrId(const char* tr_key, const char* id) {
|
||||
std::string s = TR(tr_key);
|
||||
s += "##";
|
||||
s += id;
|
||||
return s;
|
||||
}
|
||||
|
||||
// Settings state - these get loaded from Settings on window open
|
||||
static bool s_initialized = false;
|
||||
static int s_language_index = 0;
|
||||
@@ -141,17 +149,17 @@ void RenderSettingsWindow(App* app, bool* p_open)
|
||||
auto saveBtn = S.button("dialogs.settings", "save-button");
|
||||
auto cancelBtn = S.button("dialogs.settings", "cancel-button");
|
||||
|
||||
if (!material::BeginOverlayDialog("Settings", p_open, win.width, 0.94f)) {
|
||||
if (!material::BeginOverlayDialog(TR("settings"), p_open, win.width, 0.94f)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ImGui::BeginTabBar("SettingsTabs")) {
|
||||
// General settings tab
|
||||
if (ImGui::BeginTabItem("General")) {
|
||||
if (ImGui::BeginTabItem(TR("general"))) {
|
||||
ImGui::Spacing();
|
||||
|
||||
// Skin/theme selection
|
||||
ImGui::Text("Theme:");
|
||||
ImGui::Text("%s", TR("theme"));
|
||||
ImGui::SameLine(lbl.position);
|
||||
|
||||
// Active skin combo (populated from SkinManager)
|
||||
@@ -172,7 +180,7 @@ void RenderSettingsWindow(App* app, bool* p_open)
|
||||
ImGui::SetNextItemWidth(cmb.width);
|
||||
if (ImGui::BeginCombo("##Theme", active_preview.c_str())) {
|
||||
// Bundled themes header
|
||||
ImGui::TextDisabled("Built-in");
|
||||
ImGui::TextDisabled("%s", TR("settings_builtin"));
|
||||
ImGui::Separator();
|
||||
for (size_t i = 0; i < skins.size(); i++) {
|
||||
const auto& skin = skins[i];
|
||||
@@ -198,7 +206,7 @@ void RenderSettingsWindow(App* app, bool* p_open)
|
||||
}
|
||||
if (has_custom) {
|
||||
ImGui::Spacing();
|
||||
ImGui::TextDisabled("Custom");
|
||||
ImGui::TextDisabled("%s", TR("settings_custom"));
|
||||
ImGui::Separator();
|
||||
for (size_t i = 0; i < skins.size(); i++) {
|
||||
const auto& skin = skins[i];
|
||||
@@ -236,7 +244,7 @@ void RenderSettingsWindow(App* app, bool* p_open)
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("Hotkey: Ctrl+Left/Right to cycle themes");
|
||||
ImGui::SetTooltip("%s", TR("tt_theme_hotkey"));
|
||||
|
||||
// Show indicator if custom theme is active
|
||||
if (active_is_custom) {
|
||||
@@ -245,7 +253,7 @@ void RenderSettingsWindow(App* app, bool* p_open)
|
||||
ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f), ICON_CUSTOM_THEME);
|
||||
ImGui::PopFont();
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Custom theme active");
|
||||
ImGui::SetTooltip("%s", TR("tt_custom_theme"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,14 +265,14 @@ void RenderSettingsWindow(App* app, bool* p_open)
|
||||
}
|
||||
ImGui::PopFont();
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Scan for new themes.\nPlace theme folders in:\n%s",
|
||||
ImGui::SetTooltip(TR("tt_scan_themes"),
|
||||
schema::SkinManager::getUserSkinsDirectory().c_str());
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
// Language selection
|
||||
ImGui::Text("Language:");
|
||||
ImGui::Text("%s", TR("language"));
|
||||
ImGui::SameLine(lbl.position);
|
||||
auto& i18n = util::I18n::instance();
|
||||
const auto& languages = i18n.getAvailableLanguages();
|
||||
@@ -283,17 +291,17 @@ void RenderSettingsWindow(App* app, bool* p_open)
|
||||
std::advance(it, s_language_index);
|
||||
i18n.loadLanguage(it->first);
|
||||
}
|
||||
ImGui::TextDisabled(" Note: Some text requires restart to update");
|
||||
ImGui::TextDisabled(" %s", TR("settings_language_note"));
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
// Acrylic Effects settings
|
||||
ImGui::Text("Visual Effects");
|
||||
ImGui::Text("%s", TR("settings_visual_effects"));
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Text("Acrylic Level:");
|
||||
ImGui::Text("%s", TR("settings_acrylic_level"));
|
||||
ImGui::SameLine(lbl.position);
|
||||
ImGui::SetNextItemWidth(cmb.width);
|
||||
{
|
||||
@@ -309,10 +317,10 @@ void RenderSettingsWindow(App* app, bool* p_open)
|
||||
effects::ImGuiAcrylic::ApplyBlurAmount(s_blur_amount);
|
||||
}
|
||||
}
|
||||
ImGui::TextDisabled(" Blur amount (0%% = off, 100%% = maximum)");
|
||||
ImGui::TextDisabled(" %s", TR("tt_blur"));
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Text("Noise Opacity:");
|
||||
ImGui::Text("%s", TR("settings_noise_opacity"));
|
||||
ImGui::SameLine(lbl.position);
|
||||
ImGui::SetNextItemWidth(cmb.width);
|
||||
{
|
||||
@@ -326,86 +334,86 @@ void RenderSettingsWindow(App* app, bool* p_open)
|
||||
effects::ImGuiAcrylic::SetNoiseOpacity(s_noise_opacity);
|
||||
}
|
||||
}
|
||||
ImGui::TextDisabled(" Grain texture intensity (0%% = off, 100%% = maximum)");
|
||||
ImGui::TextDisabled(" %s", TR("tt_noise"));
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
// Accessibility: Reduced transparency
|
||||
if (ImGui::Checkbox("Reduce transparency", &s_reduced_transparency)) {
|
||||
if (ImGui::Checkbox(TrId("settings_reduce_transparency", "reduce_trans").c_str(), &s_reduced_transparency)) {
|
||||
effects::ImGuiAcrylic::SetReducedTransparency(s_reduced_transparency);
|
||||
}
|
||||
ImGui::TextDisabled(" Use solid colors instead of blur effects (accessibility)");
|
||||
ImGui::TextDisabled(" %s", TR("settings_solid_colors_desc"));
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
if (ImGui::Checkbox("Simple background", &s_gradient_background)) {
|
||||
if (ImGui::Checkbox(TrId("simple_background", "simple_bg").c_str(), &s_gradient_background)) {
|
||||
schema::SkinManager::instance().setGradientMode(s_gradient_background);
|
||||
}
|
||||
ImGui::TextDisabled(" Replace textured backgrounds with smooth gradients");
|
||||
ImGui::TextDisabled(" %s", TR("settings_gradient_desc"));
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
// Privacy settings
|
||||
ImGui::Text("Privacy");
|
||||
ImGui::Text("%s", TR("settings_privacy"));
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Checkbox("Save shielded transaction history locally", &s_save_ztxs);
|
||||
ImGui::TextDisabled(" Stores z-addr transactions in a local file for viewing");
|
||||
ImGui::Checkbox(TrId("settings_save_shielded_local", "save_ztx_w").c_str(), &s_save_ztxs);
|
||||
ImGui::TextDisabled(" %s", TR("settings_save_shielded_desc"));
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Checkbox("Auto-shield transparent funds", &s_auto_shield);
|
||||
ImGui::TextDisabled(" Automatically move transparent funds to shielded addresses");
|
||||
ImGui::Checkbox(TrId("settings_auto_shield_funds", "auto_shld_w").c_str(), &s_auto_shield);
|
||||
ImGui::TextDisabled(" %s", TR("settings_auto_shield_desc"));
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Checkbox("Use Tor for network connections", &s_use_tor);
|
||||
ImGui::TextDisabled(" Route all connections through Tor for enhanced privacy");
|
||||
ImGui::Checkbox(TrId("settings_use_tor_network", "tor_w").c_str(), &s_use_tor);
|
||||
ImGui::TextDisabled(" %s", TR("settings_tor_desc"));
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
// Other settings
|
||||
ImGui::Text("Other");
|
||||
ImGui::Text("%s", TR("settings_other"));
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Checkbox("Allow custom transaction fees", &s_allow_custom_fees);
|
||||
ImGui::Checkbox("Fetch price data from CoinGecko", &s_fetch_prices);
|
||||
ImGui::Checkbox(TrId("custom_fees", "fees_w").c_str(), &s_allow_custom_fees);
|
||||
ImGui::Checkbox(TrId("fetch_prices", "prices_w").c_str(), &s_fetch_prices);
|
||||
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
// Connection settings tab
|
||||
if (ImGui::BeginTabItem("Connection")) {
|
||||
if (ImGui::BeginTabItem(TR("settings_connection"))) {
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Text("RPC Connection");
|
||||
ImGui::TextDisabled("Configure connection to dragonxd daemon");
|
||||
ImGui::Text("%s", TR("settings_rpc_connection"));
|
||||
ImGui::TextDisabled("%s", TR("settings_configure_rpc"));
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Text("Host:");
|
||||
ImGui::Text("%s", TR("rpc_host"));
|
||||
ImGui::SameLine(connLbl.position);
|
||||
ImGui::SetNextItemWidth(cmb.width);
|
||||
ImGui::InputText("##RPCHost", s_rpc_host, sizeof(s_rpc_host));
|
||||
|
||||
ImGui::Text("Port:");
|
||||
ImGui::Text("%s", TR("rpc_port"));
|
||||
ImGui::SameLine(connLbl.position);
|
||||
ImGui::SetNextItemWidth(portInput.width);
|
||||
ImGui::InputText("##RPCPort", s_rpc_port, sizeof(s_rpc_port));
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Text("Username:");
|
||||
ImGui::Text("%s", TR("rpc_user"));
|
||||
ImGui::SameLine(connLbl.position);
|
||||
ImGui::SetNextItemWidth(cmb.width);
|
||||
ImGui::InputText("##RPCUser", s_rpc_user, sizeof(s_rpc_user));
|
||||
|
||||
ImGui::Text("Password:");
|
||||
ImGui::Text("%s", TR("rpc_pass"));
|
||||
ImGui::SameLine(connLbl.position);
|
||||
ImGui::SetNextItemWidth(cmb.width);
|
||||
ImGui::InputText("##RPCPassword", s_rpc_password, sizeof(s_rpc_password),
|
||||
@@ -415,11 +423,11 @@ void RenderSettingsWindow(App* app, bool* p_open)
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::TextDisabled("Note: Connection settings are usually auto-detected from DRAGONX.conf");
|
||||
ImGui::TextDisabled("%s", TR("settings_rpc_note"));
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
if (material::StyledButton("Test Connection", ImVec2(0,0), S.resolveFont("button"))) {
|
||||
if (material::StyledButton(TR("test_connection"), ImVec2(0,0), S.resolveFont("button"))) {
|
||||
if (app->rpc()) {
|
||||
app->rpc()->getInfo([](const nlohmann::json& result, const std::string& error) {
|
||||
if (error.empty()) {
|
||||
@@ -439,15 +447,15 @@ void RenderSettingsWindow(App* app, bool* p_open)
|
||||
}
|
||||
|
||||
// Wallet tab
|
||||
if (ImGui::BeginTabItem("Wallet")) {
|
||||
if (ImGui::BeginTabItem(TR("wallet"))) {
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Text("Wallet Maintenance");
|
||||
ImGui::Text("%s", TR("settings_wallet_maintenance"));
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
if (material::StyledButton("Rescan Blockchain", ImVec2(walletBtn.width, 0), S.resolveFont(walletBtn.font))) {
|
||||
if (material::StyledButton(TR("rescan"), ImVec2(walletBtn.width, 0), S.resolveFont(walletBtn.font))) {
|
||||
if (app->rpc()) {
|
||||
// Start rescan from block 0
|
||||
app->rpc()->rescanBlockchain(0, [](const nlohmann::json& result, const std::string& error) {
|
||||
@@ -465,45 +473,41 @@ void RenderSettingsWindow(App* app, bool* p_open)
|
||||
Notifications::instance().error("RPC client not initialized");
|
||||
}
|
||||
}
|
||||
ImGui::TextDisabled(" Rescan blockchain for missing transactions");
|
||||
ImGui::TextDisabled(" %s", TR("settings_rescan_desc"));
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
static bool s_confirm_clear_ztx = false;
|
||||
if (material::StyledButton("Clear Saved Z-Transaction History", ImVec2(walletBtn.width, 0), S.resolveFont(walletBtn.font))) {
|
||||
if (material::StyledButton(TR("settings_clear_ztx_long"), ImVec2(walletBtn.width, 0), S.resolveFont(walletBtn.font))) {
|
||||
s_confirm_clear_ztx = true;
|
||||
}
|
||||
ImGui::TextDisabled(" Delete locally stored shielded transaction data");
|
||||
ImGui::TextDisabled(" %s", TR("settings_clear_ztx_desc"));
|
||||
|
||||
// Confirmation dialog
|
||||
if (s_confirm_clear_ztx) {
|
||||
if (material::BeginOverlayDialog("Confirm Clear Z-Tx History", &s_confirm_clear_ztx, 480.0f, 0.94f)) {
|
||||
if (material::BeginOverlayDialog(TR("confirm_clear_ztx_title"), &s_confirm_clear_ztx, 480.0f, 0.94f)) {
|
||||
ImGui::PushFont(material::Type().iconLarge());
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.0f, 1.0f), ICON_MD_WARNING);
|
||||
ImGui::PopFont();
|
||||
ImGui::SameLine();
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.0f, 1.0f), "Warning");
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.0f, 1.0f), "%s", TR("warning"));
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::TextWrapped(
|
||||
"Clearing z-transaction history may cause your shielded balance to show as 0 "
|
||||
"until a wallet rescan is performed.");
|
||||
ImGui::TextWrapped("%s", TR("confirm_clear_ztx_warning1"));
|
||||
ImGui::Spacing();
|
||||
ImGui::TextWrapped(
|
||||
"If this happens, you will need to re-import your z-address private keys with "
|
||||
"rescan enabled to recover your balance.");
|
||||
ImGui::TextWrapped("%s", TR("confirm_clear_ztx_warning2"));
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
float btnW = (ImGui::GetContentRegionAvail().x - ImGui::GetStyle().ItemSpacing.x) * 0.5f;
|
||||
if (ImGui::Button("Cancel", ImVec2(btnW, 40))) {
|
||||
if (ImGui::Button(TR("cancel"), ImVec2(btnW, 40))) {
|
||||
s_confirm_clear_ztx = false;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.8f, 0.2f, 0.2f, 1.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.9f, 0.3f, 0.3f, 1.0f));
|
||||
if (ImGui::Button("Clear Anyway", ImVec2(btnW, 40))) {
|
||||
if (ImGui::Button(TrId("clear_anyway", "clear_ztx_w").c_str(), ImVec2(btnW, 40))) {
|
||||
std::string ztx_file = util::Platform::getDragonXDataDir() + "ztx_history.json";
|
||||
if (util::Platform::deleteFile(ztx_file)) {
|
||||
Notifications::instance().success("Z-transaction history cleared");
|
||||
@@ -521,7 +525,7 @@ void RenderSettingsWindow(App* app, bool* p_open)
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Text("Wallet Info");
|
||||
ImGui::Text("%s", TR("settings_wallet_info"));
|
||||
ImGui::Spacing();
|
||||
|
||||
// Get actual wallet size
|
||||
@@ -529,35 +533,35 @@ void RenderSettingsWindow(App* app, bool* p_open)
|
||||
uint64_t wallet_size = util::Platform::getFileSize(wallet_path);
|
||||
if (wallet_size > 0) {
|
||||
std::string size_str = util::Platform::formatFileSize(wallet_size);
|
||||
ImGui::Text("Wallet file size: %s", size_str.c_str());
|
||||
ImGui::Text(TR("settings_wallet_file_size"), size_str.c_str());
|
||||
} else {
|
||||
ImGui::TextDisabled("Wallet file not found");
|
||||
ImGui::TextDisabled("%s", TR("settings_wallet_not_found"));
|
||||
}
|
||||
ImGui::Text("Wallet location: %s", wallet_path.c_str());
|
||||
ImGui::Text(TR("settings_wallet_location"), wallet_path.c_str());
|
||||
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
// Explorer tab
|
||||
if (ImGui::BeginTabItem("Explorer")) {
|
||||
if (ImGui::BeginTabItem(TR("explorer"))) {
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Text("Block Explorer URLs");
|
||||
ImGui::TextDisabled("Configure external block explorer links");
|
||||
ImGui::Text("%s", TR("settings_block_explorer_urls"));
|
||||
ImGui::TextDisabled("%s", TR("settings_configure_explorer"));
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Text("Transaction URL:");
|
||||
ImGui::Text("%s", TR("transaction_url"));
|
||||
ImGui::SetNextItemWidth(-1);
|
||||
ImGui::InputText("##TxExplorer", s_tx_explorer, sizeof(s_tx_explorer));
|
||||
|
||||
ImGui::Text("Address URL:");
|
||||
ImGui::Text("%s", TR("address_url"));
|
||||
ImGui::SetNextItemWidth(-1);
|
||||
ImGui::InputText("##AddrExplorer", s_addr_explorer, sizeof(s_addr_explorer));
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::TextDisabled("URLs should include a trailing slash. The txid/address will be appended.");
|
||||
ImGui::TextDisabled("%s", TR("settings_explorer_hint"));
|
||||
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
@@ -570,13 +574,13 @@ void RenderSettingsWindow(App* app, bool* p_open)
|
||||
ImGui::Spacing();
|
||||
|
||||
// Save/Cancel buttons
|
||||
if (material::StyledButton("Save", ImVec2(saveBtn.width, 0), S.resolveFont(saveBtn.font))) {
|
||||
if (material::StyledButton(TR("save"), ImVec2(saveBtn.width, 0), S.resolveFont(saveBtn.font))) {
|
||||
saveSettingsFromUI(app->settings());
|
||||
Notifications::instance().success("Settings saved");
|
||||
*p_open = false;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (material::StyledButton("Cancel", ImVec2(cancelBtn.width, 0), S.resolveFont(cancelBtn.font))) {
|
||||
if (material::StyledButton(TR("cancel"), ImVec2(cancelBtn.width, 0), S.resolveFont(cancelBtn.font))) {
|
||||
// Reload settings to revert changes
|
||||
loadSettingsToUI(app->settings());
|
||||
// Revert skin to what was active when settings opened
|
||||
|
||||
@@ -76,24 +76,17 @@ void ShieldDialog::render(App* app)
|
||||
auto cancelBtn = S.button("dialogs.shield", "cancel-button");
|
||||
|
||||
const char* title = (s_mode == Mode::ShieldCoinbase)
|
||||
? "Shield Coinbase Rewards"
|
||||
: "Merge to Address";
|
||||
? TR("shield_title")
|
||||
: TR("merge_title");
|
||||
|
||||
if (material::BeginOverlayDialog(title, &s_open, win.width, 0.94f)) {
|
||||
const auto& state = app->getWalletState();
|
||||
|
||||
// Description
|
||||
if (s_mode == Mode::ShieldCoinbase) {
|
||||
ImGui::TextWrapped(
|
||||
"Shield your mining rewards by sending coinbase outputs from "
|
||||
"transparent addresses to a shielded address. This improves "
|
||||
"privacy by hiding your mining income."
|
||||
);
|
||||
ImGui::TextWrapped("%s", TR("shield_description"));
|
||||
} else {
|
||||
ImGui::TextWrapped(
|
||||
"Merge multiple UTXOs into a single shielded address. This can "
|
||||
"help reduce wallet size and improve privacy."
|
||||
);
|
||||
ImGui::TextWrapped("%s", TR("merge_description"));
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
@@ -102,18 +95,18 @@ void ShieldDialog::render(App* app)
|
||||
|
||||
// From address (for shield coinbase)
|
||||
if (s_mode == Mode::ShieldCoinbase) {
|
||||
ImGui::Text("From Address:");
|
||||
ImGui::Text("%s", TR("shield_from_address"));
|
||||
ImGui::SetNextItemWidth(-1);
|
||||
ImGui::InputText("##FromAddr", s_from_address, sizeof(s_from_address));
|
||||
ImGui::TextDisabled("Use '*' to shield from all transparent addresses");
|
||||
ImGui::TextDisabled("%s", TR("shield_wildcard_hint"));
|
||||
ImGui::Spacing();
|
||||
}
|
||||
|
||||
// To address (z-address dropdown)
|
||||
ImGui::Text("To Address (Shielded):");
|
||||
ImGui::Text("%s", TR("shield_to_address"));
|
||||
|
||||
// Get z-addresses for dropdown
|
||||
std::string to_display = s_to_address[0] ? s_to_address : "Select z-address...";
|
||||
std::string to_display = s_to_address[0] ? s_to_address : TR("shield_select_z");
|
||||
if (to_display.length() > static_cast<size_t>(addrLbl.truncate)) {
|
||||
to_display = to_display.substr(0, addrFrontLbl.truncate) + "..." + to_display.substr(to_display.length() - addrBackLbl.truncate);
|
||||
}
|
||||
@@ -142,7 +135,7 @@ void ShieldDialog::render(App* app)
|
||||
ImGui::Spacing();
|
||||
|
||||
// Fee
|
||||
ImGui::Text("Fee:");
|
||||
ImGui::Text("%s", TR("fee_label"));
|
||||
ImGui::SetNextItemWidth(feeInput.width);
|
||||
ImGui::InputDouble("##Fee", &s_fee, 0.0001, 0.001, "%.8f");
|
||||
ImGui::SameLine();
|
||||
@@ -151,11 +144,11 @@ void ShieldDialog::render(App* app)
|
||||
ImGui::Spacing();
|
||||
|
||||
// UTXO limit
|
||||
ImGui::Text("UTXO Limit:");
|
||||
ImGui::Text("%s", TR("shield_utxo_limit"));
|
||||
ImGui::SetNextItemWidth(utxoInput.width);
|
||||
ImGui::InputInt("##Limit", &s_utxo_limit);
|
||||
ImGui::SameLine();
|
||||
ImGui::TextDisabled("Max UTXOs per operation");
|
||||
ImGui::TextDisabled("%s", TR("shield_max_utxos"));
|
||||
if (s_utxo_limit < 1) s_utxo_limit = 1;
|
||||
if (s_utxo_limit > 100) s_utxo_limit = 100;
|
||||
|
||||
@@ -178,7 +171,7 @@ void ShieldDialog::render(App* app)
|
||||
|
||||
if (!can_submit) ImGui::BeginDisabled();
|
||||
|
||||
const char* btn_label = (s_mode == Mode::ShieldCoinbase) ? "Shield Funds" : "Merge Funds";
|
||||
const char* btn_label = (s_mode == Mode::ShieldCoinbase) ? TR("shield_funds") : TR("merge_funds");
|
||||
if (material::StyledButton(btn_label, ImVec2(shieldBtn.width, 0), S.resolveFont(shieldBtn.font))) {
|
||||
s_operation_pending = true;
|
||||
s_status_message = "Submitting operation...";
|
||||
@@ -201,7 +194,7 @@ void ShieldDialog::render(App* app)
|
||||
if (error.empty()) {
|
||||
s_operation_id = result.value("opid", "");
|
||||
s_status_message = "Operation submitted: " + s_operation_id;
|
||||
Notifications::instance().success("Shield operation started");
|
||||
Notifications::instance().success(TR("shield_started"));
|
||||
} else {
|
||||
s_status_message = "Error: " + error;
|
||||
Notifications::instance().error("Shield failed: " + error);
|
||||
@@ -231,7 +224,7 @@ void ShieldDialog::render(App* app)
|
||||
if (error.empty()) {
|
||||
s_operation_id = result.value("opid", "");
|
||||
s_status_message = "Operation submitted: " + s_operation_id;
|
||||
Notifications::instance().success("Merge operation started");
|
||||
Notifications::instance().success(TR("merge_started"));
|
||||
} else {
|
||||
s_status_message = "Error: " + error;
|
||||
Notifications::instance().error("Merge failed: " + error);
|
||||
@@ -246,7 +239,7 @@ void ShieldDialog::render(App* app)
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (material::StyledButton("Cancel", ImVec2(cancelBtn.width, 0), S.resolveFont(cancelBtn.font))) {
|
||||
if (material::StyledButton(TR("cancel"), ImVec2(cancelBtn.width, 0), S.resolveFont(cancelBtn.font))) {
|
||||
s_open = false;
|
||||
}
|
||||
|
||||
@@ -256,9 +249,9 @@ void ShieldDialog::render(App* app)
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Text("Operation ID: %s", s_operation_id.c_str());
|
||||
ImGui::Text(TR("shield_operation_id"), s_operation_id.c_str());
|
||||
|
||||
if (material::StyledButton("Check Status", ImVec2(0,0), S.resolveFont(shieldBtn.font))) {
|
||||
if (material::StyledButton(TR("shield_check_status"), ImVec2(0,0), S.resolveFont(shieldBtn.font))) {
|
||||
std::string opid = s_operation_id;
|
||||
if (app->worker()) {
|
||||
app->worker()->post([rpc = app->rpc(), opid]() -> rpc::RPCWorker::MainCb {
|
||||
@@ -276,14 +269,14 @@ void ShieldDialog::render(App* app)
|
||||
auto& op = result[0];
|
||||
std::string status = op.value("status", "unknown");
|
||||
if (status == "success") {
|
||||
s_status_message = "Operation completed successfully!";
|
||||
Notifications::instance().success("Shield/merge completed!");
|
||||
s_status_message = TR("shield_completed");
|
||||
Notifications::instance().success(TR("shield_merge_done"));
|
||||
} else if (status == "failed") {
|
||||
std::string errMsg = op.value("error", nlohmann::json{}).value("message", "Unknown error");
|
||||
s_status_message = "Operation failed: " + errMsg;
|
||||
Notifications::instance().error("Operation failed: " + errMsg);
|
||||
} else if (status == "executing") {
|
||||
s_status_message = "Operation in progress...";
|
||||
s_status_message = TR("shield_in_progress");
|
||||
} else {
|
||||
s_status_message = "Status: " + status;
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ void TransactionDetailsDialog::render(App* app)
|
||||
auto memoInput = S.input("dialogs.transaction-details", "memo-input");
|
||||
auto bottomBtn = S.button("dialogs.transaction-details", "bottom-button");
|
||||
|
||||
if (material::BeginOverlayDialog("Transaction Details", &s_open, win.width, 0.94f)) {
|
||||
if (material::BeginOverlayDialog(TR("tx_details_title"), &s_open, win.width, 0.94f)) {
|
||||
const auto& tx = s_transaction;
|
||||
|
||||
// Type indicator with color
|
||||
@@ -52,16 +52,16 @@ void TransactionDetailsDialog::render(App* app)
|
||||
std::string type_display;
|
||||
if (tx.type == "receive") {
|
||||
type_color = ImVec4(0.3f, 0.8f, 0.3f, 1.0f);
|
||||
type_display = "RECEIVED";
|
||||
type_display = TR("tx_received");
|
||||
} else if (tx.type == "send") {
|
||||
type_color = ImVec4(0.8f, 0.3f, 0.3f, 1.0f);
|
||||
type_display = "SENT";
|
||||
type_display = TR("tx_sent");
|
||||
} else if (tx.type == "generate" || tx.type == "mined") {
|
||||
type_color = ImVec4(0.3f, 0.6f, 0.9f, 1.0f);
|
||||
type_display = "MINED";
|
||||
type_display = TR("tx_mined");
|
||||
} else if (tx.type == "immature") {
|
||||
type_color = ImVec4(0.8f, 0.8f, 0.3f, 1.0f);
|
||||
type_display = "IMMATURE";
|
||||
type_display = TR("tx_immature");
|
||||
} else {
|
||||
type_color = ImVec4(0.7f, 0.7f, 0.7f, 1.0f);
|
||||
type_display = tx.type;
|
||||
@@ -72,11 +72,11 @@ void TransactionDetailsDialog::render(App* app)
|
||||
|
||||
// Confirmations badge
|
||||
if (tx.confirmations == 0) {
|
||||
ImGui::TextColored(ImVec4(0.8f, 0.6f, 0.0f, 1.0f), "Pending");
|
||||
ImGui::TextColored(ImVec4(0.8f, 0.6f, 0.0f, 1.0f), "%s", TR("pending"));
|
||||
} else if (tx.confirmations < 10) {
|
||||
ImGui::TextColored(ImVec4(0.8f, 0.8f, 0.3f, 1.0f), "%d confirmations", tx.confirmations);
|
||||
ImGui::TextColored(ImVec4(0.8f, 0.8f, 0.3f, 1.0f), TR("tx_confirmations"), tx.confirmations);
|
||||
} else {
|
||||
ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f), "%d confirmations", tx.confirmations);
|
||||
ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f), TR("tx_confirmations"), tx.confirmations);
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
@@ -84,7 +84,7 @@ void TransactionDetailsDialog::render(App* app)
|
||||
ImGui::Spacing();
|
||||
|
||||
// Amount (prominent display)
|
||||
ImGui::Text("Amount:");
|
||||
ImGui::Text("%s", TR("amount_label"));
|
||||
ImGui::SameLine(lbl.position);
|
||||
if (tx.amount >= 0) {
|
||||
ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f), "+%.8f DRGX", tx.amount);
|
||||
@@ -102,7 +102,7 @@ void TransactionDetailsDialog::render(App* app)
|
||||
ImGui::Spacing();
|
||||
|
||||
// Date/Time
|
||||
ImGui::Text("Date:");
|
||||
ImGui::Text("%s", TR("date_label"));
|
||||
ImGui::SameLine(lbl.position);
|
||||
ImGui::Text("%s", tx.getTimeString().c_str());
|
||||
|
||||
@@ -111,7 +111,7 @@ void TransactionDetailsDialog::render(App* app)
|
||||
ImGui::Spacing();
|
||||
|
||||
// Transaction ID
|
||||
ImGui::Text("Transaction ID:");
|
||||
ImGui::Text("%s", TR("tx_id_label"));
|
||||
char txid_buf[128];
|
||||
strncpy(txid_buf, tx.txid.c_str(), sizeof(txid_buf) - 1);
|
||||
txid_buf[sizeof(txid_buf) - 1] = '\0';
|
||||
@@ -126,7 +126,7 @@ void TransactionDetailsDialog::render(App* app)
|
||||
|
||||
// Address
|
||||
if (!tx.address.empty()) {
|
||||
ImGui::Text(tx.type == "send" ? "To Address:" : "From Address:");
|
||||
ImGui::Text("%s", tx.type == "send" ? TR("tx_to_address") : TR("tx_from_address"));
|
||||
|
||||
// Use multiline for z-addresses
|
||||
if (tx.address.length() > 50) {
|
||||
@@ -155,7 +155,7 @@ void TransactionDetailsDialog::render(App* app)
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Text("Memo:");
|
||||
ImGui::Text("%s", TR("memo_label"));
|
||||
char memo_buf[512];
|
||||
strncpy(memo_buf, tx.memo.c_str(), sizeof(memo_buf) - 1);
|
||||
memo_buf[sizeof(memo_buf) - 1] = '\0';
|
||||
@@ -173,7 +173,7 @@ void TransactionDetailsDialog::render(App* app)
|
||||
float start_x = (ImGui::GetWindowWidth() - total_width) / 2.0f;
|
||||
ImGui::SetCursorPosX(start_x);
|
||||
|
||||
if (material::StyledButton("View on Explorer", ImVec2(button_width, 0), S.resolveFont(bottomBtn.font))) {
|
||||
if (material::StyledButton(TR("tx_view_explorer"), ImVec2(button_width, 0), S.resolveFont(bottomBtn.font))) {
|
||||
std::string url = app->settings()->getTxExplorerUrl() + tx.txid;
|
||||
// Platform-specific URL opening
|
||||
#ifdef _WIN32
|
||||
@@ -189,7 +189,7 @@ void TransactionDetailsDialog::render(App* app)
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (material::StyledButton("Close", ImVec2(button_width, 0), S.resolveFont(bottomBtn.font))) {
|
||||
if (material::StyledButton(TR("close"), ImVec2(button_width, 0), S.resolveFont(bottomBtn.font))) {
|
||||
s_open = false;
|
||||
}
|
||||
material::EndOverlayDialog();
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "transaction_details_dialog.h"
|
||||
#include "export_transactions_dialog.h"
|
||||
#include "../../app.h"
|
||||
#include "../../util/i18n.h"
|
||||
#include "../../config/settings.h"
|
||||
#include "../../config/version.h"
|
||||
#include "../theme.h"
|
||||
@@ -28,6 +29,10 @@ namespace ui {
|
||||
|
||||
using namespace material;
|
||||
|
||||
static std::string TrId(const char* key, const char* id) {
|
||||
return std::string(TR(key)) + "##" + id;
|
||||
}
|
||||
|
||||
// Helper to truncate strings
|
||||
static std::string truncateString(const std::string& str, int maxLen = 16) {
|
||||
if (str.length() <= static_cast<size_t>(maxLen)) return str;
|
||||
@@ -66,7 +71,7 @@ struct DisplayTx {
|
||||
};
|
||||
|
||||
std::string DisplayTx::getTimeString() const {
|
||||
if (timestamp <= 0) return "Pending";
|
||||
if (timestamp <= 0) return TR("pending");
|
||||
std::time_t t = static_cast<std::time_t>(timestamp);
|
||||
char buf[64];
|
||||
std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M", std::localtime(&t));
|
||||
@@ -79,10 +84,11 @@ static std::string timeAgo(int64_t timestamp) {
|
||||
int64_t now = (int64_t)std::time(nullptr);
|
||||
int64_t diff = now - timestamp;
|
||||
if (diff < 0) diff = 0;
|
||||
if (diff < 60) return std::to_string(diff) + "s ago";
|
||||
if (diff < 3600) return std::to_string(diff / 60) + "m ago";
|
||||
if (diff < 86400) return std::to_string(diff / 3600) + "h ago";
|
||||
return std::to_string(diff / 86400) + "d ago";
|
||||
char buf[32];
|
||||
if (diff < 60) { snprintf(buf, sizeof(buf), TR("time_seconds_ago"), (long long)diff); return buf; }
|
||||
if (diff < 3600) { snprintf(buf, sizeof(buf), TR("time_minutes_ago"), (long long)(diff / 60)); return buf; }
|
||||
if (diff < 86400) { snprintf(buf, sizeof(buf), TR("time_hours_ago"), (long long)(diff / 3600)); return buf; }
|
||||
snprintf(buf, sizeof(buf), TR("time_days_ago"), (long long)(diff / 86400)); return buf;
|
||||
}
|
||||
|
||||
// Draw a small transaction-type icon
|
||||
@@ -191,10 +197,10 @@ void RenderTransactionsTab(App* app)
|
||||
DrawTxIcon(dl, "receive", cx + iconSz, cy + iconSz * 1.33f, iconSz, greenCol);
|
||||
|
||||
float labelX = cx + iconSz * 3.0f;
|
||||
dl->AddText(ovFont, ovFont->LegacySize, ImVec2(labelX, cy), OnSurfaceMedium(), "RECEIVED");
|
||||
dl->AddText(ovFont, ovFont->LegacySize, ImVec2(labelX, cy), OnSurfaceMedium(), TR("received_upper"));
|
||||
cy += ovFont->LegacySize + Layout::spacingSm();
|
||||
|
||||
snprintf(buf, sizeof(buf), "%d txs", recvCount);
|
||||
snprintf(buf, sizeof(buf), TR("txs_count"), recvCount);
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(cx, cy), OnSurfaceDisabled(), buf);
|
||||
cy += capFont->LegacySize + Layout::spacingXs();
|
||||
|
||||
@@ -221,10 +227,10 @@ void RenderTransactionsTab(App* app)
|
||||
DrawTxIcon(dl, "send", cx + iconSz, cy + iconSz * 1.33f, iconSz, redCol);
|
||||
|
||||
float labelX = cx + iconSz * 3.0f;
|
||||
dl->AddText(ovFont, ovFont->LegacySize, ImVec2(labelX, cy), OnSurfaceMedium(), "SENT");
|
||||
dl->AddText(ovFont, ovFont->LegacySize, ImVec2(labelX, cy), OnSurfaceMedium(), TR("sent_upper"));
|
||||
cy += ovFont->LegacySize + Layout::spacingSm();
|
||||
|
||||
snprintf(buf, sizeof(buf), "%d txs", sendCount);
|
||||
snprintf(buf, sizeof(buf), TR("txs_count"), sendCount);
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(cx, cy), OnSurfaceDisabled(), buf);
|
||||
cy += capFont->LegacySize + Layout::spacingXs();
|
||||
|
||||
@@ -251,10 +257,10 @@ void RenderTransactionsTab(App* app)
|
||||
DrawTxIcon(dl, "mined", cx + iconSz, cy + iconSz * 1.33f, iconSz, goldCol);
|
||||
|
||||
float labelX = cx + iconSz * 3.0f;
|
||||
dl->AddText(ovFont, ovFont->LegacySize, ImVec2(labelX, cy), OnSurfaceMedium(), "MINED");
|
||||
dl->AddText(ovFont, ovFont->LegacySize, ImVec2(labelX, cy), OnSurfaceMedium(), TR("mined_upper"));
|
||||
cy += ovFont->LegacySize + Layout::spacingSm();
|
||||
|
||||
snprintf(buf, sizeof(buf), "%d txs", minedCount);
|
||||
snprintf(buf, sizeof(buf), TR("txs_count"), minedCount);
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(cx, cy), OnSurfaceDisabled(), buf);
|
||||
cy += capFont->LegacySize + Layout::spacingXs();
|
||||
|
||||
@@ -292,23 +298,23 @@ void RenderTransactionsTab(App* app)
|
||||
|
||||
ImGui::SetNextItemWidth(searchWidth);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, Layout::spacingSm());
|
||||
ImGui::InputTextWithHint("##TxSearch", "Search...", search_filter, sizeof(search_filter));
|
||||
ImGui::InputTextWithHint("##TxSearch", TR("search_placeholder"), search_filter, sizeof(search_filter));
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
float filterGap = std::max(8.0f, ((filterGapEl.size > 0) ? filterGapEl.size : 20.0f) * hs);
|
||||
ImGui::SameLine(0, filterGap);
|
||||
float comboW = std::max(80.0f, ((filterCombo.width > 0) ? filterCombo.width : 120.0f) * hs);
|
||||
ImGui::SetNextItemWidth(comboW);
|
||||
const char* types[] = { "All", "Sent", "Received", "Mined" };
|
||||
const char* types[] = { TR("all_filter"), TR("sent_filter"), TR("received_filter"), TR("mined_filter") };
|
||||
ImGui::Combo("##TxType", &type_filter, types, IM_ARRAYSIZE(types));
|
||||
|
||||
ImGui::SameLine(0, filterGap);
|
||||
if (TactileButton("Refresh", ImVec2(0, 0), S.resolveFont("button"))) {
|
||||
if (TactileButton(TrId("refresh", "tx").c_str(), ImVec2(0, 0), S.resolveFont("button"))) {
|
||||
app->refreshNow();
|
||||
}
|
||||
|
||||
ImGui::SameLine(0, filterGap);
|
||||
if (TactileButton("Export CSV", ImVec2(0, 0), S.resolveFont("button"))) {
|
||||
if (TactileButton(TrId("export_csv", "tx").c_str(), ImVec2(0, 0), S.resolveFont("button"))) {
|
||||
ExportTransactionsDialog::show();
|
||||
}
|
||||
|
||||
@@ -442,7 +448,7 @@ void RenderTransactionsTab(App* app)
|
||||
|
||||
// ---- Heading line: "TRANSACTIONS" left, pagination right ----
|
||||
{
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), "TRANSACTIONS");
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("transactions_upper"));
|
||||
|
||||
if (totalPages > 1) {
|
||||
float paginationH = ImGui::GetFrameHeight();
|
||||
@@ -557,13 +563,13 @@ void RenderTransactionsTab(App* app)
|
||||
{
|
||||
if (!app->isConnected()) {
|
||||
ImGui::Dummy(ImVec2(0, 20));
|
||||
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), " Not connected to daemon...");
|
||||
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), TR("not_connected"));
|
||||
} else if (state.transactions.empty()) {
|
||||
ImGui::Dummy(ImVec2(0, 20));
|
||||
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), " No transactions found");
|
||||
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), TR("no_transactions"));
|
||||
} else if (filtered_indices.empty()) {
|
||||
ImGui::Dummy(ImVec2(0, 20));
|
||||
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), " No matching transactions");
|
||||
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), TR("no_matching"));
|
||||
} else {
|
||||
float rowH = body2->LegacySize + capFont->LegacySize + Layout::spacingLg() + Layout::spacingMd();
|
||||
float innerW = ImGui::GetContentRegionAvail().x;
|
||||
@@ -592,15 +598,15 @@ void RenderTransactionsTab(App* app)
|
||||
ImU32 iconCol;
|
||||
const char* typeStr;
|
||||
if (tx.display_type == "shield") {
|
||||
iconCol = Primary(); typeStr = "Shielded";
|
||||
iconCol = Primary(); typeStr = TR("shielded_type");
|
||||
} else if (tx.display_type == "receive") {
|
||||
iconCol = greenCol; typeStr = "Recv";
|
||||
iconCol = greenCol; typeStr = TR("recv_type");
|
||||
} else if (tx.display_type == "send") {
|
||||
iconCol = redCol; typeStr = "Sent";
|
||||
iconCol = redCol; typeStr = TR("sent_type");
|
||||
} else if (tx.display_type == "immature") {
|
||||
iconCol = Warning(); typeStr = "Immature";
|
||||
iconCol = Warning(); typeStr = TR("immature_type");
|
||||
} else {
|
||||
iconCol = goldCol; typeStr = "Mined";
|
||||
iconCol = goldCol; typeStr = TR("mined_type");
|
||||
}
|
||||
|
||||
// Expanded selection accent
|
||||
@@ -669,14 +675,14 @@ void RenderTransactionsTab(App* app)
|
||||
const char* statusStr;
|
||||
ImU32 statusCol;
|
||||
if (tx.confirmations == 0) {
|
||||
statusStr = "Pending"; statusCol = Warning();
|
||||
statusStr = TR("pending"); statusCol = Warning();
|
||||
} else if (tx.confirmations < 10) {
|
||||
snprintf(buf, sizeof(buf), "%d conf", tx.confirmations);
|
||||
snprintf(buf, sizeof(buf), TR("conf_count"), tx.confirmations);
|
||||
statusStr = buf; statusCol = Warning();
|
||||
} else if (tx.confirmations >= 100 && (tx.display_type == "generate" || tx.display_type == "mined")) {
|
||||
statusStr = "Mature"; statusCol = greenCol;
|
||||
statusStr = TR("mature"); statusCol = greenCol;
|
||||
} else {
|
||||
statusStr = "Confirmed"; statusCol = WithAlpha(Success(), 140);
|
||||
statusStr = TR("confirmed"); statusCol = WithAlpha(Success(), 140);
|
||||
}
|
||||
// Position status badge in the middle-right area
|
||||
ImVec2 sSz = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, statusStr);
|
||||
@@ -707,14 +713,14 @@ void RenderTransactionsTab(App* app)
|
||||
// Context menu
|
||||
const auto& acrylicTheme = GetCurrentAcrylicTheme();
|
||||
if (effects::ImGuiAcrylic::BeginAcrylicContextItem("TxContext", 0, acrylicTheme.menu)) {
|
||||
if (ImGui::MenuItem("Copy Address") && !tx.address.empty()) {
|
||||
if (ImGui::MenuItem(TR("copy_address")) && !tx.address.empty()) {
|
||||
ImGui::SetClipboardText(tx.address.c_str());
|
||||
}
|
||||
if (ImGui::MenuItem("Copy TxID")) {
|
||||
if (ImGui::MenuItem(TR("copy_txid"))) {
|
||||
ImGui::SetClipboardText(tx.txid.c_str());
|
||||
}
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem("View on Explorer")) {
|
||||
if (ImGui::MenuItem(TR("view_on_explorer"))) {
|
||||
std::string url = app->settings()->getTxExplorerUrl() + tx.txid;
|
||||
#ifdef _WIN32
|
||||
ShellExecuteA(nullptr, "open", url.c_str(), nullptr, nullptr, SW_SHOWNORMAL);
|
||||
@@ -726,7 +732,7 @@ void RenderTransactionsTab(App* app)
|
||||
system(cmd.c_str());
|
||||
#endif
|
||||
}
|
||||
if (ImGui::MenuItem("View Details")) {
|
||||
if (ImGui::MenuItem(TR("view_details"))) {
|
||||
if (tx.orig_idx >= 0 && tx.orig_idx < (int)state.transactions.size())
|
||||
TransactionDetailsDialog::show(state.transactions[tx.orig_idx]);
|
||||
}
|
||||
@@ -747,19 +753,19 @@ void RenderTransactionsTab(App* app)
|
||||
|
||||
// From address
|
||||
if (!tx.from_address.empty()) {
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), "FROM");
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("from_upper"));
|
||||
ImGui::TextWrapped("%s", tx.from_address.c_str());
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
}
|
||||
|
||||
// To address
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(),
|
||||
tx.display_type == "send" ? "TO" : (tx.display_type == "shield" ? "SHIELDED TO" : "ADDRESS"));
|
||||
tx.display_type == "send" ? TR("to_upper") : (tx.display_type == "shield" ? TR("shielded_to") : TR("address_upper")));
|
||||
ImGui::TextWrapped("%s", tx.address.c_str());
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
|
||||
// TxID (full, copyable)
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), "TRANSACTION ID");
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("transaction_id"));
|
||||
ImGui::TextWrapped("%s", tx.txid.c_str());
|
||||
if (ImGui::IsItemHovered()) ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
||||
if (ImGui::IsItemClicked()) ImGui::SetClipboardText(tx.txid.c_str());
|
||||
@@ -767,13 +773,13 @@ void RenderTransactionsTab(App* app)
|
||||
|
||||
// Memo
|
||||
if (!tx.memo.empty()) {
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), "MEMO");
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("memo_upper"));
|
||||
ImGui::TextWrapped("%s", tx.memo.c_str());
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
}
|
||||
|
||||
// Confirmations + time
|
||||
snprintf(buf, sizeof(buf), "%d confirmations | %s",
|
||||
snprintf(buf, sizeof(buf), TR("confirmations_display"),
|
||||
tx.confirmations, tx.getTimeString().c_str());
|
||||
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), buf);
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
@@ -782,15 +788,15 @@ void RenderTransactionsTab(App* app)
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImGui::ColorConvertU32ToFloat4(IM_COL32(255, 255, 255, 15)));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::ColorConvertU32ToFloat4(IM_COL32(255, 255, 255, 30)));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, schema::UI().drawElement("tabs.transactions", "detail-btn-rounding").size);
|
||||
if (TactileSmallButton("Copy TxID##detail", S.resolveFont("button"))) {
|
||||
if (TactileSmallButton(TrId("copy_txid", "detail").c_str(), S.resolveFont("button"))) {
|
||||
ImGui::SetClipboardText(tx.txid.c_str());
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (!tx.address.empty() && TactileSmallButton("Copy Address##detail", S.resolveFont("button"))) {
|
||||
if (!tx.address.empty() && TactileSmallButton(TrId("copy_address", "detail").c_str(), S.resolveFont("button"))) {
|
||||
ImGui::SetClipboardText(tx.address.c_str());
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (TactileSmallButton("Explorer##detail", S.resolveFont("button"))) {
|
||||
if (TactileSmallButton(TrId("explorer", "detail").c_str(), S.resolveFont("button"))) {
|
||||
std::string url = app->settings()->getTxExplorerUrl() + tx.txid;
|
||||
#ifdef _WIN32
|
||||
ShellExecuteA(nullptr, "open", url.c_str(), nullptr, nullptr, SW_SHOWNORMAL);
|
||||
@@ -803,7 +809,7 @@ void RenderTransactionsTab(App* app)
|
||||
#endif
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (TactileSmallButton("Full Details##detail", S.resolveFont("button"))) {
|
||||
if (TactileSmallButton(TrId("full_details", "detail").c_str(), S.resolveFont("button"))) {
|
||||
if (tx.orig_idx >= 0 && tx.orig_idx < (int)state.transactions.size())
|
||||
TransactionDetailsDialog::show(state.transactions[tx.orig_idx]);
|
||||
}
|
||||
@@ -849,7 +855,7 @@ void RenderTransactionsTab(App* app)
|
||||
}
|
||||
|
||||
// Status line with page info
|
||||
snprintf(buf, sizeof(buf), "Showing %d\xe2\x80\x93%d of %d transactions (total: %zu)",
|
||||
snprintf(buf, sizeof(buf), TR("showing_transactions"),
|
||||
filtered_count > 0 ? pageStart + 1 : 0, pageEnd, filtered_count, state.transactions.size());
|
||||
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), buf);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "../../app.h"
|
||||
#include "../../rpc/rpc_client.h"
|
||||
#include "../../rpc/rpc_worker.h"
|
||||
#include "../../util/i18n.h"
|
||||
#include "../schema/ui_schema.h"
|
||||
#include "../material/draw_helpers.h"
|
||||
#include "../theme.h"
|
||||
@@ -52,15 +53,15 @@ void ValidateAddressDialog::render(App* app)
|
||||
auto lbl = S.label("dialogs.validate-address", "label");
|
||||
auto closeBtn = S.button("dialogs.validate-address", "close-button");
|
||||
|
||||
if (material::BeginOverlayDialog("Validate Address", &s_open, win.width, 0.94f)) {
|
||||
ImGui::TextWrapped("Enter a DragonX address to check if it's valid and whether it belongs to this wallet.");
|
||||
if (material::BeginOverlayDialog(TR("validate_title"), &s_open, win.width, 0.94f)) {
|
||||
ImGui::TextWrapped("%s", TR("validate_description"));
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
// Address input
|
||||
ImGui::Text("Address:");
|
||||
ImGui::Text("%s", TR("address_label"));
|
||||
ImGui::SetNextItemWidth(-1);
|
||||
bool enter_pressed = ImGui::InputText("##ValidateAddr", s_address_input, sizeof(s_address_input),
|
||||
ImGuiInputTextFlags_EnterReturnsTrue);
|
||||
@@ -74,7 +75,7 @@ void ValidateAddressDialog::render(App* app)
|
||||
ImGui::BeginDisabled();
|
||||
}
|
||||
|
||||
if (material::StyledButton("Validate", ImVec2(valBtn.width, 0), S.resolveFont(valBtn.font)) || (enter_pressed && can_validate)) {
|
||||
if (material::StyledButton(TR("validate_btn"), ImVec2(valBtn.width, 0), S.resolveFont(valBtn.font)) || (enter_pressed && can_validate)) {
|
||||
s_validating = true;
|
||||
s_validated = false;
|
||||
s_error_message.clear();
|
||||
@@ -100,7 +101,7 @@ void ValidateAddressDialog::render(App* app)
|
||||
if (error.empty()) {
|
||||
s_is_valid = valid;
|
||||
s_is_mine = mine;
|
||||
s_address_type = "Shielded (z-address)";
|
||||
s_address_type = TR("validate_shielded_type");
|
||||
} else {
|
||||
s_error_message = error;
|
||||
s_is_valid = false;
|
||||
@@ -126,7 +127,7 @@ void ValidateAddressDialog::render(App* app)
|
||||
if (error.empty()) {
|
||||
s_is_valid = valid;
|
||||
s_is_mine = mine;
|
||||
s_address_type = "Transparent (t-address)";
|
||||
s_address_type = TR("validate_transparent_type");
|
||||
} else {
|
||||
s_error_message = error;
|
||||
s_is_valid = false;
|
||||
@@ -145,7 +146,7 @@ void ValidateAddressDialog::render(App* app)
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (material::StyledButton("Paste", ImVec2(pasteBtn.width, 0), S.resolveFont(pasteBtn.font))) {
|
||||
if (material::StyledButton(TR("paste"), ImVec2(pasteBtn.width, 0), S.resolveFont(pasteBtn.font))) {
|
||||
const char* clipboard = ImGui::GetClipboardText();
|
||||
if (clipboard) {
|
||||
strncpy(s_address_input, clipboard, sizeof(s_address_input) - 1);
|
||||
@@ -156,7 +157,7 @@ void ValidateAddressDialog::render(App* app)
|
||||
|
||||
if (s_validating) {
|
||||
ImGui::SameLine();
|
||||
ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), "Validating...");
|
||||
ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), "%s", TR("validating"));
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
@@ -165,39 +166,39 @@ void ValidateAddressDialog::render(App* app)
|
||||
|
||||
// Results
|
||||
if (s_validated) {
|
||||
ImGui::Text("Results:");
|
||||
ImGui::Text("%s", TR("validate_results"));
|
||||
ImGui::Spacing();
|
||||
|
||||
if (!s_error_message.empty()) {
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), "Error: %s", s_error_message.c_str());
|
||||
} else {
|
||||
// Valid/Invalid indicator
|
||||
ImGui::Text("Status:");
|
||||
ImGui::Text("%s", TR("validate_status"));
|
||||
ImGui::SameLine(lbl.position);
|
||||
if (s_is_valid) {
|
||||
ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f), "VALID");
|
||||
ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f), "%s", TR("validate_valid"));
|
||||
} else {
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), "INVALID");
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), "%s", TR("validate_invalid"));
|
||||
}
|
||||
|
||||
if (s_is_valid) {
|
||||
// Address type
|
||||
ImGui::Text("Type:");
|
||||
ImGui::Text("%s", TR("validate_type"));
|
||||
ImGui::SameLine(lbl.position);
|
||||
ImGui::Text("%s", s_address_type.c_str());
|
||||
|
||||
// Is mine?
|
||||
ImGui::Text("Ownership:");
|
||||
ImGui::Text("%s", TR("validate_ownership"));
|
||||
ImGui::SameLine(lbl.position);
|
||||
if (s_is_mine) {
|
||||
ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f), "This wallet owns this address");
|
||||
ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f), "%s", TR("validate_is_mine"));
|
||||
} else {
|
||||
ImGui::TextDisabled("Not owned by this wallet");
|
||||
ImGui::TextDisabled("%s", TR("validate_not_mine"));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (!app->isConnected()) {
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.0f, 1.0f), "Not connected to daemon");
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.0f, 1.0f), "%s", TR("not_connected"));
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
@@ -205,7 +206,7 @@ void ValidateAddressDialog::render(App* app)
|
||||
// Close button at bottom
|
||||
float button_width = closeBtn.width;
|
||||
ImGui::SetCursorPosX((ImGui::GetWindowWidth() - button_width) / 2.0f);
|
||||
if (material::StyledButton("Close", ImVec2(button_width, 0), S.resolveFont(closeBtn.font))) {
|
||||
if (material::StyledButton(TR("close"), ImVec2(button_width, 0), S.resolveFont(closeBtn.font))) {
|
||||
s_open = false;
|
||||
}
|
||||
material::EndOverlayDialog();
|
||||
|
||||
Reference in New Issue
Block a user