refactor(ui): add material::LabeledInput helper + adopt at 8 form fields

UI-standardization audit: the labeled single-line form field (a plain
ImGui::Text label above a fixed-width InputText/InputTextWithHint) was
open-coded across dialogs. Add material::LabeledInput(label, id, buf, size,
width=-1, hint=nullptr, flags=0) mirroring that idiom exactly — pixel-identical,
but a single chokepoint so input styling (label typography, frame, spacing) can
later evolve in one place. Returns the input's edited bool.

Adopt at the clean vertical (label-above) single-line sites: address-book
add/edit (label + address), shield from-address, export-all-keys + export-
transactions filename, validate-address input, request-payment label + URI.
Left as-is: horizontal label-beside-input rows (settings RPC host/port/user/
pass use Text + SameLine), multiline (InputTextMultiline), numeric (InputDouble),
combos, and hint-only inputs — none match the vertical single-line idiom.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-04 21:11:02 -05:00
parent 1c8e97c0a4
commit 840ead3645
7 changed files with 29 additions and 26 deletions

View File

@@ -354,6 +354,22 @@ inline ImVec2 DrawPill(ImDrawList* dl, const ImVec2& pos, const char* text, ImFo
return sz; return sz;
} }
// ── Labeled form input ───────────────────────────────────────────────────
// The app's common form-field idiom: a plain label above a fixed-width single-line input
// (ImGui::Text label + SetNextItemWidth + InputText / InputTextWithHint). Mirrors the existing
// pattern exactly (no visual change) so input styling can later evolve in ONE place. `width` < 0
// means full width (-1). Pass a non-null `hint` to use InputTextWithHint. Returns the input's
// edited bool. The label is drawn via "%s" so a stray '%' in the text is safe.
inline bool LabeledInput(const char* label, const char* id, char* buf, size_t bufSize,
float width = -1.0f, const char* hint = nullptr,
ImGuiInputTextFlags flags = 0)
{
ImGui::Text("%s", label);
ImGui::SetNextItemWidth(width);
if (hint && hint[0]) return ImGui::InputTextWithHint(id, hint, buf, bufSize, flags);
return ImGui::InputText(id, buf, bufSize, flags);
}
// ── Frameless icon button ──────────────────────────────────────────────── // ── Frameless icon button ────────────────────────────────────────────────
// Style for IconButton. All colors default to 0 (= unset): color falls back to OnSurfaceMedium(); // Style for IconButton. All colors default to 0 (= unset): color falls back to OnSurfaceMedium();
// hoverColor==0 means "no hover recolor"; hoverBg/restBg==0 mean "no background". // hoverColor==0 means "no hover recolor"; hoverBg/restBg==0 mean "no background".

View File

@@ -101,15 +101,13 @@ void AddressBookDialog::render(App* app)
if (material::BeginOverlayDialog(title, open, dialogW, 0.94f, if (material::BeginOverlayDialog(title, open, dialogW, 0.94f,
Layout::kDialogCompactBottomRatio(), id)) { Layout::kDialogCompactBottomRatio(), id)) {
ImGui::Text("%s", TR("label")); material::LabeledInput(TR("label"), isEdit ? "##EditLabel" : "##AddLabel",
ImGui::SetNextItemWidth(formW); s_edit_label, sizeof(s_edit_label), formW);
ImGui::InputText(isEdit ? "##EditLabel" : "##AddLabel", s_edit_label, sizeof(s_edit_label));
ImGui::Spacing(); ImGui::Spacing();
ImGui::Text("%s", TR("address_label")); material::LabeledInput(TR("address_label"), isEdit ? "##EditAddress" : "##AddAddress",
ImGui::SetNextItemWidth(formW); s_edit_address, sizeof(s_edit_address), formW);
ImGui::InputText(isEdit ? "##EditAddress" : "##AddAddress", s_edit_address, sizeof(s_edit_address));
if (!isEdit) { if (!isEdit) {
ImGui::SameLine(); ImGui::SameLine();
if (material::StyledButton(TR("paste"), ImVec2(0,0), S.resolveFont(actionBtn.font))) { if (material::StyledButton(TR("paste"), ImVec2(0,0), S.resolveFont(actionBtn.font))) {

View File

@@ -99,9 +99,7 @@ void ExportAllKeysDialog::render(App* app)
ImGui::Spacing(); ImGui::Spacing();
// Filename // Filename
ImGui::Text("%s", TR("output_filename")); material::LabeledInput(TR("output_filename"), "##Filename", s_filename, sizeof(s_filename));
ImGui::SetNextItemWidth(-1);
ImGui::InputText("##Filename", s_filename, sizeof(s_filename));
ImGui::Spacing(); ImGui::Spacing();
ImGui::TextDisabled("%s", TR("file_save_location")); ImGui::TextDisabled("%s", TR("file_save_location"));

View File

@@ -82,9 +82,7 @@ void ExportTransactionsDialog::render(App* app)
ImGui::Spacing(); ImGui::Spacing();
// Filename // Filename
ImGui::Text("%s", TR("output_filename")); material::LabeledInput(TR("output_filename"), "##Filename", s_filename, sizeof(s_filename));
ImGui::SetNextItemWidth(-1);
ImGui::InputText("##Filename", s_filename, sizeof(s_filename));
ImGui::Spacing(); ImGui::Spacing();
ImGui::TextDisabled("%s", TR("file_save_location")); ImGui::TextDisabled("%s", TR("file_save_location"));

View File

@@ -197,9 +197,7 @@ void RequestPaymentDialog::render(App* app)
ImGui::Spacing(); ImGui::Spacing();
// Label (optional) // Label (optional)
ImGui::Text("%s", TR("request_label")); if (material::LabeledInput(TR("request_label"), "##Label", s_label, sizeof(s_label))) {
ImGui::SetNextItemWidth(-1);
if (ImGui::InputText("##Label", s_label, sizeof(s_label))) {
s_uri_dirty = true; s_uri_dirty = true;
} }
@@ -242,13 +240,11 @@ void RequestPaymentDialog::render(App* app)
// Payment URI display // Payment URI display
if (!s_payment_uri.empty()) { if (!s_payment_uri.empty()) {
ImGui::Text("%s", TR("request_payment_uri"));
ImGui::SetNextItemWidth(-1);
// Use a selectable text area for the URI // Use a selectable text area for the URI
char uri_buf[1024]; char uri_buf[1024];
strncpy(uri_buf, s_payment_uri.c_str(), sizeof(uri_buf) - 1); strncpy(uri_buf, s_payment_uri.c_str(), sizeof(uri_buf) - 1);
ImGui::InputText("##URI", uri_buf, sizeof(uri_buf), ImGuiInputTextFlags_ReadOnly); material::LabeledInput(TR("request_payment_uri"), "##URI", uri_buf, sizeof(uri_buf),
-1.0f, nullptr, ImGuiInputTextFlags_ReadOnly);
ImGui::Spacing(); ImGui::Spacing();

View File

@@ -95,9 +95,7 @@ void ShieldDialog::render(App* app)
// From address (for shield coinbase) // From address (for shield coinbase)
if (s_mode == Mode::ShieldCoinbase) { if (s_mode == Mode::ShieldCoinbase) {
ImGui::Text("%s", TR("shield_from_address")); material::LabeledInput(TR("shield_from_address"), "##FromAddr", s_from_address, sizeof(s_from_address));
ImGui::SetNextItemWidth(-1);
ImGui::InputText("##FromAddr", s_from_address, sizeof(s_from_address));
ImGui::TextDisabled("%s", TR("shield_wildcard_hint")); ImGui::TextDisabled("%s", TR("shield_wildcard_hint"));
ImGui::Spacing(); ImGui::Spacing();
} }

View File

@@ -61,10 +61,9 @@ void ValidateAddressDialog::render(App* app)
ImGui::Spacing(); ImGui::Spacing();
// Address input // Address input
ImGui::Text("%s", TR("address_label")); bool enter_pressed = material::LabeledInput(TR("address_label"), "##ValidateAddr", s_address_input,
ImGui::SetNextItemWidth(-1); sizeof(s_address_input), -1.0f, nullptr,
bool enter_pressed = ImGui::InputText("##ValidateAddr", s_address_input, sizeof(s_address_input), ImGuiInputTextFlags_EnterReturnsTrue);
ImGuiInputTextFlags_EnterReturnsTrue);
ImGui::Spacing(); ImGui::Spacing();