| 77 | } |
| 78 | |
| 79 | void BackupList::draw(bool focused) const |
| 80 | { |
| 81 | TextPool& text = TextPool::get(); |
| 82 | const size_t offset = mCursor.offset(); |
| 83 | const size_t index = mCursor.index(); |
| 84 | const size_t last = std::min(offset + mVisibleRows, mRows.size()); |
| 85 | |
| 86 | // Scrollbar: only when there is more than one viewport of rows. Drawn before the |
| 87 | // rows so the selected row's fill and outline sit on top of it. |
| 88 | if (mRows.size() > mVisibleRows) { |
| 89 | const int trackX = mx + mw - 3; |
| 90 | C2D_DrawRectSolid(trackX, my, 0.5f, 3, mh, COLOR_LINE); |
| 91 | const float frac = (float)mVisibleRows / (float)mRows.size(); |
| 92 | int thumbH = (int)(mh * frac); |
| 93 | if (thumbH < 12) { |
| 94 | thumbH = 12; |
| 95 | } |
| 96 | const float posFrac = (float)offset / (float)(mRows.size() - mVisibleRows); |
| 97 | const int thumbY = my + (int)((mh - thumbH) * posFrac); |
| 98 | C2D_DrawRectSolid(trackX, thumbY, 0.5f, 3, thumbH, COLOR_ACCENT); |
| 99 | } |
| 100 | |
| 101 | for (size_t i = offset; i < last; i++) { |
| 102 | const Row& r = mRows[i]; |
| 103 | const int rowY = my + (int)(i - offset) * mRowH; |
| 104 | const bool sel = i == index; |
| 105 | |
| 106 | if (sel) { |
| 107 | C2D_DrawRectSolid(mx, rowY, 0.5f, mw, mRowH, focused ? C2D_Color32(122, 66, 196, 70) : C2D_Color32(122, 66, 196, 40)); |
| 108 | if (focused) { |
| 109 | // Breathing outline + accent bar so the active row reads as live. |
| 110 | Gui::drawPulsingOutline(mx, rowY, mw, mRowH, 1, COLOR_RING); |
| 111 | } |
| 112 | C2D_DrawRectSolid(mx, rowY + 3, 0.5f, 2, mRowH - 6, COLOR_RING); // left accent bar |
| 113 | } |
| 114 | |
| 115 | const int textY = rowY + (mRowH - 13) / 2; |
| 116 | |
| 117 | // Leading marker: a solid teal tile with a dark glyph for the action rows |
| 118 | // ("+" for New backup, "↓" for Receive — high contrast in every row |
| 119 | // state), a small dot otherwise. |
| 120 | if (r.kind != RowKind::Existing) { |
| 121 | const int tileSz = 14, tileX = mx + 6, tileY = rowY + (mRowH - tileSz) / 2; |
| 122 | C2D_DrawRectSolid(tileX, tileY, 0.5f, tileSz, tileSz, COLOR_TEAL); |
| 123 | // Teal stays light in both themes, so a black glyph holds contrast either way. |
| 124 | const char* glyph = r.kind == RowKind::New ? "+" : "↓"; |
| 125 | const float pw = text.width(glyph, 0.5f); |
| 126 | const float lf = fontGetInfo(NULL)->lineFeed; |
| 127 | text.draw(glyph, tileX + (tileSz - pw) / 2, tileY - 2 + (tileSz - 0.5f * lf) / 2, 0.5f, COLOR_BLACK); |
| 128 | } |
| 129 | else { |
| 130 | C2D_DrawRectSolid(mx + 9, rowY + mRowH / 2 - 2, 0.5f, 4, 4, sel ? COLOR_RING : COLOR_FAINT); |
| 131 | } |
| 132 | |
| 133 | // Name (left) — near-white so it stays legible on the card and on the |
| 134 | // purple selection fill alike; muted only for unselected existing rows. |
| 135 | const u32 nameColor = (r.kind != RowKind::Existing || sel) ? COLOR_TEXT : COLOR_MUTED; |
| 136 | text.draw(r.name, mx + 26, textY, 0.45f, nameColor); |