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:
44
src/app.cpp
44
src/app.cpp
@@ -3049,20 +3049,9 @@ void App::renderImportKeyDialog()
|
|||||||
sodium_memzero(import_key_scan_height_, sizeof(import_key_scan_height_)); // no stale height
|
sodium_memzero(import_key_scan_height_, sizeof(import_key_scan_height_)); // no stale height
|
||||||
const int tip = state_.sync.blocks; // chain tip = slider max; 0 when unknown
|
const int tip = state_.sync.blocks; // chain tip = slider max; 0 when unknown
|
||||||
|
|
||||||
// Render the content on an upper draw channel, then paint the glass panel behind it once the
|
// Auto-sized glass sub-section (combo-safe local splitter; see GlassSectionScope).
|
||||||
// block's height is known (immediate-mode auto-sized panel via a draw-list splitter).
|
m::GlassSectionScope section;
|
||||||
ImDrawList* dl = ImGui::GetWindowDrawList();
|
const float availW = section.interiorWidth();
|
||||||
ImDrawListSplitter splitter;
|
|
||||||
splitter.Split(dl, 2);
|
|
||||||
splitter.SetCurrentChannel(dl, 1);
|
|
||||||
|
|
||||||
const float padX = 12.0f * dp, padY = 10.0f * dp;
|
|
||||||
const float panelW = ImGui::GetContentRegionAvail().x;
|
|
||||||
const ImVec2 panelMin = ImGui::GetCursorScreenPos();
|
|
||||||
|
|
||||||
ImGui::Dummy(ImVec2(0, padY));
|
|
||||||
ImGui::Indent(padX);
|
|
||||||
const float availW = ImGui::GetContentRegionAvail().x - padX; // interior width (both side paddings)
|
|
||||||
|
|
||||||
// Label + current chain height, right-aligned within the interior.
|
// Label + current chain height, right-aligned within the interior.
|
||||||
const float rowStartX = ImGui::GetCursorPosX();
|
const float rowStartX = ImGui::GetCursorPosX();
|
||||||
@@ -3083,18 +3072,6 @@ void App::renderImportKeyDialog()
|
|||||||
// start" hint was dropped as self-evident (0 is the field's default).
|
// start" hint was dropped as self-evident (0 is the field's default).
|
||||||
if (transparentKey)
|
if (transparentKey)
|
||||||
ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(m::OnSurfaceDisabled()), "%s", TR("import_scan_transparent"));
|
ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(m::OnSurfaceDisabled()), "%s", TR("import_scan_transparent"));
|
||||||
|
|
||||||
ImGui::Unindent(padX);
|
|
||||||
ImGui::Dummy(ImVec2(0, padY));
|
|
||||||
|
|
||||||
const ImVec2 panelMax(panelMin.x + panelW, ImGui::GetCursorScreenPos().y);
|
|
||||||
splitter.SetCurrentChannel(dl, 0);
|
|
||||||
m::GlassPanelSpec spec;
|
|
||||||
spec.rounding = 8.0f * dp;
|
|
||||||
spec.fillAlpha = 20;
|
|
||||||
spec.borderAlpha = 30;
|
|
||||||
m::DrawGlassPanel(dl, panelMin, panelMax, spec);
|
|
||||||
splitter.Merge(dl);
|
|
||||||
}
|
}
|
||||||
ImGui::Dummy(ImVec2(0, 2.0f * dp));
|
ImGui::Dummy(ImVec2(0, 2.0f * dp));
|
||||||
|
|
||||||
@@ -3147,17 +3124,15 @@ void App::renderImportKeyDialog()
|
|||||||
|
|
||||||
ImGui::Spacing();
|
ImGui::Spacing();
|
||||||
|
|
||||||
float btnW = 130.0f * dp;
|
|
||||||
const bool running = sweepMode ? sweepRunning : import_in_progress_;
|
const bool running = sweepMode ? sweepRunning : import_in_progress_;
|
||||||
// Sweep needs a spending key AND a destination (a picked address when not using a fresh one).
|
// Sweep needs a spending key AND a destination (a picked address when not using a fresh one).
|
||||||
const bool destReady = !sweepMode || sweep_dest_mode_ == 0 || sweep_dest_pick_[0] != '\0';
|
const bool destReady = !sweepMode || sweep_dest_mode_ == 0 || sweep_dest_pick_[0] != '\0';
|
||||||
const bool canAct = recognized && state_.connected && !running && destReady;
|
const bool canAct = recognized && state_.connected && !running && destReady;
|
||||||
// Center the action + Close buttons in the (wider) dialog.
|
bool doAction = false, doClose = false;
|
||||||
const float footTotal = btnW * 2.0f + ImGui::GetStyle().ItemSpacing.x;
|
m::DialogActionFooter(sweepMode ? TR("sweep_button") : TR("import_key_import"), canAct,
|
||||||
const float footOff = (ImGui::GetContentRegionAvail().x - footTotal) * 0.5f;
|
TR("close"), doAction, doClose);
|
||||||
if (footOff > 0.0f) ImGui::SetCursorPosX(ImGui::GetCursorPosX() + footOff);
|
if (doClose) show_import_key_ = false;
|
||||||
ImGui::BeginDisabled(!canAct);
|
if (doAction) {
|
||||||
if (m::TactileButton(sweepMode ? TR("sweep_button") : TR("import_key_import"), ImVec2(btnW, 0))) {
|
|
||||||
int startHeight = 0; // 0 = full rescan. Shielded imports (z_importkey) honor it; transparent
|
int startHeight = 0; // 0 = full rescan. Shielded imports (z_importkey) honor it; transparent
|
||||||
if (import_key_scan_height_[0]) { // WIF (importprivkey) ignores it.
|
if (import_key_scan_height_[0]) { // WIF (importprivkey) ignores it.
|
||||||
startHeight = std::atoi(import_key_scan_height_);
|
startHeight = std::atoi(import_key_scan_height_);
|
||||||
@@ -3187,9 +3162,6 @@ void App::renderImportKeyDialog()
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ImGui::EndDisabled();
|
|
||||||
ImGui::SameLine();
|
|
||||||
if (m::TactileButton(TR("close"), ImVec2(btnW, 0))) show_import_key_ = false;
|
|
||||||
|
|
||||||
// Wipe the key the moment the dialog closes (Close button OR outside-click, which clears
|
// Wipe the key the moment the dialog closes (Close button OR outside-click, which clears
|
||||||
// show_import_key_ mid-frame). in_progress is left to the callback to clear.
|
// show_import_key_ mid-frame). in_progress is left to the callback to clear.
|
||||||
|
|||||||
@@ -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 material
|
||||||
} // namespace ui
|
} // namespace ui
|
||||||
} // namespace dragonx
|
} // namespace dragonx
|
||||||
|
|||||||
Reference in New Issue
Block a user