From ae4e6486c6cd20aa35d77f74f7d199a256b68b16 Mon Sep 17 00:00:00 2001 From: DanS Date: Thu, 2 Jul 2026 23:53:18 -0500 Subject: [PATCH] =?UTF-8?q?feat(market):=20portfolio=20editor=20=E2=80=94?= =?UTF-8?q?=20taller=20icon=20grid=20+=20custom=20color=20picker?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Reorder the Appearance column so Color sits above the icon grid, and let the icon grid fill the remaining height of the left column (via GetContentRegionAvail().y). Fixes the large empty space that sat below the color row and gives the icon picker much more room. - Add a custom-color circle after the preset swatches: it shows the current color when it isn't a preset (else a "+"), and clicking it opens a ColorPicker3 popup. The picked RGB packs straight into the group's color. New i18n key portfolio_custom_color. Full-node + Lite build clean; ctest 1/1; hygiene clean. Rendering change — needs a screenshot check. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/ui/windows/market_tab.cpp | 93 +++++++++++++++++++++++++---------- src/util/i18n.cpp | 1 + 2 files changed, 67 insertions(+), 27 deletions(-) diff --git a/src/ui/windows/market_tab.cpp b/src/ui/windows/market_tab.cpp index f800a67..2403f70 100644 --- a/src/ui/windows/market_tab.cpp +++ b/src/ui/windows/market_tab.cpp @@ -102,6 +102,7 @@ static unsigned int s_pf_color = 0; // selected accent (packed IM_COL32; 0 = de static char s_pf_search[64] = {0}; // address-list filter text static int s_pf_type_filter = 0; // 0 = all, 1 = shielded, 2 = transparent static bool s_pf_funded_only = false; +static float s_pf_custom_rgb[3] = {0.31f, 0.62f, 1.0f}; // working RGB for the custom color picker // Preset accent palette for portfolio groups (0 slot = "default / no color"). static const ImU32 kPfPalette[] = { @@ -256,11 +257,74 @@ static void RenderPortfolioEditor(App* app) ImGui::InputText("##pfLabel", s_pf_label, sizeof(s_pf_label)); ImGui::Dummy(ImVec2(0, Layout::spacingSm())); - // Icon picker + // Color picker: preset swatches + a custom-color circle that opens a picker popup. + { + float sw = 22.0f * dp; + ImGui::TextUnformatted(TR("portfolio_color")); + ImDrawList* cdl = ImGui::GetWindowDrawList(); + int nColors = (int)(sizeof(kPfPalette) / sizeof(kPfPalette[0])); + for (int c = 0; c <= nColors; c++) { // slot 0 = default / no color + if (c != 0) ImGui::SameLine(0, 5.0f * dp); + ImVec2 mn = ImGui::GetCursorScreenPos(); + ImVec2 cc(mn.x + sw * 0.5f, mn.y + sw * 0.5f); + bool isDefault = (c == 0); + bool sel = isDefault ? (s_pf_color == 0u) : (s_pf_color == kPfPalette[c - 1]); + bool hov = ImGui::IsMouseHoveringRect(mn, ImVec2(mn.x + sw, mn.y + sw)); + if (isDefault) cdl->AddCircle(cc, sw * 0.40f, OnSurfaceMedium(), 0, 1.5f * dp); + else cdl->AddCircleFilled(cc, sw * 0.40f, kPfPalette[c - 1]); + if (sel) cdl->AddCircle(cc, sw * 0.50f, OnSurface(), 0, 2.0f * dp); + else if (hov) cdl->AddCircle(cc, sw * 0.50f, WithAlpha(OnSurface(), 120), 0, 1.5f * dp); + ImGui::PushID(2000 + c); + ImGui::InvisibleButton("##pfcol", ImVec2(sw, sw)); + if (ImGui::IsItemClicked()) s_pf_color = isDefault ? 0u : kPfPalette[c - 1]; + ImGui::PopID(); + } + + // Custom-color circle: shows the current color when it isn't a preset, else a "+"; + // click opens a color-picker popup. + bool isCustom = (s_pf_color != 0u); + for (int c = 0; c < nColors; c++) + if (s_pf_color == kPfPalette[c]) isCustom = false; + ImGui::SameLine(0, 5.0f * dp); + ImVec2 mn = ImGui::GetCursorScreenPos(); + ImVec2 cc(mn.x + sw * 0.5f, mn.y + sw * 0.5f); + bool hov = ImGui::IsMouseHoveringRect(mn, ImVec2(mn.x + sw, mn.y + sw)); + if (isCustom) { + cdl->AddCircleFilled(cc, sw * 0.40f, (ImU32)s_pf_color); + } else { + cdl->AddCircle(cc, sw * 0.40f, OnSurfaceMedium(), 0, 1.5f * dp); + float pr = sw * 0.18f; + cdl->AddLine(ImVec2(cc.x - pr, cc.y), ImVec2(cc.x + pr, cc.y), OnSurfaceMedium(), 1.5f * dp); + cdl->AddLine(ImVec2(cc.x, cc.y - pr), ImVec2(cc.x, cc.y + pr), OnSurfaceMedium(), 1.5f * dp); + } + if (isCustom) cdl->AddCircle(cc, sw * 0.50f, OnSurface(), 0, 2.0f * dp); + else if (hov) cdl->AddCircle(cc, sw * 0.50f, WithAlpha(OnSurface(), 120), 0, 1.5f * dp); + ImGui::InvisibleButton("##pfcustomcol", ImVec2(sw, sw)); + if (ImGui::IsItemClicked()) { + ImU32 base = s_pf_color ? (ImU32)s_pf_color : IM_COL32(0x4F, 0x9D, 0xFF, 0xFF); + s_pf_custom_rgb[0] = ((base >> IM_COL32_R_SHIFT) & 0xFF) / 255.0f; + s_pf_custom_rgb[1] = ((base >> IM_COL32_G_SHIFT) & 0xFF) / 255.0f; + s_pf_custom_rgb[2] = ((base >> IM_COL32_B_SHIFT) & 0xFF) / 255.0f; + ImGui::OpenPopup("##pfCustomColor"); + } + if (hov) material::Tooltip("%s", TR("portfolio_custom_color")); + if (ImGui::BeginPopup("##pfCustomColor")) { + ImGui::ColorPicker3("##pfpick", s_pf_custom_rgb, + ImGuiColorEditFlags_NoSidePreview | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoLabel); + s_pf_color = IM_COL32((int)(s_pf_custom_rgb[0] * 255.0f + 0.5f), + (int)(s_pf_custom_rgb[1] * 255.0f + 0.5f), + (int)(s_pf_custom_rgb[2] * 255.0f + 0.5f), 255); + ImGui::EndPopup(); + } + } + ImGui::Dummy(ImVec2(0, Layout::spacingSm())); + + // Icon picker — fills the remaining height of the left column. { float cell = 28.0f * dp, cellGap = 4.0f * dp; ImGui::TextUnformatted(TR("portfolio_icon")); - ImGui::BeginChild("##pfIconGrid", ImVec2(0, cell * 3.0f + cellGap * 2.0f + Layout::spacingSm() * 2.0f), true); + float iconGridH = std::max(cell * 2.0f + cellGap, ImGui::GetContentRegionAvail().y); + ImGui::BeginChild("##pfIconGrid", ImVec2(0, iconGridH), true); ImDrawList* gdl = ImGui::GetWindowDrawList(); float availW = ImGui::GetContentRegionAvail().x; int cols = std::max(1, (int)((availW + cellGap) / (cell + cellGap))); @@ -299,31 +363,6 @@ static void RenderPortfolioEditor(App* app) } ImGui::EndChild(); } - ImGui::Dummy(ImVec2(0, Layout::spacingSm())); - - // Color picker - { - float sw = 22.0f * dp; - ImGui::TextUnformatted(TR("portfolio_color")); - ImDrawList* cdl = ImGui::GetWindowDrawList(); - int nColors = (int)(sizeof(kPfPalette) / sizeof(kPfPalette[0])); - for (int c = 0; c <= nColors; c++) { // slot 0 = default / no color - if (c != 0) ImGui::SameLine(0, 5.0f * dp); - ImVec2 mn = ImGui::GetCursorScreenPos(); - ImVec2 cc(mn.x + sw * 0.5f, mn.y + sw * 0.5f); - bool isDefault = (c == 0); - bool sel = isDefault ? (s_pf_color == 0u) : (s_pf_color == kPfPalette[c - 1]); - bool hov = ImGui::IsMouseHoveringRect(mn, ImVec2(mn.x + sw, mn.y + sw)); - if (isDefault) cdl->AddCircle(cc, sw * 0.40f, OnSurfaceMedium(), 0, 1.5f * dp); - else cdl->AddCircleFilled(cc, sw * 0.40f, kPfPalette[c - 1]); - if (sel) cdl->AddCircle(cc, sw * 0.50f, OnSurface(), 0, 2.0f * dp); - else if (hov) cdl->AddCircle(cc, sw * 0.50f, WithAlpha(OnSurface(), 120), 0, 1.5f * dp); - ImGui::PushID(2000 + c); - ImGui::InvisibleButton("##pfcol", ImVec2(sw, sw)); - if (ImGui::IsItemClicked()) s_pf_color = isDefault ? 0u : kPfPalette[c - 1]; - ImGui::PopID(); - } - } } ImGui::EndChild(); diff --git a/src/util/i18n.cpp b/src/util/i18n.cpp index 912ca23..eefff22 100644 --- a/src/util/i18n.cpp +++ b/src/util/i18n.cpp @@ -1123,6 +1123,7 @@ void I18n::loadBuiltinEnglish() strings_["portfolio_icon"] = "Icon"; strings_["portfolio_color"] = "Color"; strings_["portfolio_no_icon"] = "None"; + strings_["portfolio_custom_color"] = "Custom color\xE2\x80\xA6"; strings_["portfolio_appearance"] = "APPEARANCE"; strings_["portfolio_addresses_hdr"] = "ADDRESSES"; strings_["portfolio_search"] = "Search addresses\xE2\x80\xA6";