feat(ui): settings polish — button retune, daemon card, RPC 2-row, chat preview

Settings tabs brought closer to the approved mockup:
- ActionButton/renderCardButton retune (settings-scoped): 7px radius, 9px
  padX, Primary → accent-outline chip, Secondary/card buttons more defined.
- Daemon-binary card: compact status right-aligned on the DAEMON BINARY
  heading (Up to date / Version differs / Not installed), filled/rounded
  status box, neutral danger divider (was alarming red), roomier spacing.
- RPC Connection: two-row column-aligned layout (Host | Port, then
  Username | Password) so the password no longer clips off the card edge.
- Chat settings tab: live conversation preview below the Appearance /
  Messaging cards; "Focus input on open" checkbox reflowed onto the console
  color-toggle row.
- Debug Options: "Current theme only" toggle restricts either screenshot
  sweep to the active theme instead of cycling every skin.
- Tabs fill the full content width (content-max-width cap disabled) and the
  sidebar nav panel centers within the true visible area.
- i18n: new keys for the above (untranslated keys fall back to English).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-20 16:59:03 -05:00
co-authored by Claude Opus 4.8
parent b37d3d97b6
commit d0bd55b9c1
19 changed files with 1082 additions and 990 deletions
+51 -4
View File
@@ -1885,20 +1885,37 @@ void ResetChatTab()
s_show_chat_settings = false;
}
void RenderChatSettingsControls(App* app, float contentWidth)
void RenderChatSettingsControls(App* app, float contentWidth, bool drawCards)
{
auto* st = app ? app->settings() : nullptr;
if (!st) return;
const float dp = Layout::dpiScale();
const float ctrlW = 250.0f * dp; // control column width (fits a 3-segment control comfortably)
const float rowGap = 5.0f * dp;
const float rowGap = 10.0f * dp;
// Optionally paint two glass cards (Appearance | Messaging) around our own two columns so the
// Settings tab matches the mockup's card-per-group layout. The chat modal passes drawCards=false
// and keeps its plain single-surface layout — the controls themselves are identical either way.
ImDrawList* cardDL = ImGui::GetWindowDrawList();
const float cardPad = drawCards ? Layout::cardInnerPadding() : 0.0f;
material::GlassPanelSpec cardSpec; cardSpec.rounding = Layout::glassRounding();
float cardTopScr = 0.0f, cardBaseXScr = 0.0f, cardLeftBotScr = 0.0f;
if (drawCards) {
cardTopScr = ImGui::GetCursorScreenPos().y;
cardBaseXScr = ImGui::GetCursorScreenPos().x;
cardDL->ChannelsSplit(2);
cardDL->ChannelsSetCurrent(1);
ImGui::SetCursorScreenPos(ImVec2(cardBaseXScr, cardTopScr + cardPad));
ImGui::Indent(cardPad);
}
// Right-align controls to the row's true right edge. The Settings tab renders us inside a GlassCard
// whose content region isn't narrowed to the card padding, so it passes an explicit contentWidth;
// the chat modal's dialog content region is correct, so it passes 0 (auto).
// leftX/rowW define the current column the rows lay out in; retargeted below
// to split Appearance | Messaging into two columns when the card is wide.
float leftX = ImGui::GetCursorPosX();
float rowW = (contentWidth > 0.0f) ? contentWidth : ImGui::GetContentRegionAvail().x;
float rowW = drawCards ? (contentWidth - 2.0f * cardPad)
: ((contentWidth > 0.0f) ? contentWidth : ImGui::GetContentRegionAvail().x);
// Label left, control right-aligned within [leftX, leftX+rowW]. Leaves the cursor at the control origin.
auto beginRow = [&](const char* label) {
@@ -1966,7 +1983,7 @@ void RenderChatSettingsControls(App* app, float contentWidth)
// Two internal columns when the card is wide enough: Appearance on the left,
// Messaging on the right — fills the width and roughly halves the height.
// (Mirrors the Node & Security card.) Narrow (the chat modal) stays single-column.
const float chatColGap = 24.0f * dp;
const float chatColGap = drawCards ? (Layout::cardGap() + 2.0f * cardPad) : (24.0f * dp);
const bool chatTwoCol = rowW > 760.0f * dp;
const float chatColW = chatTwoCol ? (rowW - chatColGap) * 0.5f : rowW;
const float chatBaseLeftX = leftX;
@@ -2023,6 +2040,7 @@ void RenderChatSettingsControls(App* app, float contentWidth)
// line-start holds the column; retarget leftX so controls right-align in it).
if (chatTwoCol) {
chatLeftBottomY = ImGui::GetCursorPosY();
cardLeftBotScr = ImGui::GetCursorScreenPos().y; // left column bottom (screen), for its card panel
ImGui::SetCursorPosY(chatTopY);
ImGui::Indent(chatColW + chatColGap);
leftX = chatBaseLeftX + chatColW + chatColGap;
@@ -2056,12 +2074,41 @@ void RenderChatSettingsControls(App* app, float contentWidth)
}
// Close the two-column band: un-indent and drop below the taller column.
const float cardRightBotScr = ImGui::GetCursorScreenPos().y; // right (or only) column bottom, screen
if (chatTwoCol) {
ImGui::Unindent(chatColW + chatColGap);
const float chatRightBottomY = ImGui::GetCursorPosY();
ImGui::SetCursorPosX(chatBaseLeftX);
ImGui::SetCursorPosY(std::max(chatLeftBottomY, chatRightBottomY));
}
// Paint the glass card(s) behind the content, then merge the channels.
if (drawCards) {
ImGui::Unindent(cardPad);
cardDL->ChannelsSetCurrent(0);
const float cardW = (contentWidth - Layout::cardGap()) * 0.5f;
if (chatTwoCol) {
const float eqBot = std::max(cardLeftBotScr, cardRightBotScr); // equal-height cards (mockup grid stretch)
material::DrawGlassPanel(cardDL, ImVec2(cardBaseXScr, cardTopScr),
ImVec2(cardBaseXScr + cardW, eqBot + cardPad), cardSpec);
material::DrawGlassPanel(cardDL, ImVec2(cardBaseXScr + cardW + Layout::cardGap(), cardTopScr),
ImVec2(cardBaseXScr + contentWidth, eqBot + cardPad), cardSpec);
} else {
material::DrawGlassPanel(cardDL, ImVec2(cardBaseXScr, cardTopScr),
ImVec2(cardBaseXScr + contentWidth, cardRightBotScr + cardPad), cardSpec);
}
cardDL->ChannelsMerge();
const float botScr = chatTwoCol ? std::max(cardLeftBotScr, cardRightBotScr) : cardRightBotScr;
// Reserve the card footprint with a Dummy so the parent scroll region grows to include it
// (a bare SetCursorScreenPos past content warns in ImGui).
ImGui::SetCursorScreenPos(ImVec2(cardBaseXScr, cardTopScr));
ImGui::Dummy(ImVec2(contentWidth, (botScr - cardTopScr) + cardPad));
// Live conversation preview below the two cards (Settings tab only — the chat modal renders
// its own preview column beside these controls, so it passes drawCards=false and skips this).
ImGui::Dummy(ImVec2(0.0f, Layout::spacingMd()));
RenderChatSettingsPreview(app, contentWidth);
}
}
} // namespace ui
+1 -1
View File
@@ -34,7 +34,7 @@ void RenderChatTab(App* app);
* width from the Settings tab (whose GlassCard doesn't narrow the content region). 0 = auto
* (use the current content region, correct inside the chat modal's dialog).
*/
void RenderChatSettingsControls(App* app, float contentWidth = 0.0f);
void RenderChatSettingsControls(App* app, float contentWidth = 0.0f, bool drawCards = false);
/**
* @brief Securely wipe the Chat tab's UI-local state (composer / new-conversation