From 8645a82e4fd70c6831b3a81805ae466bbf9b580a Mon Sep 17 00:00:00 2001 From: dan_s Date: Thu, 19 Mar 2026 06:10:46 -0500 Subject: [PATCH] feat: sync thread grid during idle scaling, skip lock screen while pool mining, add paste preview to import key dialog - Mining tab: sync s_selected_threads with actual thread count when idle thread scaling adjusts threads (solo via genproclimit, pool via threads_active), skipping sync during user drag - Auto-lock: bypass lock screen overlay when xmrig pool mining is active so the mining UI remains accessible - Import key dialog: add clipboard hover preview with transparent overlay on the input field, inline key type validation next to title (matching send tab paste button pattern), configurable via ui.toml --- res/themes/ui.toml | 2 + src/app.cpp | 5 +- src/ui/windows/import_key_dialog.cpp | 147 +++++++++++++++++++-------- src/ui/windows/mining_tab.cpp | 9 ++ 4 files changed, 119 insertions(+), 44 deletions(-) diff --git a/res/themes/ui.toml b/res/themes/ui.toml index 5c95deb..5f9ae45 100644 --- a/res/themes/ui.toml +++ b/res/themes/ui.toml @@ -1168,6 +1168,8 @@ key-input = { height = 150 } rescan-height-input = { width = 100 } import-button = { width = 120, font = "button" } close-button = { width = 100, font = "button" } +paste-preview-alpha = { size = 0.3 } +paste-preview-max-chars = { size = 200 } [components] diff --git a/src/app.cpp b/src/app.cpp index bc00313..6ec31ab 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -1051,7 +1051,10 @@ void App::render() current_page_ != ui::NavPage::Settings); bool daemonReady = state_.connected; // don't gate on sync state - if (state_.isLocked()) { + // Don't show lock screen while pool mining — xmrig runs independently + // of the wallet and locking would block the mining UI needlessly. + bool poolMiningActive = xmrig_manager_ && xmrig_manager_->isRunning(); + if (state_.isLocked() && !poolMiningActive) { // Lock screen — covers tab content just like the loading overlay renderLockScreen(); } else if (pageNeedsDaemon && (!daemonReady || (state_.connected && !state_.encryption_state_known))) { diff --git a/src/ui/windows/import_key_dialog.cpp b/src/ui/windows/import_key_dialog.cpp index 92ad8d1..c317ed4 100644 --- a/src/ui/windows/import_key_dialog.cpp +++ b/src/ui/windows/import_key_dialog.cpp @@ -33,6 +33,8 @@ static std::string s_status; static int s_total_keys = 0; static int s_imported_keys = 0; static int s_failed_keys = 0; +static bool s_paste_previewing = false; +static std::string s_preview_text; // Helper to detect key type static std::string detectKeyType(const std::string& key) @@ -95,6 +97,8 @@ void ImportKeyDialog::show() s_total_keys = 0; s_imported_keys = 0; s_failed_keys = 0; + s_paste_previewing = false; + s_preview_text.clear(); } bool ImportKeyDialog::isOpen() @@ -134,6 +138,43 @@ void ImportKeyDialog::render(App* app) if (ImGui::IsItemHovered()) { ImGui::SetTooltip("%s", TR("import_key_tooltip")); } + + // Validation indicator inline with title — check preview text during hover, + // otherwise check the actual input + { + const char* checkSrc = s_key_input; + if (s_paste_previewing && !s_preview_text.empty()) + checkSrc = s_preview_text.c_str(); + if (checkSrc[0] != '\0') { + auto previewKeys = splitKeys(checkSrc); + int pz = 0, pt = 0, pu = 0; + for (const auto& k : previewKeys) { + std::string kt = detectKeyType(k); + if (kt == "z-spending") pz++; + else if (kt == "t-privkey") pt++; + else pu++; + } + if (pz > 0 || pt > 0) { + ImGui::SameLine(); + char vbuf[128]; + if (pz > 0 && pt > 0) + snprintf(vbuf, sizeof(vbuf), "%d shielded, %d transparent", pz, pt); + else if (pz > 0) + snprintf(vbuf, sizeof(vbuf), "%d shielded key(s)", pz); + else + snprintf(vbuf, sizeof(vbuf), "%d transparent key(s)", pt); + material::Type().textColored(material::TypeStyle::Caption, material::Success(), vbuf); + if (pu > 0) { + ImGui::SameLine(); + snprintf(vbuf, sizeof(vbuf), "(%d unrecognized)", pu); + material::Type().textColored(material::TypeStyle::Caption, material::Error(), vbuf); + } + } else if (pu > 0 && !s_paste_previewing) { + ImGui::SameLine(); + material::Type().textColored(material::TypeStyle::Caption, material::Error(), "Unrecognized key format"); + } + } + } if (s_importing) { ImGui::BeginDisabled(); @@ -142,62 +183,82 @@ void ImportKeyDialog::render(App* app) ImGui::SetNextItemWidth(-1); ImGui::InputTextMultiline("##KeyInput", s_key_input, sizeof(s_key_input), ImVec2(-1, keyInput.height > 0 ? keyInput.height : 150), ImGuiInputTextFlags_AllowTabInput); + ImVec2 inputMin = ImGui::GetItemRectMin(); + ImVec2 inputMax = ImGui::GetItemRectMax(); - // Paste button - if (material::StyledButton(TR("paste_from_clipboard"), ImVec2(0,0), S.resolveFont(importBtn.font))) { - const char* clipboard = ImGui::GetClipboardText(); - if (clipboard) { - std::string trimmed(clipboard); + // Detect paste button hover before drawing it + ImVec2 pasteBtnPos = ImGui::GetCursorScreenPos(); + // Estimate button size for hover detection + ImFont* btnFont = S.resolveFont(importBtn.font); + ImVec2 pasteBtnSize = btnFont + ? ImVec2(btnFont->CalcTextSizeA(btnFont->LegacySize, FLT_MAX, 0, TR("paste_from_clipboard")).x + + ImGui::GetStyle().FramePadding.x * 2, + ImGui::GetFrameHeight()) + : ImVec2(150, ImGui::GetFrameHeight()); + bool paste_hovered = material::IsRectHovered(pasteBtnPos, + ImVec2(pasteBtnPos.x + pasteBtnSize.x, pasteBtnPos.y + pasteBtnSize.y)); + + // Handle preview state + if (paste_hovered && !s_paste_previewing) { + const char* clip = ImGui::GetClipboardText(); + if (clip && clip[0] != '\0') { + std::string trimmed(clip); while (!trimmed.empty() && (trimmed.front() == ' ' || trimmed.front() == '\t' || trimmed.front() == '\n' || trimmed.front() == '\r')) trimmed.erase(trimmed.begin()); while (!trimmed.empty() && (trimmed.back() == ' ' || trimmed.back() == '\t' || trimmed.back() == '\n' || trimmed.back() == '\r')) trimmed.pop_back(); - snprintf(s_key_input, sizeof(s_key_input), "%s", trimmed.c_str()); + if (!trimmed.empty() && s_key_input[0] == '\0') { + s_preview_text = trimmed; + s_paste_previewing = true; + } + } + } else if (!paste_hovered && s_paste_previewing) { + s_paste_previewing = false; + s_preview_text.clear(); + } + + // Draw transparent preview text overlay on the input field + if (s_paste_previewing && !s_preview_text.empty()) { + ImVec2 textPos(inputMin.x + ImGui::GetStyle().FramePadding.x, + inputMin.y + ImGui::GetStyle().FramePadding.y); + ImVec4 previewCol = ImGui::GetStyleColorVec4(ImGuiCol_Text); + previewCol.w = S.drawElement("dialogs.import-key", "paste-preview-alpha").sizeOr(0.3f); + size_t maxChars = (size_t)S.drawElement("dialogs.import-key", "paste-preview-max-chars").sizeOr(200.0f); + // Clip to input rect + ImGui::GetWindowDrawList()->PushClipRect(inputMin, inputMax, true); + ImGui::GetWindowDrawList()->AddText(textPos, ImGui::ColorConvertFloat4ToU32(previewCol), + s_preview_text.c_str(), s_preview_text.c_str() + std::min(s_preview_text.size(), maxChars)); + ImGui::GetWindowDrawList()->PopClipRect(); + } + + // Paste button + if (material::StyledButton(TR("paste_from_clipboard"), ImVec2(0,0), S.resolveFont(importBtn.font))) { + if (s_paste_previewing) { + snprintf(s_key_input, sizeof(s_key_input), "%s", s_preview_text.c_str()); + s_paste_previewing = false; + s_preview_text.clear(); + } else { + const char* clipboard = ImGui::GetClipboardText(); + if (clipboard) { + std::string trimmed(clipboard); + while (!trimmed.empty() && (trimmed.front() == ' ' || trimmed.front() == '\t' || + trimmed.front() == '\n' || trimmed.front() == '\r')) + trimmed.erase(trimmed.begin()); + while (!trimmed.empty() && (trimmed.back() == ' ' || trimmed.back() == '\t' || + trimmed.back() == '\n' || trimmed.back() == '\r')) + trimmed.pop_back(); + snprintf(s_key_input, sizeof(s_key_input), "%s", trimmed.c_str()); + } } } ImGui::SameLine(); if (material::StyledButton(TR("clear"), ImVec2(0,0), S.resolveFont(importBtn.font))) { s_key_input[0] = '\0'; - } - - // Key validation indicator - if (s_key_input[0] != '\0') { - auto keys = splitKeys(s_key_input); - int zCount = 0, tCount = 0, unknownCount = 0; - for (const auto& key : keys) { - std::string kt = detectKeyType(key); - if (kt == "z-spending") zCount++; - else if (kt == "t-privkey") tCount++; - else unknownCount++; - } - if (zCount > 0 || tCount > 0) { - ImGui::PushFont(material::Type().iconSmall()); - material::Type().textColored(material::TypeStyle::Caption, material::Success(), ICON_MD_CHECK_CIRCLE); - ImGui::PopFont(); - ImGui::SameLine(0, 2.0f); - char validBuf[128]; - if (zCount > 0 && tCount > 0) - snprintf(validBuf, sizeof(validBuf), "%d shielded, %d transparent key(s)", zCount, tCount); - else if (zCount > 0) - snprintf(validBuf, sizeof(validBuf), "%d shielded key(s)", zCount); - else - snprintf(validBuf, sizeof(validBuf), "%d transparent key(s)", tCount); - material::Type().textColored(material::TypeStyle::Caption, material::Success(), validBuf); - if (unknownCount > 0) { - ImGui::SameLine(); - snprintf(validBuf, sizeof(validBuf), "(%d unrecognized)", unknownCount); - material::Type().textColored(material::TypeStyle::Caption, material::Error(), validBuf); - } - } else if (unknownCount > 0) { - ImGui::PushFont(material::Type().iconSmall()); - material::Type().textColored(material::TypeStyle::Caption, material::Error(), ICON_MD_ERROR); - ImGui::PopFont(); - ImGui::SameLine(0, 2.0f); - material::Type().textColored(material::TypeStyle::Caption, material::Error(), "Unrecognized key format"); - } + s_paste_previewing = false; + s_preview_text.clear(); } ImGui::Spacing(); diff --git a/src/ui/windows/mining_tab.cpp b/src/ui/windows/mining_tab.cpp index fd557f7..8af8872 100644 --- a/src/ui/windows/mining_tab.cpp +++ b/src/ui/windows/mining_tab.cpp @@ -140,6 +140,15 @@ void RenderMiningTab(App* app) s_threads_initialized = true; } + // Sync thread grid with actual count when idle thread scaling adjusts threads + if (app->settings()->getMineWhenIdle() && app->settings()->getIdleThreadScaling() && !s_drag_active) { + if (s_pool_mode && state.pool_mining.xmrig_running && state.pool_mining.threads_active > 0) { + s_selected_threads = std::min(state.pool_mining.threads_active, max_threads); + } else if (mining.generate && mining.genproclimit > 0) { + s_selected_threads = std::min(mining.genproclimit, max_threads); + } + } + ImDrawList* dl = ImGui::GetWindowDrawList(); GlassPanelSpec glassSpec; glassSpec.rounding = Layout::glassRounding();