feat(material): extract DialogActionFooter + GlassSectionScope helpers

Phase 0b of the settings-modal redesign — pull the reference dialog's two
repeated patterns into shared helpers so the upcoming migrations are mechanical
and identical:

- material::DialogActionFooter(primary, enabled, close, &outPrimary, &outClose)
  — the centered fixed-width primary+Close TactileButton pair with no divider.
- material::GlassSectionScope — an RAII auto-sized glass sub-section using a LOCAL
  ImDrawListSplitter (combo-safe, unlike GlassCardScope's shared ChannelsSplit) with
  the reference's 12/10 padding and 8dp rounding; interiorWidth() for full-width kids.

Dogfooded by refactoring renderImportKeyDialog's footer and scan-height panel onto
them — same padding/spec/centering, so the render is unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-13 20:49:04 -05:00
parent 2b191cea34
commit 8b9cd5bf80
2 changed files with 72 additions and 36 deletions

View File

@@ -1706,6 +1706,70 @@ inline void DialogConfirmFooter(const char* cancelId, const char* confirmLabel,
}
}
// Reference dialog footer: a centered primary + Close (or Cancel) TactileButton pair, fixed width,
// NO divider above (the reference dropped the separator). Sets outPrimary/outClose when the respective
// button is clicked — the caller runs the bodies (so the primary's action can be arbitrary). The
// primary is BeginDisabled'd when !primaryEnabled. btnW<=0 → 130·dp default.
inline void DialogActionFooter(const char* primaryLabel, bool primaryEnabled,
const char* closeLabel, bool& outPrimary, bool& outClose,
float btnW = 0.0f)
{
const float dp = Layout::dpiScale();
if (btnW <= 0.0f) btnW = 130.0f * dp;
const float total = btnW * 2.0f + ImGui::GetStyle().ItemSpacing.x;
const float off = (ImGui::GetContentRegionAvail().x - total) * 0.5f;
if (off > 0.0f) ImGui::SetCursorPosX(ImGui::GetCursorPosX() + off);
ImGui::BeginDisabled(!primaryEnabled);
if (TactileButton(primaryLabel, ImVec2(btnW, 0))) outPrimary = true;
ImGui::EndDisabled();
ImGui::SameLine();
if (TactileButton(closeLabel, ImVec2(btnW, 0))) outClose = true;
}
// RAII glass sub-section for grouping an optional/advanced block inside a dialog (the reference's
// scan-height panel). Auto-sizes to its content and paints a subtle glass panel behind it. Uses a
// LOCAL ImDrawListSplitter — safe with BeginCombo popups, unlike the shared ChannelsSplit that
// GlassCardScope uses. padX/padY are the interior insets (in logical px, dp-scaled here). Render the
// section body between construction and destruction; use interiorWidth() for full-width children.
struct GlassSectionScope {
ImDrawListSplitter splitter;
ImDrawList* dl;
ImVec2 cardMin;
float panelW, padX, padY;
GlassPanelSpec spec;
explicit GlassSectionScope(float padXLogical = 12.0f, float padYLogical = 10.0f)
{
const float dp = Layout::dpiScale();
padX = padXLogical * dp;
padY = padYLogical * dp;
spec.rounding = 8.0f * dp;
spec.fillAlpha = 20;
spec.borderAlpha = 30;
dl = ImGui::GetWindowDrawList();
panelW = ImGui::GetContentRegionAvail().x;
cardMin = ImGui::GetCursorScreenPos();
splitter.Split(dl, 2);
splitter.SetCurrentChannel(dl, 1); // content above the panel background
ImGui::Dummy(ImVec2(0, padY));
ImGui::Indent(padX);
}
~GlassSectionScope()
{
ImGui::Unindent(padX);
ImGui::Dummy(ImVec2(0, padY));
const ImVec2 cardMax(cardMin.x + panelW, ImGui::GetCursorScreenPos().y);
splitter.SetCurrentChannel(dl, 0);
DrawGlassPanel(dl, cardMin, cardMax, spec);
splitter.Merge(dl);
}
// Interior content width (panel width minus both side insets) for full-width children.
float interiorWidth() const { return panelW - 2.0f * padX; }
GlassSectionScope(const GlassSectionScope&) = delete;
GlassSectionScope& operator=(const GlassSectionScope&) = delete;
};
} // namespace material
} // namespace ui
} // namespace dragonx