fix(ui): DPI-scale hand-drawn overlays + freeze sweep demo state
Hand-drawn absolute geometry (dl->AddText offsets, SetNextWindowSize, explicit ImVec2 button widths, SameLine strides) is immune to the app's DPI machinery (font-atlas rebuild + ScaleAllSizes), so several overlays rendered native-size (tiny) on HiDPI / Windows 150% displays. Multiply the raw logical-px geometry by Layout::dpiScale(): - seed_display: content-aware, DPI-scaled seed-grid column stride (a raw 130px stride let words collide with the next column's index at 150%) - lock screen: card/logo/input/unlock-button + all vertical offsets - migrate-to-seed + seed-backup dialogs: button widths - send-confirm: confirm/cancel button widths - about dialog: value-column x, edition inset, link/close button widths - chat conversation list: row height + padding (fixes the preview clip) Also freeze demo state during the full UI sweep: guard App::update() on capture_mode_ so a running daemon's refresh can't clobber the injected demo state (it blanked Send/Receive/History with a CDB error mid-sweep). Verified on Windows 4K@150% and locally at font_scale=1.5 (same dpiScale()==1.5 code path). Adds a DPI-scaling convention note to CLAUDE.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -824,12 +824,19 @@ void App::renderLockScreen() {
|
||||
MarkBlurOverlayDrawn(nullptr); // suppress foreground theme-effect bleed + re-capture on unlock
|
||||
}
|
||||
|
||||
// Card
|
||||
// Card. The lock screen is fully hand-drawn (dl->AddText at manual y offsets), so — unlike
|
||||
// BeginOverlayDialog cards — nothing scales automatically. Multiply every geometry literal
|
||||
// (card size, logo, input/button widths, vertical gaps) by dpiScale() so the card grows with
|
||||
// OS DPI / font scale instead of stranding a native-size card in a large window. The font
|
||||
// metrics (LegacySize / CalcTextSize) are already DPI-scaled via the atlas, so leave those.
|
||||
const float dp = ui::Layout::dpiScale();
|
||||
const auto& S = ui::schema::UI();
|
||||
float cardW = S.drawElement("screens.lock-screen", "card").getFloat("width", 400.0f);
|
||||
float cardH = S.drawElement("screens.lock-screen", "card").height;
|
||||
if (cardW <= 0) cardW = 400.0f;
|
||||
if (cardH <= 0) cardH = 320.0f;
|
||||
cardW *= dp;
|
||||
cardH *= dp;
|
||||
|
||||
float cardX = winPos.x + (winSize.x - cardW) * 0.5f;
|
||||
float cardY = winPos.y + (winSize.y - cardH) * 0.5f;
|
||||
@@ -845,17 +852,17 @@ void App::renderLockScreen() {
|
||||
cardGlass.borderWidth = 1.0f;
|
||||
DrawGlassPanel(dl, cardMin, cardMax, cardGlass);
|
||||
|
||||
float cy = cardY + 24.0f;
|
||||
float cy = cardY + 24.0f * dp;
|
||||
|
||||
// Logo
|
||||
float logoSize = S.drawElement("screens.lock-screen", "logo").sizeOr(64.0f);
|
||||
float logoSize = S.drawElement("screens.lock-screen", "logo").sizeOr(64.0f) * dp;
|
||||
if (logo_tex_ != 0) {
|
||||
float aspect = (logo_h_ > 0) ? (float)logo_w_ / (float)logo_h_ : 1.0f;
|
||||
float logoW = logoSize * aspect;
|
||||
float logoX = cardX + (cardW - logoW) * 0.5f;
|
||||
dl->AddImage(logo_tex_, ImVec2(logoX, cy), ImVec2(logoX + logoW, cy + logoSize));
|
||||
}
|
||||
cy += logoSize + 16.0f;
|
||||
cy += logoSize + 16.0f * dp;
|
||||
|
||||
// Title
|
||||
ImFont* titleFont = S.resolveFont(S.label("screens.lock-screen", "title").font);
|
||||
@@ -869,7 +876,7 @@ void App::renderLockScreen() {
|
||||
ImVec2 ts = titleFont->CalcTextSizeA(titleFont->LegacySize, FLT_MAX, 0, title);
|
||||
dl->AddText(titleFont, titleFont->LegacySize,
|
||||
ImVec2(cardX + (cardW - ts.x) * 0.5f, cy), textCol, title);
|
||||
cy += ts.y + 20.0f;
|
||||
cy += ts.y + 20.0f * dp;
|
||||
}
|
||||
|
||||
// Lockout timer
|
||||
@@ -882,7 +889,7 @@ void App::renderLockScreen() {
|
||||
ImVec2 ms = captionFont->CalcTextSizeA(captionFont->LegacySize, FLT_MAX, 0, msg);
|
||||
dl->AddText(captionFont, captionFont->LegacySize,
|
||||
ImVec2(cardX + (cardW - ms.x) * 0.5f, cy), ui::material::Warning(), msg);
|
||||
cy += captionFont->LegacySize + 12.0f;
|
||||
cy += captionFont->LegacySize + 12.0f * dp;
|
||||
}
|
||||
|
||||
// Check if PIN vault is available
|
||||
@@ -907,7 +914,7 @@ void App::renderLockScreen() {
|
||||
ImVec2(startX, cy), IM_COL32(255,255,255,120), modeIcon);
|
||||
dl->AddText(captionFont, captionFont->LegacySize,
|
||||
ImVec2(startX + iconSize.x, textY), IM_COL32(255,255,255,120), modeText);
|
||||
cy += std::max(iconSize.y, textSize.y) + 8.0f;
|
||||
cy += std::max(iconSize.y, textSize.y) + 8.0f * dp;
|
||||
|
||||
// Switch link
|
||||
ImVec2 sls = captionFont->CalcTextSizeA(captionFont->LegacySize, FLT_MAX, 0, switchLabel);
|
||||
@@ -924,7 +931,7 @@ void App::renderLockScreen() {
|
||||
dl->AddText(captionFont, captionFont->LegacySize,
|
||||
ImVec2(switchX, cy), ui::material::Primary(), switchLabel);
|
||||
ImGui::PopStyleColor();
|
||||
cy += captionFont->LegacySize + 12.0f;
|
||||
cy += captionFont->LegacySize + 12.0f * dp;
|
||||
} else {
|
||||
// No PIN vault — don't show toggle, force passphrase mode
|
||||
lock_use_pin_ = false;
|
||||
@@ -933,6 +940,7 @@ void App::renderLockScreen() {
|
||||
// Input field
|
||||
float inputW = S.drawElement("screens.lock-screen", "input").getFloat("width", 320.0f);
|
||||
if (inputW <= 0) inputW = 320.0f;
|
||||
inputW *= dp;
|
||||
float inputX = cardX + (cardW - inputW) * 0.5f;
|
||||
|
||||
bool canSubmit = lock_lockout_timer_ <= 0.0f && !lock_unlock_in_progress_;
|
||||
@@ -942,7 +950,7 @@ void App::renderLockScreen() {
|
||||
// PIN input
|
||||
ImGui::SetCursorScreenPos(ImVec2(inputX, cy));
|
||||
ImGui::PushItemWidth(inputW);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 6.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 6.0f * dp);
|
||||
ImGuiInputTextFlags pinFlags = ImGuiInputTextFlags_Password | ImGuiInputTextFlags_CharsDecimal;
|
||||
if (canSubmit) pinFlags |= ImGuiInputTextFlags_EnterReturnsTrue;
|
||||
submitted = ImGui::InputText("##lock_pin", lock_pin_buf_,
|
||||
@@ -953,7 +961,7 @@ void App::renderLockScreen() {
|
||||
// Passphrase input (original)
|
||||
ImGui::SetCursorScreenPos(ImVec2(inputX, cy));
|
||||
ImGui::PushItemWidth(inputW);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 6.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 6.0f * dp);
|
||||
ImGuiInputTextFlags inputFlags = ImGuiInputTextFlags_Password;
|
||||
if (canSubmit) inputFlags |= ImGuiInputTextFlags_EnterReturnsTrue;
|
||||
submitted = ImGui::InputText("##lock_pass", lock_passphrase_buf_,
|
||||
@@ -965,9 +973,9 @@ void App::renderLockScreen() {
|
||||
// default frame is subtle on the glass card, so this shows the field is active and ready to type.
|
||||
if (ImGui::IsItemActive() || ImGui::IsItemFocused()) {
|
||||
dl->AddRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(),
|
||||
ui::material::Primary(), 6.0f, 0, 2.0f);
|
||||
ui::material::Primary(), 6.0f * dp, 0, 2.0f * dp);
|
||||
}
|
||||
cy += 40.0f + 12.0f;
|
||||
cy += (40.0f + 12.0f) * dp;
|
||||
|
||||
// Focus the input when the lock screen first appears.
|
||||
// IsWindowAppearing() does not work here because the lock screen is
|
||||
@@ -985,13 +993,13 @@ void App::renderLockScreen() {
|
||||
dl->AddText(captionFont, captionFont->LegacySize,
|
||||
ImVec2(cardX + (cardW - es.x) * 0.5f, cy), ui::material::Error(),
|
||||
lock_error_msg_.c_str());
|
||||
cy += captionFont->LegacySize + 8.0f;
|
||||
cy += captionFont->LegacySize + 8.0f * dp;
|
||||
}
|
||||
|
||||
// "Unlocking..." feedback while worker thread is running
|
||||
// Always reserve the vertical space so the button doesn't shift.
|
||||
{
|
||||
float rowH = captionFont->LegacySize + 8.0f;
|
||||
float rowH = captionFont->LegacySize + 8.0f * dp;
|
||||
if (lock_unlock_in_progress_) {
|
||||
// Animated spinner dots
|
||||
char msg[64];
|
||||
@@ -1009,13 +1017,15 @@ void App::renderLockScreen() {
|
||||
float unlockH = S.drawElement("screens.lock-screen", "unlock-button").height;
|
||||
if (unlockW <= 0) unlockW = 320.0f;
|
||||
if (unlockH <= 0) unlockH = 44.0f;
|
||||
unlockW *= dp;
|
||||
unlockH *= dp;
|
||||
float unlockX = cardX + (cardW - unlockW) * 0.5f;
|
||||
|
||||
ImGui::SetCursorScreenPos(ImVec2(unlockX, cy));
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImGui::ColorConvertU32ToFloat4(ui::material::Primary()));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::ColorConvertU32ToFloat4(ui::material::PrimaryVariant()));
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(ui::material::OnPrimary()));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * dp);
|
||||
ImGui::BeginDisabled(!canSubmit);
|
||||
bool btnClicked = ui::material::TactileButton("Unlock", ImVec2(unlockW, unlockH));
|
||||
ImGui::EndDisabled();
|
||||
|
||||
Reference in New Issue
Block a user