buildResultRow renders one table row. Layout: [No.] [Product] [Region] ... scrollable attrs ... [Min Price] [Max Price] The "no / product / region" and price columns are always pinned. attrKeys are the scrollable middle section; HScrollOffset determines which attr column is shown first. Only as ma
(idx int, attrKeys []string, colWidths map[string]int, selected bool)
| 1223 | // attr column is shown first. Only as many attrs as fit in the remaining inner |
| 1224 | // width are rendered, so the row is guaranteed never to exceed v.Width-2 chars. |
| 1225 | func (v *PricingView) buildResultRow(idx int, attrKeys []string, colWidths map[string]int, selected bool) string { |
| 1226 | _ = selected |
| 1227 | pad := func(s string, n int) string { |
| 1228 | if len(s) >= n { |
| 1229 | return s |
| 1230 | } |
| 1231 | return s + strings.Repeat(" ", n-len(s)) |
| 1232 | } |
| 1233 | |
| 1234 | // Width consumed by the five always-visible columns (each separated by 1 space). |
| 1235 | // no + sp + product + sp + region + sp + minprice + sp + maxprice |
| 1236 | pinnedWidth := colWidths["no"] + 1 + |
| 1237 | colWidths["product"] + 1 + |
| 1238 | colWidths["region"] + 1 + |
| 1239 | colWidths["minprice"] + 1 + |
| 1240 | colWidths["maxprice"] |
| 1241 | |
| 1242 | // Available inner width for the scrollable attr section. |
| 1243 | innerWidth := v.Width - 2 // subtract box left/right borders |
| 1244 | attrBudget := innerWidth - pinnedWidth |
| 1245 | |
| 1246 | // Determine which attr columns are visible starting at HScrollOffset. |
| 1247 | // Try to fill the budget completely — if starting at HScrollOffset leaves |
| 1248 | // empty space on the right, pull the offset back so columns stay full. |
| 1249 | offset := v.HScrollOffset |
| 1250 | if offset >= len(attrKeys) { |
| 1251 | offset = max(0, len(attrKeys)-1) |
| 1252 | } |
| 1253 | |
| 1254 | // First, find the maximum offset that still fills the budget. |
| 1255 | // Walk backwards from the end to find how many columns fit. |
| 1256 | if len(attrKeys) > 0 { |
| 1257 | maxOffset := len(attrKeys) - 1 |
| 1258 | used := 0 |
| 1259 | for i := len(attrKeys) - 1; i >= 0; i-- { |
| 1260 | needed := colWidths[attrKeys[i]] + 1 |
| 1261 | if used+needed > attrBudget { |
| 1262 | break |
| 1263 | } |
| 1264 | used += needed |
| 1265 | maxOffset = i |
| 1266 | } |
| 1267 | if offset > maxOffset { |
| 1268 | offset = maxOffset |
| 1269 | } |
| 1270 | } |
| 1271 | |
| 1272 | var visibleAttrs []string |
| 1273 | used := 0 |
| 1274 | for i := offset; i < len(attrKeys); i++ { |
| 1275 | needed := colWidths[attrKeys[i]] + 1 // +1 for leading separator |
| 1276 | if used+needed > attrBudget && len(visibleAttrs) > 0 { |
| 1277 | break |
| 1278 | } |
| 1279 | visibleAttrs = append(visibleAttrs, attrKeys[i]) |
| 1280 | used += needed |
| 1281 | } |
| 1282 |