feat(console): jump-to-bottom "N new" pill when scrolled up

Surface new_lines_since_scroll_ (already tracked) as a floating pill at the output
panel's bottom-right whenever the user is scrolled up with unseen output; clicking it
re-enables auto-scroll and snaps to the bottom.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-01 16:25:34 -05:00
parent b5001ad4db
commit e9121fe49e
2 changed files with 41 additions and 2 deletions

View File

@@ -304,9 +304,47 @@ void ConsoleTab::render(ConsoleCommandExecutor& exec)
}
}
}
// Jump-to-bottom pill — shown at the panel's bottom-right when scrolled up with
// unseen output. Restores the layout cursor after its hit area so the input row
// still flows below the panel.
if (!auto_scroll_ && new_lines_since_scroll_ > 0) {
ImVec2 savedCursor = ImGui::GetCursorScreenPos();
std::string pill = std::to_string(new_lines_since_scroll_) + " " + TR("console_new_lines");
ImFont* pillFont = Type().caption();
ImFont* icoF = Type().iconSmall();
const char* ico = ICON_MD_ARROW_DOWNWARD;
float padX = 10.0f * Layout::hScale();
float padY = 5.0f * Layout::hScale();
ImVec2 tsz = pillFont->CalcTextSizeA(pillFont->LegacySize, FLT_MAX, 0, pill.c_str());
ImVec2 isz = icoF->CalcTextSizeA(icoF->LegacySize, FLT_MAX, 0, ico);
float gap = 4.0f * Layout::hScale();
float pillW = isz.x + gap + tsz.x + padX * 2.0f;
float pillH = std::max(tsz.y, isz.y) + padY * 2.0f;
ImVec2 pMax(outPanelMax.x - 12.0f, outPanelMax.y - 12.0f);
ImVec2 pMin(pMax.x - pillW, pMax.y - pillH);
ImGui::SetCursorScreenPos(pMin);
ImGui::InvisibleButton("##ConsoleJumpBottom", ImVec2(pillW, pillH));
bool hov = ImGui::IsItemHovered();
bool clk = ImGui::IsItemClicked();
dlOut->AddRectFilled(pMin, pMax, WithAlpha(Primary(), hov ? 240 : 205), pillH * 0.5f);
ImU32 fg = IM_COL32(255, 255, 255, 235);
dlOut->AddText(icoF, icoF->LegacySize,
ImVec2(pMin.x + padX, pMin.y + (pillH - isz.y) * 0.5f), fg, ico);
dlOut->AddText(pillFont, pillFont->LegacySize,
ImVec2(pMin.x + padX + isz.x + gap, pMin.y + (pillH - tsz.y) * 0.5f), fg, pill.c_str());
if (hov) ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
if (clk) {
auto_scroll_ = true;
scroll_to_bottom_ = true;
new_lines_since_scroll_ = 0;
}
ImGui::SetCursorScreenPos(savedCursor);
}
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
// Input area
renderInput(exec);

View File

@@ -982,6 +982,7 @@ void I18n::loadBuiltinEnglish()
strings_["console_help_setgenerate"] = " setgenerate - Control mining";
strings_["console_help_stop"] = " stop - Stop the daemon";
strings_["console_line_count"] = "%zu lines";
strings_["console_new_lines"] = "new";
strings_["console_new_lines"] = "%d new lines";
strings_["console_no_daemon"] = "No daemon";
strings_["console_not_connected"] = "Error: Not connected to daemon";