fix(ui): frost the chat + contacts panes with acrylic blur + padding

The chat and contacts panes drew flat ImGui fills (ChildBg / default
WindowBg / NoBackground), so they never sampled the acrylic blur and
showed the raw backdrop texture. Route them through DrawGlassPanel --
the pattern the peers_tab sibling and every other tab already use:

- chat: frost the conversation-list + thread panes and the composer
  input box, and add inner padding so content doesn't hug the glass edges
- contacts: frost the card/list container AND the table view, padding both

Padding needed ImGuiChildFlags_AlwaysUseWindowPadding -- a borderless
child silently ignores WindowPadding otherwise (documented gotcha in
daemon_download_dialog.h). BeginTable can't take that flag, so the table
view is inset within its glass panel instead.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-19 19:32:11 -05:00
parent 40f8425d94
commit 38f2aa2f8f
2 changed files with 77 additions and 10 deletions

View File

@@ -557,9 +557,20 @@ void RenderChatTab(App* app)
const float metaSz = scaledSize(metaFont);
// ---- Left: new-conversation button + conversation list ----
// Faint sidebar tint so the list reads as distinct from the thread pane (V6).
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImGui::ColorConvertU32ToFloat4(material::WithAlpha(material::OnSurface(), 10)));
ImGui::BeginChild("##ChatList", ImVec2(listW, avail.y), ImGuiChildFlags_Borders,
// Frosted-glass pane so the list blurs the backdrop like the rest of the app (the contacts_tab
// pattern): draw the glass behind the child, then give the child a TRANSPARENT bg. A flat ChildBg
// tint (the old approach) never samples the acrylic blur, so the raw texture showed through.
{
ImDrawList* paneDL = ImGui::GetWindowDrawList();
const ImVec2 pMin = ImGui::GetCursorScreenPos();
material::GlassPanelSpec g; g.rounding = 12.0f * Layout::dpiScale(); g.fillAlpha = 20; g.borderAlpha = 34;
material::DrawGlassPanel(paneDL, pMin, ImVec2(pMin.x + listW, pMin.y + avail.y), g);
}
// Inner padding so the list content (buttons, search, conversation cards) doesn't hug the glass
// pane's edges now that it's a visible frosted card.
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(12.0f * Layout::dpiScale(), 10.0f * Layout::dpiScale()));
ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(0, 0, 0, 0)); // transparent — the frosted glass shows through
ImGui::BeginChild("##ChatList", ImVec2(listW, avail.y), ImGuiChildFlags_AlwaysUseWindowPadding,
ImGuiWindowFlags_NoScrollWithMouse);
material::ApplySmoothScroll(); // wheel-driven lerp scroll, matching the rest of the app
if (s_show_emoji_picker) {
@@ -683,6 +694,7 @@ void RenderChatTab(App* app)
}
ImGui::EndChild();
ImGui::PopStyleColor(); // list ChildBg tint (V6)
ImGui::PopStyleVar(); // list inner WindowPadding
ImGui::SameLine(0.0f, 10.0f * Layout::dpiScale()); // breathing gutter between the list and thread panes
@@ -699,7 +711,21 @@ void RenderChatTab(App* app)
// the box doesn't jump when a reply arrives.
const float composerAreaH = sel ? (composerBoxH + metaSz + 12.0f * tdp) : 0.0f;
ImGui::BeginChild("##ChatThread", ImVec2(0, avail.y - composerAreaH), true);
// Frosted-glass thread pane (same reasoning as the list): glass behind + transparent child bg so
// the message area blurs the backdrop instead of showing the sharp texture. Replaces the old
// bordered child (which drew the flat, translucent WindowBg) — the glass supplies the border.
{
ImDrawList* paneDL = ImGui::GetWindowDrawList();
const ImVec2 pMin = ImGui::GetCursorScreenPos();
const float pW = ImGui::GetContentRegionAvail().x;
material::GlassPanelSpec g; g.rounding = 12.0f * tdp; g.fillAlpha = 12; g.borderAlpha = 34;
material::DrawGlassPanel(paneDL, pMin, ImVec2(pMin.x + pW, pMin.y + (avail.y - composerAreaH)), g);
}
// Inner padding so the header + messages don't hug the glass card's edges (the message child
// below trims its own inset to compensate so bubbles aren't double-indented).
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(12.0f * tdp, 10.0f * tdp));
ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(0, 0, 0, 0)); // transparent — frosted glass shows through
ImGui::BeginChild("##ChatThread", ImVec2(0, avail.y - composerAreaH), ImGuiChildFlags_AlwaysUseWindowPadding);
{
if (sel) {
app->markChatConversationSeen(sel->cid, sel->lastTs); // viewing the thread clears its unread (Q1)
@@ -942,14 +968,15 @@ void RenderChatTab(App* app)
ImGui::Separator();
const float dp = Layout::dpiScale();
// Inset the message list from the pane edges so bubbles don't hug the border/scrollbar (8px).
// Small message-list inset (the ##ChatThread WindowPadding above already holds content off
// the glass edge; keep a little here so bubbles don't hug the scrollbar).
// Zero the VERTICAL item spacing: the message loop reserves its own gaps via Dummy() (tight
// within a run so the grouped/merged corners read right), so the theme's 6px would double them.
const float origSpacingX = ImGui::GetStyle().ItemSpacing.x;
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8.0f * dp, 8.0f * dp));
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(4.0f * dp, 8.0f * dp));
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(origSpacingX, 0.0f));
ImGui::BeginChild("##ChatMessages", ImVec2(0, ImGui::GetContentRegionAvail().y),
ImGuiChildFlags_None, ImGuiWindowFlags_NoScrollWithMouse);
ImGuiChildFlags_AlwaysUseWindowPadding, ImGuiWindowFlags_NoScrollWithMouse);
material::ApplySmoothScroll(); // smooth wheel scroll; syncs with the auto-scroll-to-bottom below
bool atBottom = true;
const ImVec2 msgWinMin = ImGui::GetWindowPos();
@@ -1148,6 +1175,8 @@ void RenderChatTab(App* app)
}
}
ImGui::EndChild();
ImGui::PopStyleColor(); // ##ChatThread transparent ChildBg (GetItemRect below still reads the child)
ImGui::PopStyleVar(); // ##ChatThread inner WindowPadding
// ── Composer — OUTSIDE / below the bordered message box, spanning its width. No divider above it.
// Layout (all placed by absolute screen pos so nothing wraps to the window's left edge): a thin
@@ -1204,7 +1233,17 @@ void RenderChatTab(App* app)
s_emoji_search[0] = '\0';
}
}
// Input.
// Input — frosted so it blurs the backdrop like the panes above (glass behind + transparent
// FrameBg); a flat FrameBg showed the sharp texture.
{
ImDrawList* cdl = ImGui::GetWindowDrawList();
material::GlassPanelSpec g; g.rounding = 8.0f * tdp; g.fillAlpha = 16; g.borderAlpha = 34;
material::DrawGlassPanel(cdl, ImVec2(inputX, rowY),
ImVec2(inputX + inputW, rowY + composerBoxH), g);
}
ImGui::PushStyleColor(ImGuiCol_FrameBg, IM_COL32(0, 0, 0, 0));
ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, IM_COL32(0, 0, 0, 0));
ImGui::PushStyleColor(ImGuiCol_FrameBgActive, IM_COL32(0, 0, 0, 0));
ImGui::SetCursorScreenPos(ImVec2(inputX, rowY));
// Enter-to-send (setting): on → Enter sends + Ctrl+Enter newline; off → Enter is a newline and
// the Send button is the only way to send. NOTE: without EnterReturnsTrue, InputTextMultiline
@@ -1216,6 +1255,7 @@ void RenderChatTab(App* app)
: ImGuiInputTextFlags_None;
const bool inputReturned = ImGui::InputTextMultiline("##compose", s_compose, sizeof(s_compose),
ImVec2(inputW, composerBoxH), composeFlags);
ImGui::PopStyleColor(3); // composer FrameBg (transparent — glass shows through)
bool submit = enterSends && inputReturned; // off-mode sends only via the Send button
// Send.
ImGui::SetCursorScreenPos(ImVec2(sendX, rowY));

View File

@@ -1005,9 +1005,23 @@ void RenderContactsTab(App* app)
if (viewMode == 2) {
// ── TABLE mode (material-ized): no outer/grid borders, row backgrounds, interactive sort. ──
// Frosted-glass panel behind the table so it blurs the backdrop like the card/list view (the
// table's translucent row backgrounds let the blur show through). A BeginTable can't take
// AlwaysUseWindowPadding, so inset the table itself inside the glass so its headers/cells
// don't hug the container edges.
ImDrawList* tpdl = ImGui::GetWindowDrawList();
const ImVec2 tpMin = ImGui::GetCursorScreenPos();
const float tpW = ImGui::GetContentRegionAvail().x;
const float tIpad = 12.0f * dp, tVpad = 10.0f * dp;
{
material::GlassPanelSpec g; g.rounding = 12.0f * dp; g.fillAlpha = 14; g.borderAlpha = 34;
material::DrawGlassPanel(tpdl, tpMin, ImVec2(tpMin.x + tpW, tpMin.y + listH), g);
}
ImGui::SetCursorScreenPos(ImVec2(tpMin.x + tIpad, tpMin.y + tVpad));
if (ImGui::BeginTable("AddressBookTable", 3,
ImGuiTableFlags_RowBg | ImGuiTableFlags_Sortable |
ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY, ImVec2(0, listH)))
ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY,
ImVec2(tpW - 2.0f * tIpad, listH - 2.0f * tVpad)))
{
ImGui::TableSetupColumn(TR("label"), ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_DefaultSort, 1.5f);
ImGui::TableSetupColumn(TR("address_label"), ImGuiTableColumnFlags_WidthStretch, 2.6f);
@@ -1093,8 +1107,20 @@ void RenderContactsTab(App* app)
return toLower(entries[a].label).compare(toLower(entries[b].label)) < 0;
});
}
// Frosted-glass list container so the contacts area blurs the backdrop (peers_tab pattern:
// glass behind a NoBackground list child) instead of showing the raw texture through the pane.
{
ImDrawList* pdl = ImGui::GetWindowDrawList();
const ImVec2 pMin = ImGui::GetCursorScreenPos();
const float pW = ImGui::GetContentRegionAvail().x;
material::GlassPanelSpec g; g.rounding = 12.0f * dp; g.fillAlpha = 14; g.borderAlpha = 34;
material::DrawGlassPanel(pdl, pMin, ImVec2(pMin.x + pW, pMin.y + listH), g);
}
// AlwaysUseWindowPadding: a borderless child ignores WindowPadding without it, so the rows
// would hug the frosted container's edges.
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(12.0f * dp, 10.0f * dp));
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0,0,0,0));
ImGui::BeginChild("##contactList", ImVec2(0, listH), false,
ImGui::BeginChild("##contactList", ImVec2(0, listH), ImGuiChildFlags_AlwaysUseWindowPadding,
ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoScrollWithMouse);
material::ApplySmoothScroll(); // wheel-lerp scroll, matching the rest of the app
if (visibleRows.empty()) {
@@ -1243,6 +1269,7 @@ void RenderContactsTab(App* app)
}
ImGui::EndChild();
ImGui::PopStyleColor();
ImGui::PopStyleVar(); // ##contactList inner WindowPadding
}
// Shared right-click context menu (opened by either view's row right-click; acts on the selection).