refactor(market): extract pure portfolio grid-layout into pfComputeGridLayout

Pull the ~30-line row-major cell placement (honor stored placement else
auto-place) out of RenderMarketTab into a pure, deterministic helper taking
(entries, cols, minW, minH). Also bump the grid resize-readout buffer 16->32 to
silence a worst-case -Wformat-truncation the extraction surfaced. No behavior
change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-04 00:38:08 -05:00
parent 529a74f6a7
commit 0ae52ee095

View File

@@ -1143,6 +1143,44 @@ static void RenderPortfolioEditor(App* app)
bool PortfolioEditorActive() { return s_pfEdit.open; }
// Resolve each portfolio group's grid cell: honor a stored placement when it fits, else auto-place
// row-major into the first free cells. Pure + deterministic (no ImGui). One PfCell per entry.
static std::vector<PfCell> pfComputeGridLayout(
const std::vector<config::Settings::PortfolioEntry>& entries, int cols, int minW, int minH)
{
int n = (int)entries.size();
std::vector<PfCell> layout((size_t)std::max(0, n));
std::vector<std::vector<char>> occ;
auto ensure = [&](int rEnd) { while ((int)occ.size() < rEnd) occ.emplace_back(cols, 0); };
auto fits = [&](int c, int r, int w, int h) -> bool {
if (c < 0 || r < 0 || c + w > cols) return false;
ensure(r + h);
for (int rr = r; rr < r + h; rr++) for (int cc = c; cc < c + w; cc++) if (occ[rr][cc]) return false;
return true;
};
auto mark = [&](int c, int r, int w, int h) { ensure(r + h);
for (int rr = r; rr < r + h; rr++) for (int cc = c; cc < c + w; cc++) occ[rr][cc] = 1; };
std::vector<char> placed((size_t)std::max(0, n), 0);
for (int i = 0; i < n; i++) {
const auto& e = entries[i];
int w = std::max(minW, std::min(e.gridW, cols)), h = std::max(minH, e.gridH);
if (e.gridCol >= 0 && e.gridRow >= 0 && fits(e.gridCol, e.gridRow, w, h)) {
layout[i] = {e.gridCol, e.gridRow, w, h}; mark(e.gridCol, e.gridRow, w, h); placed[i] = 1;
}
}
for (int i = 0; i < n; i++) {
if (placed[i]) continue;
const auto& e = entries[i];
int w = std::max(minW, std::min(e.gridW, cols)), h = std::max(minH, e.gridH);
int fc = 0, fr = 0; bool found = false;
for (int r = 0; !found && r < 4096; r++)
for (int c = 0; c + w <= cols; c++)
if (fits(c, r, w, h)) { fc = c; fr = r; found = true; break; }
layout[i] = {fc, fr, w, h}; mark(fc, fr, w, h);
}
return layout;
}
void RenderMarketTab(App* app)
{
auto& S = schema::UI();
@@ -1237,37 +1275,7 @@ void RenderMarketTab(App* app)
float pfCellH = pfCellW;
// Resolve each group's grid cell: honor a stored placement when it fits, else auto-place
// row-major into the first free cells. Deterministic each frame.
std::vector<PfCell> pfLayout((size_t)std::max(0, pfN));
{
std::vector<std::vector<char>> occ;
auto ensure = [&](int rEnd) { while ((int)occ.size() < rEnd) occ.emplace_back(pfCols, 0); };
auto fits = [&](int c, int r, int w, int h) -> bool {
if (c < 0 || r < 0 || c + w > pfCols) return false;
ensure(r + h);
for (int rr = r; rr < r + h; rr++) for (int cc = c; cc < c + w; cc++) if (occ[rr][cc]) return false;
return true;
};
auto mark = [&](int c, int r, int w, int h) { ensure(r + h);
for (int rr = r; rr < r + h; rr++) for (int cc = c; cc < c + w; cc++) occ[rr][cc] = 1; };
std::vector<char> placed((size_t)pfN, 0);
for (int i = 0; i < pfN; i++) {
const auto& e = pfEntriesGeo[i];
int w = std::max(pfMinW, std::min(e.gridW, pfCols)), h = std::max(pfMinH, e.gridH);
if (e.gridCol >= 0 && e.gridRow >= 0 && fits(e.gridCol, e.gridRow, w, h)) {
pfLayout[i] = {e.gridCol, e.gridRow, w, h}; mark(e.gridCol, e.gridRow, w, h); placed[i] = 1;
}
}
for (int i = 0; i < pfN; i++) {
if (placed[i]) continue;
const auto& e = pfEntriesGeo[i];
int w = std::max(pfMinW, std::min(e.gridW, pfCols)), h = std::max(pfMinH, e.gridH);
int fc = 0, fr = 0; bool found = false;
for (int r = 0; !found && r < 4096; r++)
for (int c = 0; c + w <= pfCols; c++)
if (fits(c, r, w, h)) { fc = c; fr = r; found = true; break; }
pfLayout[i] = {fc, fr, w, h}; mark(fc, fr, w, h);
}
}
std::vector<PfCell> pfLayout = pfComputeGridLayout(pfEntriesGeo, pfCols, pfMinW, pfMinH);
int pfMaxRow = 0; for (const auto& L : pfLayout) pfMaxRow = std::max(pfMaxRow, L.row + L.h);
// Content height = the grid plus a couple of empty rows so cards can be dragged below the
// current bottom (the tab scrolls when the total exceeds the window).
@@ -2035,7 +2043,7 @@ void RenderMarketTab(App* app)
// Live size readout (e.g. "8\xC3\x972") so the reached grid span is visible — the
// minimum is pfMinW x pfMinH (8x2).
{
char szb[16]; snprintf(szb, sizeof(szb), "%d\xC3\x97%d", nw, nh);
char szb[32]; snprintf(szb, sizeof(szb), "%d\xC3\x97%d", nw, nh);
ImVec2 tsz = sub1->CalcTextSizeA(sub1->LegacySize, FLT_MAX, 0, szb);
ImVec2 tp((mn.x + mx.x) * 0.5f - tsz.x * 0.5f, (mn.y + mx.y) * 0.5f - tsz.y * 0.5f);
dl->AddRectFilled(ImVec2(tp.x - 6, tp.y - 3), ImVec2(tp.x + tsz.x + 6, tp.y + tsz.y + 3),