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
This commit is contained in:
@@ -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]
|
||||
|
||||
|
||||
@@ -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))) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user