refactor(contacts): two-column edit dialog — wider, shorter, roomier grid
The edit dialog was a tall, narrow single column that wasted ~half the horizontal space and cramped the icon grid into ~2.5 clipped rows crowding the footer. Rework it into the portfolio-editor two-column shape: - Card widened 560 → 880 logical. - Full-width live preview stays on top (now shows more of the address). - Body is two fixed-height columns: form (label / address+paste / notes / global) on the left, avatar picker (segmented + icon grid / image / badge hint) on the right, each filling its column so the grid gets ~6 columns and ~4 rows instead of clipping mid-row. - Overall modal is much shorter, so Save/Cancel no longer crowd the grid. - Badge-mode hint re-centered relative to the current cursor (it now sits below the segmented control inside the shared column, not a fresh child). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -228,14 +228,13 @@ void RenderContactsTab(App* app)
|
||||
const char* id = isEdit ? "AddressBookEdit" : "AddressBookAdd";
|
||||
const float dp = Layout::dpiScale();
|
||||
const bool light = material::IsLightTheme();
|
||||
float formW = -1.0f; // stretch inputs to the card content width
|
||||
float notesH = (notesInput.height > 0 ? notesInput.height : 56.0f) * dp;
|
||||
ImFont* btnFont = S.resolveFont(actionBtn.font);
|
||||
|
||||
material::OverlayDialogSpec ov;
|
||||
ov.title = title; ov.p_open = open;
|
||||
ov.style = material::OverlayStyle::BlurFloat;
|
||||
ov.cardWidth = 560.0f; ov.idSuffix = id; // wider than a plain form — holds the icon grid
|
||||
ov.cardWidth = 880.0f; ov.idSuffix = id; // two-column body: form (left) + avatar picker (right)
|
||||
ov.cardBottomViewportRatio = 0.92f;
|
||||
if (material::BeginOverlayDialog(ov)) {
|
||||
// End-truncate a string to fit maxW (whole UTF-8 code points), appending an ellipsis.
|
||||
@@ -289,15 +288,24 @@ void RenderContactsTab(App* app)
|
||||
hasAddr ? material::OnSurfaceMedium() : material::OnSurfaceDisabled(), ad.c_str());
|
||||
ImGui::Dummy(ImVec2(pw, ph));
|
||||
}
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingMd()));
|
||||
|
||||
// ================= Two-column body: form (left) | avatar picker (right) =================
|
||||
const float bodyH = 300.0f * dp;
|
||||
const float colGap = Layout::spacingLg();
|
||||
const float bodyAvail = ImGui::GetContentRegionAvail().x;
|
||||
const float colW = (bodyAvail - colGap) * 0.5f;
|
||||
|
||||
// ---- LEFT: label / address / notes / global ----------------------------------------
|
||||
ImGui::BeginChild("##contactFormCol", ImVec2(colW, bodyH), false,
|
||||
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
|
||||
// Focus the first field the frame the dialog opens so it's keyboard-ready.
|
||||
if (s_focus_edit_field) {
|
||||
ImGui::SetKeyboardFocusHere();
|
||||
s_focus_edit_field = false;
|
||||
}
|
||||
material::LabeledInput(TR("label"), isEdit ? "##EditLabel" : "##AddLabel",
|
||||
s_edit_label, sizeof(s_edit_label), formW);
|
||||
s_edit_label, sizeof(s_edit_label), -1.0f);
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
@@ -320,14 +328,18 @@ void RenderContactsTab(App* app)
|
||||
ImGui::Spacing();
|
||||
|
||||
material::LabeledInputMultiline(TR("notes_optional"), isEdit ? "##EditNotes" : "##AddNotes",
|
||||
s_edit_notes, sizeof(s_edit_notes), ImVec2(formW, notesH));
|
||||
s_edit_notes, sizeof(s_edit_notes), ImVec2(-1.0f, notesH));
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Checkbox(TR("contact_global"), &s_edit_global);
|
||||
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("contact_global_tt"));
|
||||
ImGui::EndChild(); // ##contactFormCol
|
||||
|
||||
// ---- Avatar picker: Badge / Icon / Image -------------------------------------------
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
ImGui::SameLine(0, colGap);
|
||||
|
||||
// ---- RIGHT: avatar picker (Badge / Icon / Image), fills the column height -----------
|
||||
ImGui::BeginChild("##contactAvatarCol", ImVec2(colW, bodyH), false,
|
||||
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
|
||||
material::Type().textColored(material::TypeStyle::Caption, material::OnSurfaceMedium(), TR("contact_avatar"));
|
||||
{
|
||||
const char* segs[3] = { TR("contact_avatar_badge"), TR("contact_avatar_icon"),
|
||||
@@ -349,18 +361,18 @@ void RenderContactsTab(App* app)
|
||||
}
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
|
||||
// Fixed-height area so the modal height doesn't jump when switching avatar modes.
|
||||
const float avatarAreaH = 176.0f * dp;
|
||||
ImGui::BeginChild("##avArea", ImVec2(ImGui::GetContentRegionAvail().x, avatarAreaH), false,
|
||||
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
|
||||
// The mode content fills the rest of the avatar column (fixed height → no jump on switch).
|
||||
if (s_edit_avatar_mode == 0) {
|
||||
// Badge: centered explanatory hint.
|
||||
// Badge: explanatory hint, centered in the remaining space below the segmented control.
|
||||
const char* msg = TR("contact_avatar_badge_hint");
|
||||
ImFont* f = material::Type().caption();
|
||||
ImVec2 a = ImGui::GetContentRegionAvail();
|
||||
ImVec2 ts = f->CalcTextSizeA(f->LegacySize, a.x - 16.0f * dp, 0, msg);
|
||||
ImGui::SetCursorPos(ImVec2(std::max(0.0f, (a.x - ts.x) * 0.5f), a.y * 0.4f));
|
||||
ImGui::PushTextWrapPos(ImGui::GetCursorPosX() + std::min(ts.x, a.x));
|
||||
ImVec2 a = ImGui::GetContentRegionAvail(); // remaining w,h from the current cursor
|
||||
float wrapW = a.x - 16.0f * dp;
|
||||
ImVec2 ts = f->CalcTextSizeA(f->LegacySize, wrapW, 0, msg);
|
||||
float x0 = ImGui::GetCursorPosX(), y0 = ImGui::GetCursorPosY();
|
||||
ImGui::SetCursorPosX(x0 + std::max(0.0f, (a.x - ts.x) * 0.5f));
|
||||
ImGui::SetCursorPosY(y0 + std::max(0.0f, (a.y - ts.y) * 0.4f));
|
||||
ImGui::PushTextWrapPos(ImGui::GetCursorPosX() + std::min(ts.x, wrapW));
|
||||
material::Type().textColored(material::TypeStyle::Caption, material::OnSurfaceDisabled(), msg);
|
||||
ImGui::PopTextWrapPos();
|
||||
} else if (s_edit_avatar_mode == 1) {
|
||||
@@ -435,8 +447,9 @@ void RenderContactsTab(App* app)
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
material::Type().textColored(material::TypeStyle::Caption, material::OnSurfaceDisabled(), TR("contact_avatar_image_hint"));
|
||||
}
|
||||
ImGui::EndChild(); // ##avArea
|
||||
ImGui::EndChild(); // ##contactAvatarCol
|
||||
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
bool canSubmit = std::strlen(s_edit_label) > 0 && std::strlen(s_edit_address) > 0;
|
||||
float actionW = std::max(110.0f * dp,
|
||||
btnFont->CalcTextSizeA(btnFont->LegacySize, FLT_MAX, 0, TR("save")).x
|
||||
|
||||
Reference in New Issue
Block a user