From 38f2aa2f8f9d30265c2319bdf115f148df9c2c35 Mon Sep 17 00:00:00 2001 From: DanS Date: Sun, 19 Jul 2026 19:32:11 -0500 Subject: [PATCH] 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) --- src/ui/windows/chat_tab.cpp | 56 ++++++++++++++++++++++++++++----- src/ui/windows/contacts_tab.cpp | 31 ++++++++++++++++-- 2 files changed, 77 insertions(+), 10 deletions(-) diff --git a/src/ui/windows/chat_tab.cpp b/src/ui/windows/chat_tab.cpp index 1d8b163..8d609bd 100644 --- a/src/ui/windows/chat_tab.cpp +++ b/src/ui/windows/chat_tab.cpp @@ -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)); diff --git a/src/ui/windows/contacts_tab.cpp b/src/ui/windows/contacts_tab.cpp index e682657..8bb5657 100644 --- a/src/ui/windows/contacts_tab.cpp +++ b/src/ui/windows/contacts_tab.cpp @@ -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).