fix(ui): overview/mining polish + default-pool cleanup
- balance: inset the address-row favorite (star) button by the card's inner padding so it mirrors the left margin instead of hugging the card edge. - mining: remove pool.dragonx.cc from the built-in default pools (pool.dragonx.is is now the sole default); update the pool-registry test accordingly. - mining: middle-truncate saved pool-URL and payout-address dropdown rows (new shared material::TruncateToWidth helper) so a full z-address no longer runs under the trailing delete (X) button. - mining: fix the thread-grid cells overflowing the card at >100% display scaling — the reserved Mine-button width used a raw clamp that didn't scale; scale it by dp so cols is estimated correctly (no-op at 100%). Full-node build + test suite green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -57,6 +57,21 @@ inline ImU32 ReadableError() {
|
|||||||
return IM_COL32(r, g, b, (e >> IM_COL32_A_SHIFT) & 0xFF);
|
return IM_COL32(r, g, b, (e >> IM_COL32_A_SHIFT) & 0xFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Middle-ellipsis truncation ("front...back", roughly equal halves) so `text` fits within
|
||||||
|
// maxWidth pixels when drawn with `font` at `fontSize`. Returns `text` unchanged if it already
|
||||||
|
// fits (or maxWidth is non-positive). Display-only — never mutate the underlying value with this.
|
||||||
|
inline std::string TruncateToWidth(const std::string& text, ImFont* font, float fontSize, float maxWidth) {
|
||||||
|
if (text.empty() || !font || maxWidth <= 0.0f) return text;
|
||||||
|
if (font->CalcTextSizeA(fontSize, FLT_MAX, 0.0f, text.c_str()).x <= maxWidth) return text;
|
||||||
|
const int n = static_cast<int>(text.size());
|
||||||
|
for (int f = n / 2; f >= 3; --f) {
|
||||||
|
const int b = (f - 2 > 3) ? (f - 2) : 3; // keep the two halves roughly equal
|
||||||
|
std::string t = text.substr(0, f) + "..." + text.substr(n - b);
|
||||||
|
if (font->CalcTextSizeA(fontSize, FLT_MAX, 0.0f, t.c_str()).x <= maxWidth) return t;
|
||||||
|
}
|
||||||
|
return n > 6 ? (text.substr(0, 3) + "..." + text.substr(n - 3)) : text;
|
||||||
|
}
|
||||||
|
|
||||||
// Animated "loading" ellipsis: "", ".", "..", "..." cycling on a ~3Hz phase.
|
// Animated "loading" ellipsis: "", ".", "..", "..." cycling on a ~3Hz phase.
|
||||||
inline const char* LoadingDots() {
|
inline const char* LoadingDots() {
|
||||||
int n = ((int)(ImGui::GetTime() * 3.0f)) % 4;
|
int n = ((int)(ImGui::GetTime() * 3.0f)) % 4;
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ AddressRowLayout ComputeAddressRowLayout(float rowX,
|
|||||||
float spacingSm,
|
float spacingSm,
|
||||||
float spacingXs)
|
float spacingXs)
|
||||||
{
|
{
|
||||||
|
(void)spacingXs; // trailing button now insets by rowPadLeft (mirrors the left margin)
|
||||||
AddressRowLayout layout;
|
AddressRowLayout layout;
|
||||||
layout.contentStartX = rowX + rowPadLeft;
|
layout.contentStartX = rowX + rowPadLeft;
|
||||||
layout.contentStartY = rowY + spacingMd;
|
layout.contentStartY = rowY + spacingMd;
|
||||||
@@ -77,7 +78,9 @@ AddressRowLayout ComputeAddressRowLayout(float rowX,
|
|||||||
|
|
||||||
const float buttonY = rowY + (rowHeight - layout.buttonSize) * 0.5f;
|
const float buttonY = rowY + (rowHeight - layout.buttonSize) * 0.5f;
|
||||||
const float rightEdge = rowX + rowWidth;
|
const float rightEdge = rowX + rowWidth;
|
||||||
const float favoriteX = rightEdge - layout.buttonSize - spacingXs;
|
// Inset the trailing (favorite/star) button by the card's inner padding — the same margin the
|
||||||
|
// left content uses (rowPadLeft) — so it mirrors the left edge instead of hugging the card edge.
|
||||||
|
const float favoriteX = rightEdge - layout.buttonSize - rowPadLeft;
|
||||||
const float visibilityX = favoriteX - spacingSm - layout.buttonSize;
|
const float visibilityX = favoriteX - spacingSm - layout.buttonSize;
|
||||||
|
|
||||||
layout.favoriteButton = {favoriteX, buttonY, layout.buttonSize, layout.buttonSize};
|
layout.favoriteButton = {favoriteX, buttonY, layout.buttonSize, layout.buttonSize};
|
||||||
|
|||||||
@@ -57,7 +57,10 @@ void RenderMiningControls(App* app, const WalletState& state, const MiningInfo&
|
|||||||
|
|
||||||
// --- Compute thread grid layout based on controls card width ---
|
// --- Compute thread grid layout based on controls card width ---
|
||||||
// Estimate controlsW first to compute cols correctly
|
// Estimate controlsW first to compute cols correctly
|
||||||
float estControlsW = availWidth - std::min(schema::UI().drawElement("tabs.mining", "button-max-width-clamp").size, miningBtnMaxW) - miningBtnGap;
|
// The Mine button is square (= card height, which scales with DPI), so the width we
|
||||||
|
// reserve for it here must scale too — a RAW clamp under-reserves at >100% scaling, which
|
||||||
|
// over-estimates the grid width and lets the thread cells overflow the card (e.g. at 150%).
|
||||||
|
float estControlsW = availWidth - std::min(schema::UI().drawElement("tabs.mining", "button-max-width-clamp").size * dp, miningBtnMaxW) - miningBtnGap;
|
||||||
float innerW = estControlsW - pad * 2;
|
float innerW = estControlsW - pad * 2;
|
||||||
float cellSz = std::clamp(schema::UI().drawElement("tabs.mining", "cell-size").size * vs, schema::UI().drawElement("tabs.mining", "cell-min-size").size, schema::UI().drawElement("tabs.mining", "cell-max-size").sizeOr(42.0f));
|
float cellSz = std::clamp(schema::UI().drawElement("tabs.mining", "cell-size").size * vs, schema::UI().drawElement("tabs.mining", "cell-min-size").size, schema::UI().drawElement("tabs.mining", "cell-max-size").sizeOr(42.0f));
|
||||||
float cellGap = std::max(schema::UI().drawElement("tabs.mining", "cell-gap-min").size, cellSz * schema::UI().drawElement("tabs.mining", "cell-gap-ratio").size);
|
float cellGap = std::max(schema::UI().drawElement("tabs.mining", "cell-gap-min").size, cellSz * schema::UI().drawElement("tabs.mining", "cell-gap-ratio").size);
|
||||||
|
|||||||
@@ -308,11 +308,14 @@ void RenderMiningModeToggle(App* app, const WalletState& state, const MiningInfo
|
|||||||
pdl->AddRectFilled(rowMin, rowMax, IM_COL32(255, 255, 255, 10));
|
pdl->AddRectFilled(rowMin, rowMax, IM_COL32(255, 255, 255, 10));
|
||||||
if (rowHov && !inXZone)
|
if (rowHov && !inXZone)
|
||||||
pdl->AddRectFilled(rowMin, rowMax, StateHover());
|
pdl->AddRectFilled(rowMin, rowMax, StateHover());
|
||||||
// Item text with internal padding
|
// Item text with internal padding, middle-truncated so a long saved URL
|
||||||
|
// can't run under the trailing X (delete) button.
|
||||||
float textY = rowMin.y + (rowH - rowFontSz) * 0.5f;
|
float textY = rowMin.y + (rowH - rowFontSz) * 0.5f;
|
||||||
|
float maxTextW = popupInnerW - xZoneW - textPadX * 2.0f;
|
||||||
|
std::string urlDisp = material::TruncateToWidth(url, rowFont, rowFontSz, maxTextW);
|
||||||
pdl->AddText(rowFont, rowFontSz,
|
pdl->AddText(rowFont, rowFontSz,
|
||||||
ImVec2(rowMin.x + textPadX, textY),
|
ImVec2(rowMin.x + textPadX, textY),
|
||||||
isCurrent ? Primary() : OnSurface(), url.c_str());
|
isCurrent ? Primary() : OnSurface(), urlDisp.c_str());
|
||||||
// X button — flush with right edge, icon centered
|
// X button — flush with right edge, icon centered
|
||||||
{
|
{
|
||||||
ImVec2 xMin(rowMax.x - xZoneW, rowMin.y);
|
ImVec2 xMin(rowMax.x - xZoneW, rowMin.y);
|
||||||
@@ -467,11 +470,14 @@ void RenderMiningModeToggle(App* app, const WalletState& state, const MiningInfo
|
|||||||
pdl->AddRectFilled(rowMin, rowMax, IM_COL32(255, 255, 255, 10));
|
pdl->AddRectFilled(rowMin, rowMax, IM_COL32(255, 255, 255, 10));
|
||||||
if (rowHov && !inXZone)
|
if (rowHov && !inXZone)
|
||||||
pdl->AddRectFilled(rowMin, rowMax, StateHover());
|
pdl->AddRectFilled(rowMin, rowMax, StateHover());
|
||||||
// Full address text with internal padding
|
// Address text with internal padding, middle-truncated so a full z-address
|
||||||
|
// (~78 chars) can't run under the trailing X (delete) button.
|
||||||
float textY = rowMin.y + (wRowH - wRowFontSz) * 0.5f;
|
float textY = rowMin.y + (wRowH - wRowFontSz) * 0.5f;
|
||||||
|
float wMaxTextW = wPopupInnerW - wXZoneW - wTextPadX * 2.0f;
|
||||||
|
std::string addrDisp = material::TruncateToWidth(addr, wRowFont, wRowFontSz, wMaxTextW);
|
||||||
pdl->AddText(wRowFont, wRowFontSz,
|
pdl->AddText(wRowFont, wRowFontSz,
|
||||||
ImVec2(rowMin.x + wTextPadX, textY),
|
ImVec2(rowMin.x + wTextPadX, textY),
|
||||||
isCurrent ? Primary() : OnSurface(), addr.c_str());
|
isCurrent ? Primary() : OnSurface(), addrDisp.c_str());
|
||||||
// Tooltip for long addresses
|
// Tooltip for long addresses
|
||||||
if (rowHov && !inXZone)
|
if (rowHov && !inXZone)
|
||||||
material::Tooltip("%s", addr.c_str());
|
material::Tooltip("%s", addr.c_str());
|
||||||
|
|||||||
@@ -30,14 +30,6 @@ const std::vector<KnownPool>& knownPools()
|
|||||||
"https://pool.dragonx.is/api/stats", PoolStatsSchema::DragonXIs,
|
"https://pool.dragonx.is/api/stats", PoolStatsSchema::DragonXIs,
|
||||||
/*miningcorePoolId=*/"", /*feePercent=*/0.0, /*official=*/true,
|
/*miningcorePoolId=*/"", /*feePercent=*/0.0, /*official=*/true,
|
||||||
},
|
},
|
||||||
KnownPool{
|
|
||||||
// The mining (stratum) host is us.dragonx.cc — pool.dragonx.cc is the
|
|
||||||
// Cloudflare-proxied web/API host and does NOT accept stratum on :3333.
|
|
||||||
// Stats still come from pool.dragonx.cc/api/pools (proxied HTTP is fine).
|
|
||||||
"dragonx-cc-pplns", "pool.dragonx.cc", "us.dragonx.cc:3333", "rx/dragonx",
|
|
||||||
"https://pool.dragonx.cc/api/pools", PoolStatsSchema::Miningcore,
|
|
||||||
/*miningcorePoolId=*/"dragonx-pplns", /*feePercent=*/3.0, /*official=*/true,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
return pools;
|
return pools;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3434,9 +3434,11 @@ void testBalanceAddressListModel()
|
|||||||
EXPECT_NEAR(layout.contentStartX, 22.0, 0.0001);
|
EXPECT_NEAR(layout.contentStartX, 22.0, 0.0001);
|
||||||
EXPECT_NEAR(layout.contentStartY, 26.0, 0.0001);
|
EXPECT_NEAR(layout.contentStartY, 26.0, 0.0001);
|
||||||
EXPECT_NEAR(layout.buttonSize, 38.0, 0.0001);
|
EXPECT_NEAR(layout.buttonSize, 38.0, 0.0001);
|
||||||
EXPECT_NEAR(layout.favoriteButton.x, 270.0, 0.0001);
|
// Trailing (favorite) button is inset by rowPadLeft (12) so it mirrors the left margin
|
||||||
EXPECT_NEAR(layout.visibilityButton.x, 228.0, 0.0001);
|
// instead of hugging the card edge: 310 - 38 - 12 = 260.
|
||||||
EXPECT_NEAR(layout.contentRight, 224.0, 0.0001);
|
EXPECT_NEAR(layout.favoriteButton.x, 260.0, 0.0001);
|
||||||
|
EXPECT_NEAR(layout.visibilityButton.x, 218.0, 0.0001);
|
||||||
|
EXPECT_NEAR(layout.contentRight, 214.0, 0.0001);
|
||||||
EXPECT_EQ(dragonx::ui::FormatAddressUsdValue(2.0, 3.5), std::string("$7.00"));
|
EXPECT_EQ(dragonx::ui::FormatAddressUsdValue(2.0, 3.5), std::string("$7.00"));
|
||||||
EXPECT_EQ(dragonx::ui::FormatAddressUsdValue(0.001, 2.0), std::string("$0.002000"));
|
EXPECT_EQ(dragonx::ui::FormatAddressUsdValue(0.001, 2.0), std::string("$0.002000"));
|
||||||
EXPECT_EQ(dragonx::ui::FormatAddressUsdValue(0.0, 2.0), std::string(""));
|
EXPECT_EQ(dragonx::ui::FormatAddressUsdValue(0.0, 2.0), std::string(""));
|
||||||
@@ -5774,23 +5776,20 @@ void testXmrigLiveInstall()
|
|||||||
void testPoolRegistryLookup()
|
void testPoolRegistryLookup()
|
||||||
{
|
{
|
||||||
using namespace dragonx::util;
|
using namespace dragonx::util;
|
||||||
EXPECT_EQ(knownPools().size(), static_cast<std::size_t>(2));
|
// pool.dragonx.cc was removed from the built-in defaults; pool.dragonx.is is the sole entry.
|
||||||
|
EXPECT_EQ(knownPools().size(), static_cast<std::size_t>(1));
|
||||||
|
|
||||||
// The algo follows the pool. Note pool.dragonx.cc's stratum host is us.dragonx.cc
|
// The algo follows the pool: pool.dragonx.is resolves to rx/hush regardless of the caller default.
|
||||||
// (the .cc domain is only the Cloudflare-proxied web/API host).
|
|
||||||
EXPECT_EQ(resolvePoolAlgo("us.dragonx.cc:3333", "rx/hush"), std::string("rx/dragonx"));
|
|
||||||
EXPECT_EQ(resolvePoolAlgo("pool.dragonx.is:3433", "rx/dragonx"), std::string("rx/hush"));
|
EXPECT_EQ(resolvePoolAlgo("pool.dragonx.is:3433", "rx/dragonx"), std::string("rx/hush"));
|
||||||
// Bare host (no port) still matches; scheme + path are tolerated.
|
// The former us.dragonx.cc (pool.dragonx.cc) host is now unknown -> caller's fallback algo.
|
||||||
EXPECT_EQ(resolvePoolAlgo("us.dragonx.cc", "rx/hush"), std::string("rx/dragonx"));
|
EXPECT_EQ(resolvePoolAlgo("us.dragonx.cc:3333", "rx/hush"), std::string("rx/hush"));
|
||||||
EXPECT_EQ(resolvePoolAlgo("stratum+tcp://us.dragonx.cc:3333/x", "rx/hush"),
|
|
||||||
std::string("rx/dragonx"));
|
|
||||||
// Unknown host -> fallback algo.
|
|
||||||
EXPECT_EQ(resolvePoolAlgo("my.pool.example:1234", "rx/hush"), std::string("rx/hush"));
|
EXPECT_EQ(resolvePoolAlgo("my.pool.example:1234", "rx/hush"), std::string("rx/hush"));
|
||||||
|
|
||||||
EXPECT_TRUE(findKnownPoolByUrl("us.dragonx.cc:3333") != nullptr);
|
EXPECT_TRUE(findKnownPoolByUrl("pool.dragonx.is:3433") != nullptr);
|
||||||
|
EXPECT_TRUE(findKnownPoolByUrl("us.dragonx.cc:3333") == nullptr); // removed built-in default
|
||||||
EXPECT_TRUE(findKnownPoolByUrl("unknown.host:1") == nullptr);
|
EXPECT_TRUE(findKnownPoolByUrl("unknown.host:1") == nullptr);
|
||||||
// A mismatched explicit port must NOT match a known pool.
|
// A mismatched explicit port must NOT match a known pool.
|
||||||
EXPECT_TRUE(findKnownPoolByUrl("us.dragonx.cc:9999") == nullptr);
|
EXPECT_TRUE(findKnownPoolByUrl("pool.dragonx.is:9999") == nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Schema-aware pool hashrate parsing (the two pools speak different APIs).
|
// Schema-aware pool hashrate parsing (the two pools speak different APIs).
|
||||||
|
|||||||
Reference in New Issue
Block a user