diff --git a/res/fonts/NotoSansCJK-Subset.ttf b/res/fonts/NotoSansCJK-Subset.ttf index 5d58ab0..9551578 100644 Binary files a/res/fonts/NotoSansCJK-Subset.ttf and b/res/fonts/NotoSansCJK-Subset.ttf differ diff --git a/res/lang/de.json b/res/lang/de.json index 450dbc1..e799578 100644 --- a/res/lang/de.json +++ b/res/lang/de.json @@ -949,6 +949,9 @@ "mining_syncing_tooltip": "Blockchain synchronisiert...", "mining_tag": " · Mining", "mining_threads": "Mining-Threads", + "mining_threads_input_tooltip": "Genaue Thread-Anzahl eingeben (Enter zum Übernehmen)", + "mining_threads_minus_tooltip": "Weniger Threads", + "mining_threads_plus_tooltip": "Mehr Threads", "mining_to_save": "zum Speichern", "mining_today": "Heute", "mining_uptime": "Laufzeit", @@ -1785,6 +1788,7 @@ "xmrig_loading_releases": "Releases werden geladen…", "xmrig_none": "keiner", "xmrig_reinstall": "Neu installieren", + "xmrig_releases": "xmrig-Releases", "xmrig_stop_mining_first": "Stoppen Sie das Mining, bevor Sie den Miner aktualisieren.", "xmrig_unavailable_body": "Für diese Plattform ist kein Miner-Build verfügbar.", "xmrig_unavailable_title": "Miner-Updates nicht verfügbar", diff --git a/res/lang/es.json b/res/lang/es.json index 4bf7d17..0b912d7 100644 --- a/res/lang/es.json +++ b/res/lang/es.json @@ -949,6 +949,9 @@ "mining_syncing_tooltip": "El blockchain está sincronizando...", "mining_tag": " · Minería", "mining_threads": "Hilos de Minería", + "mining_threads_input_tooltip": "Escribe un número exacto de hilos (pulsa Enter para aplicar)", + "mining_threads_minus_tooltip": "Menos hilos", + "mining_threads_plus_tooltip": "Más hilos", "mining_to_save": "para guardar", "mining_today": "Hoy", "mining_uptime": "Tiempo activo", @@ -1785,6 +1788,7 @@ "xmrig_loading_releases": "Cargando versiones…", "xmrig_none": "ninguno", "xmrig_reinstall": "Reinstalar", + "xmrig_releases": "versiones de xmrig", "xmrig_stop_mining_first": "Detén la minería antes de actualizar el minero.", "xmrig_unavailable_body": "No hay ninguna versión del minero disponible para esta plataforma.", "xmrig_unavailable_title": "Actualizaciones del minero no disponibles", diff --git a/res/lang/fr.json b/res/lang/fr.json index 2030811..f28389b 100644 --- a/res/lang/fr.json +++ b/res/lang/fr.json @@ -949,6 +949,9 @@ "mining_syncing_tooltip": "La blockchain se synchronise...", "mining_tag": " · Minage", "mining_threads": "Threads de minage", + "mining_threads_input_tooltip": "Saisissez un nombre exact de threads (Entrée pour appliquer)", + "mining_threads_minus_tooltip": "Moins de threads", + "mining_threads_plus_tooltip": "Plus de threads", "mining_to_save": "pour enregistrer", "mining_today": "Aujourd'hui", "mining_uptime": "Temps de fonctionnement", @@ -1785,6 +1788,7 @@ "xmrig_loading_releases": "Chargement des versions…", "xmrig_none": "aucun", "xmrig_reinstall": "Réinstaller", + "xmrig_releases": "versions de xmrig", "xmrig_stop_mining_first": "Arrêtez le minage avant de mettre à jour le mineur.", "xmrig_unavailable_body": "Aucune version du mineur n'est disponible pour cette plateforme.", "xmrig_unavailable_title": "Mises à jour du mineur indisponibles", diff --git a/res/lang/ja.json b/res/lang/ja.json index 72e5117..c02d619 100644 --- a/res/lang/ja.json +++ b/res/lang/ja.json @@ -949,6 +949,9 @@ "mining_syncing_tooltip": "ブロックチェーン同期中...", "mining_tag": " · マイニング", "mining_threads": "マイニングスレッド", + "mining_threads_input_tooltip": "正確なスレッド数を入力(Enter で適用)", + "mining_threads_minus_tooltip": "スレッドを減らす", + "mining_threads_plus_tooltip": "スレッドを増やす", "mining_to_save": "保存する", "mining_today": "今日", "mining_uptime": "稼働時間", @@ -1782,6 +1785,7 @@ "xmrig_loading_releases": "リリースを読み込み中…", "xmrig_none": "なし", "xmrig_reinstall": "再インストール", + "xmrig_releases": "xmrig リリース", "xmrig_stop_mining_first": "マイナーを更新する前にマイニングを停止してください。", "xmrig_unavailable_body": "このプラットフォーム向けのマイナービルドは利用できません。", "xmrig_unavailable_title": "マイナーの更新は利用できません", diff --git a/res/lang/ko.json b/res/lang/ko.json index 05aa546..3970d60 100644 --- a/res/lang/ko.json +++ b/res/lang/ko.json @@ -948,6 +948,9 @@ "mining_syncing_tooltip": "블록체인 동기화 중...", "mining_tag": " · 채굴", "mining_threads": "채굴 스레드", + "mining_threads_input_tooltip": "정확한 스레드 수 입력 (Enter로 적용)", + "mining_threads_minus_tooltip": "스레드 줄이기", + "mining_threads_plus_tooltip": "스레드 늘리기", "mining_to_save": "저장하려면", "mining_today": "오늘", "mining_uptime": "가동 시간", @@ -1784,6 +1787,7 @@ "xmrig_loading_releases": "릴리스를 불러오는 중…", "xmrig_none": "없음", "xmrig_reinstall": "재설치", + "xmrig_releases": "xmrig 릴리스", "xmrig_stop_mining_first": "채굴기를 업데이트하기 전에 채굴을 중지하세요.", "xmrig_unavailable_body": "이 플랫폼에서 사용 가능한 채굴기 빌드가 없습니다.", "xmrig_unavailable_title": "채굴기 업데이트를 사용할 수 없습니다", diff --git a/res/lang/pt.json b/res/lang/pt.json index 07bceae..1df6a8a 100644 --- a/res/lang/pt.json +++ b/res/lang/pt.json @@ -949,6 +949,9 @@ "mining_syncing_tooltip": "Blockchain está sincronizando...", "mining_tag": " · Mineração", "mining_threads": "Threads de Mineração", + "mining_threads_input_tooltip": "Digite um número exato de threads (Enter para aplicar)", + "mining_threads_minus_tooltip": "Menos threads", + "mining_threads_plus_tooltip": "Mais threads", "mining_to_save": "para salvar", "mining_today": "Hoje", "mining_uptime": "Tempo Ativo", @@ -1785,6 +1788,7 @@ "xmrig_loading_releases": "Carregando versões…", "xmrig_none": "nenhum", "xmrig_reinstall": "Reinstalar", + "xmrig_releases": "versões do xmrig", "xmrig_stop_mining_first": "Pare a mineração antes de atualizar o minerador.", "xmrig_unavailable_body": "Nenhuma versão do minerador está disponível para esta plataforma.", "xmrig_unavailable_title": "Atualizações do minerador indisponíveis", diff --git a/res/lang/ru.json b/res/lang/ru.json index b6842d2..195bd2d 100644 --- a/res/lang/ru.json +++ b/res/lang/ru.json @@ -949,6 +949,9 @@ "mining_syncing_tooltip": "Блокчейн синхронизируется...", "mining_tag": " · Майнинг", "mining_threads": "Потоки майнинга", + "mining_threads_input_tooltip": "Введите точное число потоков (Enter для применения)", + "mining_threads_minus_tooltip": "Меньше потоков", + "mining_threads_plus_tooltip": "Больше потоков", "mining_to_save": "для сохранения", "mining_today": "Сегодня", "mining_uptime": "Время работы", @@ -1785,6 +1788,7 @@ "xmrig_loading_releases": "Загрузка релизов…", "xmrig_none": "нет", "xmrig_reinstall": "Переустановить", + "xmrig_releases": "релизы xmrig", "xmrig_stop_mining_first": "Остановите майнинг перед обновлением майнера.", "xmrig_unavailable_body": "Для этой платформы нет доступной сборки майнера.", "xmrig_unavailable_title": "Обновления майнера недоступны", diff --git a/res/lang/zh.json b/res/lang/zh.json index 7c51509..21c4629 100644 --- a/res/lang/zh.json +++ b/res/lang/zh.json @@ -948,6 +948,9 @@ "mining_syncing_tooltip": "区块链同步中...", "mining_tag": " · 挖矿", "mining_threads": "挖矿线程", + "mining_threads_input_tooltip": "输入精确的线程数(按 Enter 应用)", + "mining_threads_minus_tooltip": "减少线程", + "mining_threads_plus_tooltip": "增加线程", "mining_to_save": "保存", "mining_today": "今天", "mining_uptime": "运行时间", @@ -1783,6 +1786,7 @@ "xmrig_loading_releases": "正在加载发行版…", "xmrig_none": "无", "xmrig_reinstall": "重新安装", + "xmrig_releases": "xmrig 版本", "xmrig_stop_mining_first": "更新矿工程序前请先停止挖矿。", "xmrig_unavailable_body": "此平台没有可用的矿工构建版本。", "xmrig_unavailable_title": "矿工更新不可用", diff --git a/src/ui/windows/mining_controls.cpp b/src/ui/windows/mining_controls.cpp index 8e25f47..a06aeeb 100644 --- a/src/ui/windows/mining_controls.cpp +++ b/src/ui/windows/mining_controls.cpp @@ -28,6 +28,7 @@ #include #include #include +#include // std::atoi (xmrig version compare) namespace dragonx { namespace ui { @@ -56,6 +57,25 @@ void RenderMiningControls(App* app, const WalletState& state, const MiningInfo& float miningBtnGap = gap; float miningBtnMaxW = availWidth * schema::UI().drawElement("tabs.mining", "btn-max-width-ratio").size; + // Thread-count tiles at an adaptive step (multiples of 1/2/4/8, chosen by core count) plus 1 and the + // max, so the tile row stays bounded (~<=24 tiles) instead of one tile per thread — a 192-thread + // EPYC would otherwise overflow the card. Exact in-between counts are settable via the input box. + int tileStep = 1; + if (max_threads > 96) tileStep = 8; + else if (max_threads > 48) tileStep = 4; + else if (max_threads > 24) tileStep = 2; + std::vector threadOptions; + threadOptions.push_back(1); + for (int v = tileStep; v < max_threads; v += tileStep) + if (v > 1) threadOptions.push_back(v); + if (max_threads > 1 && threadOptions.back() != max_threads) threadOptions.push_back(max_threads); + const int nOpts = (int)threadOptions.size(); + + // Custom drawlist hit-tests (the thread tiles + the Mine button) bypass ImGui's popup input capture, + // so a click on an open dropdown (saved pools / payout addresses) would ALSO fire them. Gate on this + // so clicking the dropdown's X (or a row) doesn't bleed through to the tiles/Mine button. + const bool anyPopupOpen = ImGui::IsPopupOpen("", ImGuiPopupFlags_AnyPopupId | ImGuiPopupFlags_AnyPopupLevel); + // --- Compute thread grid layout based on controls card width --- // Estimate controlsW first to compute cols correctly // The Mine button is square (= card height, which scales with DPI), so the width we @@ -65,8 +85,8 @@ void RenderMiningControls(App* app, const WalletState& state, const MiningInfo& 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 cellGap = std::max(schema::UI().drawElement("tabs.mining", "cell-gap-min").size, cellSz * schema::UI().drawElement("tabs.mining", "cell-gap-ratio").size); - int cols = std::max(1, std::min(max_threads, (int)(innerW / (cellSz + cellGap)))); - int rows = (max_threads + cols - 1) / cols; + int cols = std::max(1, std::min(nOpts, (int)(innerW / (cellSz + cellGap)))); + int rows = (nOpts + cols - 1) / cols; float gridW = cols * cellSz + (cols - 1) * cellGap; float gridH = rows * cellSz + (rows - 1) * cellGap; @@ -116,6 +136,67 @@ void RenderMiningControls(App* app, const WalletState& state, const MiningInfo& OnSurfaceDisabled(), buf); } + // Centered, top-aligned thread-count stepper: [-] centered editable number [+]. Lets a + // high-core CPU pick an EXACT count that isn't one of the tiles. The input commits on Enter + // (not per keystroke) so it doesn't restart the miner while the user is still typing. + { + auto applyThreads = [&](int tc) { + tc = std::clamp(tc, 1, max_threads); + if (tc == s_selected_threads) return; + s_selected_threads = tc; + app->settings()->setPoolThreads(tc); + app->settings()->save(); + if (mining.generate) app->startMining(tc); + if (s_pool_mode && state.pool_mining.xmrig_running) { + app->stopPoolMining(); + app->startPoolMining(tc); + } + }; + ImFont* stepFont = Type().iconSmall(); + const float fieldH = capFont->LegacySize + 6.0f * dp; + const float sideW = fieldH; // square -/+ buttons + const float fieldW = 46.0f * dp; + const float g = 2.0f * dp; + const float totalW = sideW + g + fieldW + g + sideW; + float sx = cardMin.x + (controlsW - totalW) * 0.5f; + const float sy = curY; + ImVec2 savedCur = ImGui::GetCursorScreenPos(); + + material::IconButtonStyle sideStyle; + sideStyle.color = OnSurfaceMedium(); + sideStyle.hoverBg = StateHover(); + + // "-" button + sideStyle.tooltip = TR("mining_threads_minus_tooltip"); + ImGui::SetCursorScreenPos(ImVec2(sx, sy)); + if (material::IconButton("##ThreadMinus", ICON_MD_REMOVE, stepFont, ImVec2(sideW, fieldH), sideStyle)) + applyThreads(s_selected_threads - 1); + sx += sideW + g; + + // Centered editable number: symmetric FramePadding pushes the digits to the field centre. + char tb[8]; snprintf(tb, sizeof(tb), "%d", s_selected_threads); + float basePad = ImGui::GetStyle().FramePadding.x; + float txtW = ImGui::CalcTextSize(tb).x; + float centerPad = std::max(basePad, (fieldW - txtW) * 0.5f); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(centerPad, ImGui::GetStyle().FramePadding.y)); + ImGui::SetCursorScreenPos(ImVec2(sx, sy)); + ImGui::SetNextItemWidth(fieldW); + int tc = s_selected_threads; + if (ImGui::InputInt("##ThreadCountInput", &tc, 0, 0, ImGuiInputTextFlags_EnterReturnsTrue)) + applyThreads(tc); + if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("mining_threads_input_tooltip")); + ImGui::PopStyleVar(); + sx += fieldW + g; + + // "+" button + sideStyle.tooltip = TR("mining_threads_plus_tooltip"); + ImGui::SetCursorScreenPos(ImVec2(sx, sy)); + if (material::IconButton("##ThreadPlus", ICON_MD_ADD, stepFont, ImVec2(sideW, fieldH), sideStyle)) + applyThreads(s_selected_threads + 1); + + ImGui::SetCursorScreenPos(savedCur); + } + // Idle mining toggle (top-right corner of card) float idleRightEdge = cardMax.x - pad; { @@ -475,8 +556,39 @@ void RenderMiningControls(App* app, const WalletState& state, const MiningInfo& std::string curVer = s_live_miner_ver; if (curVer.empty()) curVer = app->poolMiningInstalledVersion(); if (curVer.empty()) curVer = app->settings()->getXmrigVersion(); + // Version state: subtle GREEN + "xmrig releases" when up to date, subtle ORANGE + + // "Update " when an update is available, neutral when either version is unknown. + auto verNum = [](std::string s) -> std::vector { + if (!s.empty() && (s[0] == 'v' || s[0] == 'V')) s.erase(0, 1); + s = s.substr(0, s.find('-')); // drop any -build suffix + std::vector v; size_t p = 0; + while (p <= s.size()) { + size_t q = s.find('.', p); + v.push_back(std::atoi(s.substr(p, q == std::string::npos ? std::string::npos : q - p).c_str())); + if (q == std::string::npos) break; + p = q + 1; + } + return v; + }; + const bool verKnown = !curVer.empty() && !s_xmrig_latest_tag.empty(); + bool upToDate = false; + if (verKnown) { + auto a = verNum(curVer), b = verNum(s_xmrig_latest_tag); + int cmp = 0; + for (size_t i = 0; i < std::max(a.size(), b.size()) && cmp == 0; ++i) { + int x = i < a.size() ? a[i] : 0, y = i < b.size() ? b[i] : 0; + if (x != y) cmp = x < y ? -1 : 1; + } + upToDate = (cmp >= 0); // installed >= latest + } + const bool outdated = verKnown && !upToDate; + const ImU32 subtleGreen = IM_COL32(120, 190, 130, 255); + const ImU32 subtleOrange = IM_COL32(214, 158, 74, 255); + char xbtn[64]; - if (!s_xmrig_latest_tag.empty()) + if (upToDate) + snprintf(xbtn, sizeof(xbtn), "%s", TR("xmrig_releases")); + else if (!s_xmrig_latest_tag.empty()) snprintf(xbtn, sizeof(xbtn), "%s %s", TR("xmrig_update_short"), s_xmrig_latest_tag.c_str()); else snprintf(xbtn, sizeof(xbtn), "%s", TR("xmrig_update_button")); @@ -499,7 +611,9 @@ void RenderMiningControls(App* app, const WalletState& state, const MiningInfo& xpill, xbtnH * 0.3f); dl->AddText(capFont, capFont->LegacySize, ImVec2(xbtnX + xpadX, xbtnY + (xbtnH - xlblSz.y) * 0.5f), - minerBusy ? OnSurfaceDisabled() : OnSurfaceMedium(), xbtn); + minerBusy ? OnSurfaceDisabled() + : (upToDate ? subtleGreen : outdated ? subtleOrange : OnSurfaceMedium()), + xbtn); if (xhov) { ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); material::Tooltip("%s", minerBusy ? TR("xmrig_stop_mining_first") @@ -515,7 +629,7 @@ void RenderMiningControls(App* app, const WalletState& state, const MiningInfo& float xcurX = xbtnX - 6.0f * dp - xcurSz.x; dl->AddText(capFont, capFont->LegacySize, ImVec2(xcurX, curY + (headerH - xcurSz.y) * 0.5f), - OnSurfaceDisabled(), xcur); + upToDate ? subtleGreen : outdated ? subtleOrange : OnSurfaceDisabled(), xcur); idleRightEdge = xcurX - 8.0f * dp; } @@ -548,14 +662,14 @@ void RenderMiningControls(App* app, const WalletState& state, const MiningInfo& // Track which thread the mouse is currently over (-1 = none) int hovered_thread = -1; - // First pass: hit-test all cells to find hovered thread - for (int i = 0; i < max_threads; i++) { + // First pass: hit-test all cells to find the hovered thread-count option (its value, not index) + for (int i = 0; i < nOpts; i++) { int row = i / cols; int col = i % cols; float cx = gridX + col * (cellSz + cellGap); float cy = gridY + row * (cellSz + cellGap); if (material::IsRectHovered(ImVec2(cx, cy), ImVec2(cx + cellSz, cy + cellSz))) { - hovered_thread = i + 1; + hovered_thread = threadOptions[i]; break; } } @@ -566,8 +680,8 @@ void RenderMiningControls(App* app, const WalletState& state, const MiningInfo& if (hovered_thread > 0 && !benchActive) ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); - // Drag-to-select logic (disabled during benchmark) - if (!benchActive && ImGui::IsMouseClicked(0) && hovered_thread > 0) { + // Drag-to-select logic (disabled during benchmark; ignored while a dropdown popup is open) + if (!benchActive && !anyPopupOpen && ImGui::IsMouseClicked(0) && hovered_thread > 0) { // Begin drag s_drag_active = true; s_drag_anchor_thread = hovered_thread; @@ -587,8 +701,8 @@ void RenderMiningControls(App* app, const WalletState& state, const MiningInfo& } } - // Render cells - for (int i = 0; i < max_threads; i++) { + // Render cells (one per discrete thread-count option) + for (int i = 0; i < nOpts; i++) { int row = i / cols; int col = i % cols; float cx = gridX + col * (cellSz + cellGap); @@ -596,8 +710,8 @@ void RenderMiningControls(App* app, const WalletState& state, const MiningInfo& ImVec2 cMin(cx, cy); ImVec2 cMax(cx + cellSz, cy + cellSz); - int threadNum = i + 1; - bool active = threadNum <= s_selected_threads; + int threadNum = threadOptions[i]; + bool active = threadNum <= s_selected_threads; // fill-up-to-selected heat metaphor bool hovered = (threadNum == hovered_thread); // Determine visual state @@ -674,7 +788,7 @@ void RenderMiningControls(App* app, const WalletState& state, const MiningInfo& ImVec2 bMin(btnX, btnY); ImVec2 bMax(btnX + miningBtnSz, btnY + cardH); - bool btnHovered = material::IsRectHovered(bMin, bMax); + bool btnHovered = !anyPopupOpen && material::IsRectHovered(bMin, bMax); // don't bleed through an open dropdown bool btnClicked = btnHovered && ImGui::IsMouseClicked(0); bool isSyncing = state.sync.syncing; bool poolBlockedBySolo = s_pool_mode && mining.generate && !state.pool_mining.xmrig_running; diff --git a/src/util/i18n.cpp b/src/util/i18n.cpp index d591196..9a126f4 100644 --- a/src/util/i18n.cpp +++ b/src/util/i18n.cpp @@ -1296,6 +1296,9 @@ void I18n::loadBuiltinEnglish() strings_["start_mining"] = "Start Mining"; strings_["stop_mining"] = "Stop Mining"; strings_["mining_threads"] = "Mining Threads"; + strings_["mining_threads_input_tooltip"] = "Type an exact thread count (press Enter to apply)"; + strings_["mining_threads_minus_tooltip"] = "Fewer threads"; + strings_["mining_threads_plus_tooltip"] = "More threads"; strings_["mining_statistics"] = "Mining Statistics"; strings_["local_hashrate"] = "Local Hashrate"; strings_["network_hashrate"] = "Network Hashrate"; @@ -1933,6 +1936,7 @@ void I18n::loadBuiltinEnglish() // --- Miner (xmrig) updater --- strings_["xmrig_update_button"] = "Update miner…"; strings_["xmrig_update_short"] = "Update"; + strings_["xmrig_releases"] = "xmrig releases"; strings_["xmrig_current"] = "Current:"; strings_["xmrig_none"] = "none"; strings_["xmrig_update_title"] = "Update Miner";