feat(settings): rebuild Node data-dir + Daemon binary on the new components
Step 3. Node "Data Dir" is now a clean key/value block: the directory on its own row as a click-to-open accent link with a copy button, the wallet size below, and the folder buttons on their own row (icon ActionButtons) — no more path font-scaling or right-align overflow math. Daemon binary shows Installed / Bundled as key/value rows plus a Success/Warning status chip (replacing the fragile atom-by-atom wrapped status line); "Check for updates…" is now the accent Primary action, and the maintenance actions are a separate row with Delete Blockchain styled Destructive (Test/Rescan/Repair Secondary). All handlers, disabled-state predicates and tooltips are preserved; the buttons wrap instead of overflowing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1993,103 +1993,64 @@ void RenderSettingsPage(App* app) {
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("node"));
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
{
|
||||
std::string dirPath = util::Platform::getDragonXDataDir();
|
||||
std::string walletPath = dirPath + "wallet.dat";
|
||||
uint64_t wallet_size = util::Platform::getFileSize(walletPath);
|
||||
std::string size_str = (wallet_size > 0)
|
||||
? util::Platform::formatFileSize(wallet_size) : TR("settings_not_found");
|
||||
const std::string dirPath = util::Platform::getDragonXDataDir();
|
||||
const std::string walletPath = dirPath + "wallet.dat";
|
||||
const uint64_t wallet_size = util::Platform::getFileSize(walletPath);
|
||||
const std::string size_str = (wallet_size > 0)
|
||||
? util::Platform::formatFileSize(wallet_size) : std::string(TR("settings_not_found"));
|
||||
const float leftX = ImGui::GetCursorPosX();
|
||||
const float labelW = std::max(ImGui::CalcTextSize(TR("settings_data_dir")).x,
|
||||
ImGui::CalcTextSize(TR("settings_wallet_size_label")).x)
|
||||
+ Layout::spacingLg();
|
||||
const ImU32 metaCol = OnSurfaceMedium();
|
||||
|
||||
// Right-aligned "Open data folder" button trailing the row; the path
|
||||
// gets whatever width is left after the data-dir label, wallet-size
|
||||
// label+value, and the trailing button.
|
||||
ImFont* nodeBtnFont = S.resolveFont("button");
|
||||
if (!nodeBtnFont) nodeBtnFont = Type().button();
|
||||
// Measure with the BUTTON font (not the current font) so the width matches
|
||||
// the real TactileButton size — the old estimate mis-measured and the button
|
||||
// overflowed the card's right edge, worse at high DPI.
|
||||
float openBtnW = nodeBtnFont->CalcTextSizeA(nodeBtnFont->LegacySize, FLT_MAX, 0,
|
||||
TR("settings_open_data_dir")).x + ImGui::GetStyle().FramePadding.x * 2.0f;
|
||||
// "Open app folder" (the ObsidianDragon config folder), left of the data button.
|
||||
float appBtnW = nodeBtnFont->CalcTextSizeA(nodeBtnFont->LegacySize, FLT_MAX, 0,
|
||||
TR("settings_open_app_dir")).x + ImGui::GetStyle().FramePadding.x * 2.0f;
|
||||
float openGroupW = appBtnW + spMd + openBtnW;
|
||||
float dataDirLabelW = ImGui::CalcTextSize(TR("settings_data_dir")).x;
|
||||
float sizeLabelW = ImGui::CalcTextSize(TR("settings_wallet_size_label")).x;
|
||||
float sizeValueW = ImGui::CalcTextSize(size_str.c_str()).x;
|
||||
// Space reserved to the right of the path for the size block + both buttons.
|
||||
float trailW = sizeLabelW + Layout::spacingXs() + sizeValueW + spMd + openGroupW;
|
||||
float availForPath = contentW - dataDirLabelW - Layout::spacingXs() - spMd - trailW;
|
||||
if (availForPath < 60.0f) availForPath = contentW - dataDirLabelW - Layout::spacingXs();
|
||||
|
||||
ImVec4 linkCol = ImGui::ColorConvertU32ToFloat4(Primary());
|
||||
ImVec4 linkHoverCol = linkCol;
|
||||
linkHoverCol.w = std::min(1.0f, linkCol.w + 0.2f);
|
||||
|
||||
float rowTopY = ImGui::GetCursorScreenPos().y; // top of the data-dir row
|
||||
// Row 1: Data directory — a clickable link (opens the folder) + a copy button.
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, metaCol);
|
||||
ImGui::TextUnformatted(TR("settings_data_dir"));
|
||||
ImGui::SameLine(0, Layout::spacingXs());
|
||||
ImVec2 pathSize = ImGui::CalcTextSize(dirPath.c_str());
|
||||
bool hovered = false;
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::SameLine(0, 0);
|
||||
ImGui::SetCursorPosX(leftX + labelW);
|
||||
ImGui::AlignTextToFramePadding();
|
||||
if (pathSize.x > availForPath && availForPath > 0) {
|
||||
float scale = availForPath / pathSize.x;
|
||||
float condensedSize = body2Info->LegacySize * std::max(scale, 0.65f);
|
||||
ImGui::SetWindowFontScale(condensedSize / body2Info->LegacySize);
|
||||
ImGui::TextColored(linkCol, "%s", dirPath.c_str());
|
||||
hovered = ImGui::IsItemHovered();
|
||||
if (hovered) {
|
||||
ImVec2 tMin = ImGui::GetItemRectMin();
|
||||
ImVec2 tMax = ImGui::GetItemRectMax();
|
||||
dl->AddLine(ImVec2(tMin.x, tMax.y), ImVec2(tMax.x, tMax.y), ImGui::GetColorU32(linkHoverCol));
|
||||
}
|
||||
ImGui::SetWindowFontScale(1.0f);
|
||||
} else {
|
||||
ImGui::TextColored(linkCol, "%s", dirPath.c_str());
|
||||
hovered = ImGui::IsItemHovered();
|
||||
if (hovered) {
|
||||
ImVec2 tMin = ImGui::GetItemRectMin();
|
||||
ImVec2 tMax = ImGui::GetItemRectMax();
|
||||
dl->AddLine(ImVec2(tMin.x, tMax.y), ImVec2(tMax.x, tMax.y), ImGui::GetColorU32(linkHoverCol));
|
||||
}
|
||||
}
|
||||
if (hovered) {
|
||||
material::Tooltip("%s", TR("tt_open_dir"));
|
||||
ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(Primary()), "%s", dirPath.c_str());
|
||||
if (ImGui::IsItemHovered()) {
|
||||
const ImVec2 tmn = ImGui::GetItemRectMin(), tmx = ImGui::GetItemRectMax();
|
||||
dl->AddLine(ImVec2(tmn.x, tmx.y), ImVec2(tmx.x, tmx.y), Primary());
|
||||
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
||||
material::Tooltip("%s", TR("tt_open_dir"));
|
||||
}
|
||||
if (ImGui::IsItemClicked()) util::Platform::openFolder(dirPath);
|
||||
ImGui::SameLine(0, Layout::spacingSm());
|
||||
{
|
||||
material::IconButtonStyle cs;
|
||||
cs.hoverBg = material::WithAlpha(OnSurface(), 30);
|
||||
cs.tooltip = TR("copy");
|
||||
const float ih = ImGui::GetFrameHeight();
|
||||
if (material::IconButton("##copydir", ICON_MD_CONTENT_COPY, Type().iconSmall(), ImVec2(ih, ih), cs))
|
||||
ImGui::SetClipboardText(dirPath.c_str());
|
||||
}
|
||||
if (ImGui::IsItemClicked())
|
||||
util::Platform::openFolder(dirPath);
|
||||
|
||||
// Wallet Size label + value, trailing the path on the same row.
|
||||
ImGui::SameLine(0, spMd);
|
||||
// Row 2: Wallet size.
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, metaCol);
|
||||
ImGui::TextUnformatted(TR("settings_wallet_size_label"));
|
||||
ImGui::SameLine(0, Layout::spacingXs());
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::SameLine(0, 0);
|
||||
ImGui::SetCursorPosX(leftX + labelW);
|
||||
ImGui::AlignTextToFramePadding();
|
||||
if (wallet_size > 0)
|
||||
ImGui::TextUnformatted(size_str.c_str());
|
||||
else
|
||||
ImGui::TextDisabled("%s", TR("settings_not_found"));
|
||||
if (wallet_size > 0) ImGui::TextUnformatted(size_str.c_str());
|
||||
else ImGui::TextDisabled("%s", TR("settings_not_found"));
|
||||
|
||||
// Folder buttons: [Open app folder] [Open data folder], right-aligned as a
|
||||
// group on the data-dir row (rowTopY), each an EXPLICIT width so the group's
|
||||
// right edge lands exactly on the card edge (no overflow at any DPI).
|
||||
float rowY = rowTopY;
|
||||
float rightEdge = sectionOrigin.x + contentW;
|
||||
float groupX = rightEdge - openGroupW;
|
||||
float afterSizeX = ImGui::GetItemRectMax().x + spMd;
|
||||
ImGui::SameLine(0, spMd);
|
||||
if (groupX > afterSizeX)
|
||||
ImGui::SetCursorScreenPos(ImVec2(groupX, rowY));
|
||||
// App (ObsidianDragon config) folder — opens ~/.config/ObsidianDragon etc.
|
||||
if (TactileButton(TR("settings_open_app_dir"), ImVec2(appBtnW, 0), nodeBtnFont)) {
|
||||
// Row 3: folder buttons (their own row so the path gets the full width).
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
material::ButtonFlow ff(contentW);
|
||||
ff.next(material::ActionButtonWidth(TR("settings_open_app_dir"), ICON_MD_FOLDER));
|
||||
if (material::ActionButton("##openapp", TR("settings_open_app_dir"), ICON_MD_FOLDER, material::ActionTier::Secondary))
|
||||
util::Platform::openFolder(util::Platform::getObsidianDragonDir());
|
||||
}
|
||||
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_open_app_dir"));
|
||||
ImGui::SameLine(0, spMd);
|
||||
if (TactileButton(TR("settings_open_data_dir"), ImVec2(openBtnW, 0), nodeBtnFont)) {
|
||||
ff.next(material::ActionButtonWidth(TR("settings_open_data_dir"), ICON_MD_FOLDER_OPEN));
|
||||
if (material::ActionButton("##opendata", TR("settings_open_data_dir"), ICON_MD_FOLDER_OPEN, material::ActionTier::Secondary))
|
||||
util::Platform::openFolder(dirPath);
|
||||
}
|
||||
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_open_data_dir"));
|
||||
}
|
||||
|
||||
@@ -2218,100 +2179,90 @@ void RenderSettingsPage(App* app) {
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("daemon_binary"));
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
|
||||
// Status line: Installed <v> (<size> · <date>) · Bundled <v> (<size>) · <status>.
|
||||
// Built as color-tagged atoms and laid out with manual wrapping — as one long inline
|
||||
// row of SameLine() Text calls it overran the content width and clipped its colored
|
||||
// tail at narrow windows / higher font scale (the status was effectively hidden).
|
||||
struct DbAtom { std::string text; ImU32 col; bool sep; };
|
||||
std::vector<DbAtom> dbAtoms;
|
||||
const ImU32 dbNorm = ImGui::GetColorU32(ImGuiCol_Text);
|
||||
const ImU32 dbDimU = OnSurfaceMedium();
|
||||
|
||||
dbAtoms.push_back({ TR("daemon_installed"), dbNorm, false });
|
||||
// Version info (Installed / Bundled) as key/value rows, then a status chip.
|
||||
const float dLeftX = ImGui::GetCursorPosX();
|
||||
const float dLabelW = std::max(ImGui::CalcTextSize(TR("daemon_installed")).x,
|
||||
ImGui::CalcTextSize(TR("daemon_bundled")).x) + Layout::spacingLg();
|
||||
auto dkv = [&](const char* label, const std::string& value, bool dim) {
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, OnSurfaceMedium());
|
||||
ImGui::TextUnformatted(label);
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::SameLine(0, 0);
|
||||
ImGui::SetCursorPosX(dLeftX + dLabelW);
|
||||
ImGui::AlignTextToFramePadding();
|
||||
if (dim) ImGui::TextDisabled("%s", value.c_str());
|
||||
else ImGui::TextUnformatted(value.c_str());
|
||||
};
|
||||
if (inst.exists) {
|
||||
dbAtoms.push_back({ inst.version.empty() ? std::string(TR("unknown")) : inst.version, dbNorm, false });
|
||||
char dbParen[96];
|
||||
std::snprintf(dbParen, sizeof(dbParen), "(%s · %s)",
|
||||
char ibuf[176];
|
||||
std::snprintf(ibuf, sizeof(ibuf), "%s \xC2\xB7 %s \xC2\xB7 %s",
|
||||
inst.version.empty() ? TR("unknown") : inst.version.c_str(),
|
||||
util::Platform::formatFileSize(inst.size).c_str(),
|
||||
fmtDate(inst.modifiedEpoch).c_str());
|
||||
dbAtoms.push_back({ dbParen, dbDimU, false });
|
||||
dkv(TR("daemon_installed"), ibuf, false);
|
||||
} else {
|
||||
dbAtoms.push_back({ TR("daemon_not_installed"), dbDimU, false });
|
||||
dkv(TR("daemon_installed"), TR("daemon_not_installed"), true);
|
||||
}
|
||||
dbAtoms.push_back({ "·", dbDimU, true });
|
||||
dbAtoms.push_back({ TR("daemon_bundled"), dbNorm, false });
|
||||
if (bun.available) {
|
||||
dbAtoms.push_back({ bun.version.empty() ? std::string(TR("unknown")) : bun.version, dbNorm, false });
|
||||
dbAtoms.push_back({ "(" + util::Platform::formatFileSize(bun.size) + ")", dbDimU, false });
|
||||
char bbuf[144];
|
||||
std::snprintf(bbuf, sizeof(bbuf), "%s \xC2\xB7 %s",
|
||||
bun.version.empty() ? TR("unknown") : bun.version.c_str(),
|
||||
util::Platform::formatFileSize(bun.size).c_str());
|
||||
dkv(TR("daemon_bundled"), bbuf, false);
|
||||
} else {
|
||||
dbAtoms.push_back({ TR("daemon_none_bundled"), dbDimU, false });
|
||||
dkv(TR("daemon_bundled"), TR("daemon_none_bundled"), true);
|
||||
}
|
||||
if (bun.available) {
|
||||
const bool sameSize = inst.exists && inst.size == bun.size;
|
||||
dbAtoms.push_back({ "·", dbDimU, true });
|
||||
if (!inst.exists)
|
||||
dbAtoms.push_back({ TR("daemon_status_missing"), ImGui::ColorConvertFloat4ToU32(ImVec4(1.0f, 0.8f, 0.2f, 1.0f)), false });
|
||||
else if (sameSize)
|
||||
dbAtoms.push_back({ TR("daemon_status_match"), ImGui::ColorConvertFloat4ToU32(ImVec4(0.3f, 0.8f, 0.3f, 1.0f)), false });
|
||||
else
|
||||
dbAtoms.push_back({ TR("daemon_status_differ"), ImGui::ColorConvertFloat4ToU32(ImVec4(1.0f, 0.8f, 0.2f, 1.0f)), false });
|
||||
const char* chipTxt = !inst.exists ? TR("daemon_status_missing")
|
||||
: sameSize ? TR("daemon_status_match")
|
||||
: TR("daemon_status_differ");
|
||||
const ImU32 chipCol = sameSize ? Success() : Warning();
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
const float dp = Layout::dpiScale();
|
||||
const ImVec2 cp = ImGui::GetCursorScreenPos();
|
||||
const float chpad = 8.0f * dp, chh = ImGui::GetFrameHeight();
|
||||
const ImVec2 cts = ImGui::CalcTextSize(chipTxt);
|
||||
const float chw = cts.x + chpad * 2.0f;
|
||||
dl->AddRectFilled(cp, ImVec2(cp.x + chw, cp.y + chh), material::WithAlpha(chipCol, 38), chh * 0.4f);
|
||||
dl->AddRect(cp, ImVec2(cp.x + chw, cp.y + chh), material::WithAlpha(chipCol, 120), chh * 0.4f, 0, 1.0f);
|
||||
dl->AddText(ImVec2(cp.x + chpad, cp.y + (chh - cts.y) * 0.5f), chipCol, chipTxt);
|
||||
ImGui::Dummy(ImVec2(chw, chh));
|
||||
}
|
||||
|
||||
// Lay the atoms out left-to-right, wrapping to a new line when the next one would
|
||||
// exceed the content width. A "·" separator is dropped when it (plus the atom after
|
||||
// it) doesn't fit, so a wrapped line never starts or ends with a dangling separator.
|
||||
const float dbAvail = ImGui::GetContentRegionAvail().x;
|
||||
const float dbGap = Layout::spacingXs();
|
||||
float dbX = 0.0f;
|
||||
ImGui::AlignTextToFramePadding();
|
||||
for (std::size_t i = 0; i < dbAtoms.size(); ++i) {
|
||||
const float w = ImGui::CalcTextSize(dbAtoms[i].text.c_str()).x;
|
||||
if (dbAtoms[i].sep) {
|
||||
const float nextW = (i + 1 < dbAtoms.size())
|
||||
? ImGui::CalcTextSize(dbAtoms[i + 1].text.c_str()).x : 0.0f;
|
||||
if (dbX > 0.0f && dbX + dbGap + w + dbGap + nextW > dbAvail) continue;
|
||||
}
|
||||
const bool wrap = (dbX > 0.0f) && (dbX + dbGap + w > dbAvail);
|
||||
if (dbX > 0.0f && !wrap) { ImGui::SameLine(0, dbGap); dbX += dbGap; }
|
||||
else if (wrap) { dbX = 0.0f; }
|
||||
ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(dbAtoms[i].col), "%s", dbAtoms[i].text.c_str());
|
||||
dbX += w;
|
||||
}
|
||||
|
||||
// In-app node update: download + verify the latest dragonxd from the project Gitea.
|
||||
// Refresh the cached daemon info once an install has completed.
|
||||
// Refresh the cached daemon info once an in-app install has completed.
|
||||
if (ui::DaemonUpdateDialog::consumeInstalled())
|
||||
s_settingsState.daemon_info_loaded = false;
|
||||
|
||||
// Action row: Check for updates | Refresh | Install bundled on the left,
|
||||
// with the four maintenance actions right-aligned on the same row.
|
||||
// Update actions: Check for updates (primary) | Refresh | Install bundled.
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
float daemonBtnRowY = ImGui::GetCursorScreenPos().y;
|
||||
if (TactileButton(TR("daemon_update_check"), ImVec2(0, 0), dbBtnFont)) {
|
||||
ui::DaemonUpdateDialog::show(app, inst.exists ? inst.version : std::string());
|
||||
{
|
||||
using AT = material::ActionTier;
|
||||
material::ButtonFlow uf(contentW);
|
||||
uf.next(material::ActionButtonWidth(TR("daemon_update_check"), ICON_MD_SYSTEM_UPDATE));
|
||||
if (material::ActionButton("##dupd", TR("daemon_update_check"), ICON_MD_SYSTEM_UPDATE, AT::Primary))
|
||||
ui::DaemonUpdateDialog::show(app, inst.exists ? inst.version : std::string());
|
||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) material::Tooltip("%s", TR("tt_daemon_update_check"));
|
||||
uf.next(material::ActionButtonWidth(TR("refresh"), ICON_MD_REFRESH));
|
||||
if (material::ActionButton("##drefresh", TR("refresh"), ICON_MD_REFRESH, AT::Secondary))
|
||||
s_settingsState.daemon_info_loaded = false;
|
||||
ImGui::BeginDisabled(!app->isUsingEmbeddedDaemon() || !bun.available);
|
||||
uf.next(material::ActionButtonWidth(TR("daemon_install_bundled"), ICON_MD_DOWNLOAD));
|
||||
if (material::ActionButton("##dinst", TR("daemon_install_bundled"), ICON_MD_DOWNLOAD, AT::Secondary))
|
||||
s_settingsState.confirm_reinstall_daemon = true;
|
||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) material::Tooltip("%s", TR("tt_daemon_install_bundled"));
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) material::Tooltip("%s", TR("tt_daemon_update_check"));
|
||||
ImGui::SameLine(0, spMd);
|
||||
// Unique ImGui ID (label still displays "Refresh") — the theme sections
|
||||
// also have "Refresh" buttons, so a bare label collides on this page.
|
||||
if (TactileButton((std::string(TR("refresh")) + "##daemonRefresh").c_str(), ImVec2(0, 0), dbBtnFont)) {
|
||||
s_settingsState.daemon_info_loaded = false;
|
||||
}
|
||||
ImGui::SameLine(0, spMd);
|
||||
ImGui::BeginDisabled(!app->isUsingEmbeddedDaemon() || !bun.available);
|
||||
if (TactileButton(TR("daemon_install_bundled"), ImVec2(0, 0), dbBtnFont)) {
|
||||
s_settingsState.confirm_reinstall_daemon = true;
|
||||
}
|
||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) material::Tooltip("%s", TR("tt_daemon_install_bundled"));
|
||||
ImGui::EndDisabled();
|
||||
|
||||
// The four maintenance actions (Test connection / Rescan / Delete blockchain
|
||||
// / Repair wallet), right-aligned on the SAME row as Install bundled — no
|
||||
// separate "Advanced" toggle. Every BeginDisabled/EndDisabled predicate is
|
||||
// preserved exactly. Wraps to a right-aligned next row when too narrow.
|
||||
auto renderDaemonMaintenanceButtons = [&]() {
|
||||
// Maintenance actions: Test / Rescan / Repair | Delete blockchain (destructive).
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
{
|
||||
using AT = material::ActionTier;
|
||||
material::ButtonFlow mf(contentW);
|
||||
ImGui::BeginDisabled(!app->isConnected());
|
||||
if (TactileButton(TR("test_connection"), ImVec2(0, 0), dbBtnFont)) {
|
||||
mf.next(material::ActionButtonWidth(TR("test_connection"), ICON_MD_LINK));
|
||||
if (material::ActionButton("##dtest", TR("test_connection"), ICON_MD_LINK, AT::Secondary)) {
|
||||
if (app->rpc() && app->rpc()->isConnected() && app->worker()) {
|
||||
app->worker()->post([rpc = app->rpc()]() -> rpc::RPCWorker::MainCb {
|
||||
try {
|
||||
@@ -2328,52 +2279,24 @@ void RenderSettingsPage(App* app) {
|
||||
}
|
||||
}
|
||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) material::Tooltip("%s", TR("tt_test_conn"));
|
||||
ImGui::EndDisabled();
|
||||
ImGui::SameLine(0, spMd);
|
||||
ImGui::BeginDisabled(!app->isConnected());
|
||||
if (TactileButton(TR("rescan"), ImVec2(0, 0), dbBtnFont)) {
|
||||
mf.next(material::ActionButtonWidth(TR("rescan"), ICON_MD_REFRESH));
|
||||
if (material::ActionButton("##drescan", TR("rescan"), ICON_MD_REFRESH, AT::Secondary)) {
|
||||
s_settingsState.confirm_rescan = true;
|
||||
// Re-probe the available block range each time the dialog is opened.
|
||||
s_settingsState.rescan_height_detecting = false;
|
||||
s_settingsState.rescan_height_detected = false;
|
||||
}
|
||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) material::Tooltip("%s", TR("tt_rescan"));
|
||||
ImGui::EndDisabled();
|
||||
ImGui::SameLine(0, spMd);
|
||||
ImGui::BeginDisabled(!app->isUsingEmbeddedDaemon());
|
||||
if (TactileButton(TR("delete_blockchain"), ImVec2(0, 0), dbBtnFont)) {
|
||||
s_settingsState.confirm_delete_blockchain = true;
|
||||
}
|
||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) material::Tooltip("%s", TR("tt_delete_blockchain"));
|
||||
ImGui::SameLine(0, spMd);
|
||||
if (TactileButton(TR("repair_wallet"), ImVec2(0, 0), dbBtnFont)) {
|
||||
mf.next(material::ActionButtonWidth(TR("repair_wallet"), ICON_MD_HEALING));
|
||||
if (material::ActionButton("##drepair", TR("repair_wallet"), ICON_MD_HEALING, AT::Secondary))
|
||||
s_settingsState.confirm_repair_wallet = true;
|
||||
}
|
||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) material::Tooltip("%s", TR("tt_repair_wallet"));
|
||||
mf.next(material::ActionButtonWidth(TR("delete_blockchain"), ICON_MD_DELETE));
|
||||
if (material::ActionButton("##ddelete", TR("delete_blockchain"), ICON_MD_DELETE, AT::Destructive))
|
||||
s_settingsState.confirm_delete_blockchain = true;
|
||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) material::Tooltip("%s", TR("tt_delete_blockchain"));
|
||||
ImGui::EndDisabled();
|
||||
};
|
||||
{
|
||||
// Group width (button font) to right-align the four maintenance actions.
|
||||
const char* mLabels[] = { TR("test_connection"), TR("rescan"),
|
||||
TR("delete_blockchain"), TR("repair_wallet") };
|
||||
float groupW = spMd * 3.0f;
|
||||
for (const char* l : mLabels)
|
||||
groupW += dbBtnFont->CalcTextSizeA(dbBtnFont->LegacySize, FLT_MAX, 0, l).x
|
||||
+ ImGui::GetStyle().FramePadding.x * 2.0f;
|
||||
float afterTestX = ImGui::GetItemRectMax().x + spMd; // right edge of Install bundled
|
||||
float rightEdge = sectionOrigin.x + contentW;
|
||||
float groupX = rightEdge - groupW;
|
||||
if (groupX >= afterTestX) {
|
||||
// Fits on the Install-bundled row, right-aligned.
|
||||
ImGui::SameLine(0, 0);
|
||||
ImGui::SetCursorScreenPos(ImVec2(groupX, daemonBtnRowY));
|
||||
} else {
|
||||
// Too narrow: wrap to the next row, right-aligned (clamped to left edge).
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
float wrapX = std::max(sectionOrigin.x, rightEdge - groupW);
|
||||
ImGui::SetCursorScreenPos(ImVec2(wrapX, ImGui::GetCursorScreenPos().y));
|
||||
}
|
||||
renderDaemonMaintenanceButtons();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user