|
1 | 1 | #include "gamecard.h" |
2 | 2 | #include "utils/colors.h" |
| 3 | +#include "materialicons.h" |
3 | 4 |
|
4 | 5 | #include <QPainter> |
5 | 6 | #include <QPainterPath> |
@@ -56,134 +57,164 @@ void GameCard::paintEvent(QPaintEvent* event) { |
56 | 57 | painter.setRenderHint(QPainter::Antialiasing); |
57 | 58 | painter.setRenderHint(QPainter::SmoothPixmapTransform); |
58 | 59 |
|
59 | | - QRect cardRect = rect().adjusted(5, 5, -5, -5); |
60 | | - int radius = 14; |
| 60 | + QRectF cardRect = QRectF(rect()).adjusted(4, 4, -4, -4); |
| 61 | + int radius = 16; // Material M3 standard |
61 | 62 | bool supported = (m_data.value("supported") == "true"); |
62 | 63 |
|
| 64 | + // ── Elevation shadow ── |
| 65 | + if (m_hovered || m_selected) { |
| 66 | + // Level 2 elevation |
| 67 | + for (int i = 4; i >= 1; --i) { |
| 68 | + QColor shadowColor(0, 0, 0, 12 * i); |
| 69 | + QPen shadowPen(shadowColor, 0.5); |
| 70 | + painter.setPen(shadowPen); |
| 71 | + painter.setBrush(Qt::NoBrush); |
| 72 | + painter.drawRoundedRect(cardRect.adjusted(-i, -i + 1, i, i + 1), radius + i, radius + i); |
| 73 | + } |
| 74 | + } else { |
| 75 | + // Level 1 elevation |
| 76 | + for (int i = 2; i >= 1; --i) { |
| 77 | + QColor shadowColor(0, 0, 0, 15 * i); |
| 78 | + QPen shadowPen(shadowColor, 0.5); |
| 79 | + painter.setPen(shadowPen); |
| 80 | + painter.setBrush(Qt::NoBrush); |
| 81 | + painter.drawRoundedRect(cardRect.adjusted(-i, i * 0.5, i, i + 0.5), radius + i, radius + i); |
| 82 | + } |
| 83 | + } |
| 84 | + |
63 | 85 | // Clip to rounded rect |
64 | 86 | QPainterPath clipPath; |
65 | 87 | clipPath.addRoundedRect(cardRect, radius, radius); |
66 | 88 | painter.setClipPath(clipPath); |
67 | 89 |
|
68 | 90 | if (m_hasThumbnail) { |
69 | | - // Stretch thumbnail to fill the entire card |
70 | | - painter.drawPixmap(cardRect, m_thumbnail); |
71 | | - |
72 | | - // Subtle dark vignette overlay for depth |
73 | | - QRadialGradient vignette(cardRect.center(), cardRect.width() * 0.7); |
74 | | - vignette.setColorAt(0, QColor(0, 0, 0, 0)); |
75 | | - vignette.setColorAt(1, QColor(0, 0, 0, 80)); |
76 | | - painter.fillRect(cardRect, vignette); |
| 91 | + // Stretch thumbnail to fill card |
| 92 | + painter.drawPixmap(cardRect.toRect(), m_thumbnail); |
77 | 93 | } else { |
78 | | - // Rich glass background gradient |
79 | | - QLinearGradient bg(cardRect.topLeft(), cardRect.bottomRight()); |
80 | | - bg.setColorAt(0, QColor(30, 41, 59, 220)); |
81 | | - bg.setColorAt(0.5, QColor(20, 30, 48, 230)); |
82 | | - bg.setColorAt(1, QColor(10, 18, 32, 240)); |
83 | | - painter.fillRect(cardRect, bg); |
84 | | - |
85 | | - // Subtle pattern: inner glow |
86 | | - QRadialGradient glow(cardRect.center(), cardRect.height() * 0.5); |
87 | | - glow.setColorAt(0, QColor(59, 130, 246, 15)); |
88 | | - glow.setColorAt(1, QColor(0, 0, 0, 0)); |
89 | | - painter.fillRect(cardRect, glow); |
90 | | - |
91 | | - // Large default gamepad icon |
92 | | - QFont iconFont("Segoe UI Emoji", 48); |
93 | | - painter.setFont(iconFont); |
94 | | - painter.setPen(QColor(148, 163, 184, 80)); |
| 94 | + // Material surface container background |
| 95 | + QColor surfaceColor = Colors::toQColor(Colors::SURFACE_CONTAINER_HIGH); |
| 96 | + painter.fillRect(cardRect.toRect(), surfaceColor); |
95 | 97 |
|
96 | | - QRect iconArea = cardRect.adjusted(0, -15, 0, -55); |
97 | | - painter.drawText(iconArea, Qt::AlignCenter, QString::fromUtf8("\xF0\x9F\x8E\xAE")); |
| 98 | + // Subtle tonal overlay |
| 99 | + QRadialGradient glow(cardRect.center(), cardRect.height() * 0.6); |
| 100 | + glow.setColorAt(0, QColor(208, 188, 255, 10)); // Primary tint |
| 101 | + glow.setColorAt(1, QColor(0, 0, 0, 0)); |
| 102 | + painter.fillRect(cardRect.toRect(), glow); |
| 103 | + |
| 104 | + // Gamepad icon placeholder |
| 105 | + QRectF iconArea( |
| 106 | + cardRect.center().x() - 28, |
| 107 | + cardRect.center().y() - 40, |
| 108 | + 56, 56 |
| 109 | + ); |
| 110 | + QColor iconColor = Colors::toQColor(Colors::ON_SURFACE_VARIANT); |
| 111 | + iconColor.setAlpha(60); |
| 112 | + MaterialIcons::draw(painter, iconArea, iconColor, MaterialIcons::Gamepad); |
98 | 113 | } |
99 | 114 |
|
100 | | - // ---- Bottom info gradient overlay ---- |
101 | | - int infoHeight = 65; |
102 | | - QRect infoRect(cardRect.left(), cardRect.bottom() - infoHeight, cardRect.width(), infoHeight); |
| 115 | + // ── Bottom info area ── |
| 116 | + int infoHeight = 62; |
| 117 | + QRectF infoRect(cardRect.left(), cardRect.bottom() - infoHeight, |
| 118 | + cardRect.width(), infoHeight); |
103 | 119 |
|
| 120 | + // Material surface overlay for readability |
104 | 121 | QLinearGradient infoGrad(infoRect.topLeft(), infoRect.bottomLeft()); |
105 | | - infoGrad.setColorAt(0, QColor(0, 0, 0, 0)); |
106 | | - infoGrad.setColorAt(0.25, QColor(0, 0, 0, 140)); |
107 | | - infoGrad.setColorAt(1, QColor(0, 0, 0, 230)); |
108 | | - painter.fillRect(infoRect, infoGrad); |
| 122 | + if (m_hasThumbnail) { |
| 123 | + infoGrad.setColorAt(0, QColor(0, 0, 0, 0)); |
| 124 | + infoGrad.setColorAt(0.3, QColor(28, 27, 31, 180)); |
| 125 | + infoGrad.setColorAt(1, QColor(28, 27, 31, 240)); |
| 126 | + } else { |
| 127 | + infoGrad.setColorAt(0, QColor(0, 0, 0, 0)); |
| 128 | + infoGrad.setColorAt(0.3, QColor(20, 18, 24, 120)); |
| 129 | + infoGrad.setColorAt(1, QColor(20, 18, 24, 180)); |
| 130 | + } |
| 131 | + painter.fillRect(infoRect.toRect(), infoGrad); |
109 | 132 |
|
110 | | - // Game name (larger, bolder) |
| 133 | + // Game name |
111 | 134 | QString name = m_data.value("name", "Unknown"); |
112 | | - QFont nameFont("Segoe UI", 10, QFont::Bold); |
| 135 | + QFont nameFont("Roboto", 10, QFont::DemiBold); |
| 136 | + nameFont.setStyleStrategy(QFont::PreferAntialias); |
113 | 137 | painter.setFont(nameFont); |
114 | | - painter.setPen(Qt::white); |
| 138 | + painter.setPen(Colors::toQColor(Colors::ON_SURFACE)); |
115 | 139 |
|
116 | | - QRect nameRect(infoRect.left() + 12, infoRect.top() + 10, infoRect.width() - 24, 22); |
| 140 | + QRectF nameRect(infoRect.left() + 12, infoRect.top() + 10, |
| 141 | + infoRect.width() - 24, 22); |
117 | 142 | QFontMetrics fm(nameFont); |
118 | | - QString elidedName = fm.elidedText(name, Qt::ElideRight, nameRect.width()); |
| 143 | + QString elidedName = fm.elidedText(name, Qt::ElideRight, (int)nameRect.width()); |
119 | 144 | painter.drawText(nameRect, Qt::AlignLeft | Qt::AlignVCenter, elidedName); |
120 | 145 |
|
121 | | - // App ID line |
122 | | - QFont idFont("Segoe UI", 8); |
| 146 | + // App ID |
| 147 | + QFont idFont("Roboto", 8); |
| 148 | + idFont.setStyleStrategy(QFont::PreferAntialias); |
123 | 149 | painter.setFont(idFont); |
124 | | - painter.setPen(QColor(180, 190, 210, 180)); |
125 | | - QRect idRect(infoRect.left() + 12, infoRect.top() + 36, infoRect.width() - 24, 20); |
| 150 | + painter.setPen(Colors::toQColor(Colors::ON_SURFACE_VARIANT)); |
| 151 | + QRectF idRect(infoRect.left() + 12, infoRect.top() + 34, |
| 152 | + infoRect.width() - 24, 18); |
126 | 153 | painter.drawText(idRect, Qt::AlignLeft | Qt::AlignVCenter, |
127 | 154 | QString("ID: %1").arg(m_data.value("appid", "?"))); |
128 | 155 |
|
129 | 156 | // Reset clip for border drawing |
130 | 157 | painter.setClipRect(rect()); |
131 | 158 |
|
132 | | - // ---- Border & glow effects ---- |
| 159 | + // ── Border & selection state ── |
133 | 160 | if (m_selected) { |
134 | | - // Outer glow |
135 | | - for (int i = 3; i >= 1; --i) { |
136 | | - QPen glowPen(QColor(59, 130, 246, 30 * i), i * 1.5); |
137 | | - painter.setPen(glowPen); |
138 | | - painter.setBrush(Qt::NoBrush); |
139 | | - painter.drawRoundedRect(cardRect.adjusted(-i, -i, i, i), radius + i, radius + i); |
140 | | - } |
141 | | - // Main selection border |
142 | | - QPen selPen(Colors::toQColor(Colors::ACCENT_BLUE), 2); |
| 161 | + // Material primary border |
| 162 | + QPen selPen(Colors::toQColor(Colors::PRIMARY), 2.5); |
143 | 163 | painter.setPen(selPen); |
144 | 164 | painter.setBrush(Qt::NoBrush); |
145 | 165 | painter.drawRoundedRect(cardRect, radius, radius); |
146 | 166 | } else if (m_hovered) { |
147 | | - // Hover glow |
148 | | - QPen hovPen(QColor(255, 255, 255, 50), 1.5); |
| 167 | + // Subtle outline on hover |
| 168 | + QPen hovPen(Colors::toQColor(Colors::OUTLINE), 1.2); |
149 | 169 | painter.setPen(hovPen); |
150 | 170 | painter.setBrush(Qt::NoBrush); |
151 | 171 | painter.drawRoundedRect(cardRect, radius, radius); |
152 | 172 |
|
153 | | - // Subtle inner highlight at top |
| 173 | + // Top highlight shimmer |
154 | 174 | painter.setClipPath(clipPath); |
155 | | - QLinearGradient topShine(cardRect.topLeft(), QPoint(cardRect.left(), cardRect.top() + 40)); |
156 | | - topShine.setColorAt(0, QColor(255, 255, 255, 20)); |
| 175 | + QLinearGradient topShine(cardRect.topLeft(), |
| 176 | + QPointF(cardRect.left(), cardRect.top() + 30)); |
| 177 | + topShine.setColorAt(0, QColor(255, 255, 255, 12)); |
157 | 178 | topShine.setColorAt(1, QColor(255, 255, 255, 0)); |
158 | | - painter.fillRect(QRect(cardRect.left(), cardRect.top(), cardRect.width(), 40), topShine); |
| 179 | + painter.fillRect(QRectF(cardRect.left(), cardRect.top(), |
| 180 | + cardRect.width(), 30), topShine); |
159 | 181 | painter.setClipRect(rect()); |
160 | 182 | } else { |
161 | | - // Resting border |
162 | | - QPen borderPen(QColor(255, 255, 255, 15), 1); |
| 183 | + // Resting outline variant |
| 184 | + QPen borderPen(Colors::toQColor(Colors::OUTLINE_VARIANT), 1); |
163 | 185 | painter.setPen(borderPen); |
164 | 186 | painter.setBrush(Qt::NoBrush); |
165 | 187 | painter.drawRoundedRect(cardRect, radius, radius); |
166 | 188 | } |
167 | 189 |
|
168 | | - // Supported badge (top-right corner) |
| 190 | + // ── Supported badge ── |
169 | 191 | if (supported) { |
170 | 192 | painter.setClipPath(clipPath); |
171 | | - // Green gradient badge |
172 | | - QRect badge(cardRect.right() - 28, cardRect.top() + 6, 22, 22); |
173 | | - QRadialGradient badgeGrad(badge.center(), 11); |
174 | | - badgeGrad.setColorAt(0, Colors::toQColor(Colors::ACCENT_GREEN)); |
175 | | - badgeGrad.setColorAt(1, QColor(16, 185, 129, 180)); |
| 193 | + |
| 194 | + QRectF badgeRect(cardRect.right() - 30, cardRect.top() + 6, 24, 24); |
| 195 | + |
| 196 | + // Material container background |
| 197 | + QColor badgeColor = Colors::toQColor(Colors::ACCENT_GREEN); |
| 198 | + QPainterPath badgePath; |
| 199 | + badgePath.addRoundedRect(badgeRect, 12, 12); |
| 200 | + painter.fillPath(badgePath, badgeColor); |
| 201 | + |
| 202 | + // Check icon |
| 203 | + QRectF checkRect = badgeRect.adjusted(4, 4, -4, -4); |
176 | 204 | painter.setPen(Qt::NoPen); |
177 | | - painter.setBrush(badgeGrad); |
178 | | - painter.drawEllipse(badge); |
| 205 | + painter.setBrush(Qt::NoBrush); |
| 206 | + |
| 207 | + QPen checkPen(QColor("#FFFFFF"), 2.2); |
| 208 | + checkPen.setCapStyle(Qt::RoundCap); |
| 209 | + checkPen.setJoinStyle(Qt::RoundJoin); |
| 210 | + painter.setPen(checkPen); |
179 | 211 |
|
180 | | - // Checkmark |
181 | | - painter.setPen(QPen(Qt::white, 2, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)); |
182 | 212 | QPainterPath check; |
183 | | - check.moveTo(badge.left() + 5, badge.center().y()); |
184 | | - check.lineTo(badge.center().x() - 1, badge.bottom() - 6); |
185 | | - check.lineTo(badge.right() - 5, badge.top() + 6); |
| 213 | + check.moveTo(checkRect.left() + 1, checkRect.center().y()); |
| 214 | + check.lineTo(checkRect.center().x() - 1, checkRect.bottom() - 2); |
| 215 | + check.lineTo(checkRect.right() - 1, checkRect.top() + 2); |
186 | 216 | painter.drawPath(check); |
| 217 | + |
187 | 218 | painter.setClipRect(rect()); |
188 | 219 | } |
189 | 220 | } |
|
0 commit comments