Skip to content

Commit 62bc999

Browse files
committed
ui updtae
1 parent a1f9bda commit 62bc999

2 files changed

Lines changed: 107 additions & 32 deletions

File tree

src/gamecard.cpp

Lines changed: 38 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,16 @@ GameCard::GameCard(QWidget* parent)
1919

2020
void GameCard::setGameData(const QMap<QString, QString>& data) {
2121
m_data = data;
22+
// Auto-start shimmer animation if no thumbnail yet
23+
if (!m_hasThumbnail && !m_isSkeleton && !data.isEmpty()) {
24+
if (!m_skeletonTimer) {
25+
m_skeletonTimer = new QTimer(this);
26+
connect(m_skeletonTimer, &QTimer::timeout, this, &GameCard::updateSkeletonPulse);
27+
}
28+
m_skeletonPulse = 0.0;
29+
m_pulseIncreasing = true;
30+
m_skeletonTimer->start(30);
31+
}
2232
update();
2333
}
2434

@@ -29,7 +39,12 @@ QMap<QString, QString> GameCard::gameData() const {
2939
void GameCard::setThumbnail(const QPixmap& pixmap) {
3040
m_thumbnail = pixmap;
3141
m_hasThumbnail = !pixmap.isNull();
32-
if (m_hasThumbnail) extractDominantColor(pixmap);
42+
if (m_hasThumbnail) {
43+
extractDominantColor(pixmap);
44+
// Stop shimmer animation — real image arrived
45+
if (m_skeletonTimer) m_skeletonTimer->stop();
46+
m_skeletonPulse = 0.0;
47+
}
3348
update();
3449
}
3550

@@ -219,26 +234,31 @@ void GameCard::paintEvent(QPaintEvent* event) {
219234
int sx = (scaled.width() - cardSize.width()) / 2;
220235
int sy = (scaled.height() - cardSize.height()) / 2;
221236
painter.drawPixmap(cardRect.toRect(), scaled, QRect(sx, sy, cardSize.width(), cardSize.height()));
237+
} else if (!m_data.isEmpty()) {
238+
// No thumbnail yet — show skeleton shimmer instead of static gamepad icon
239+
QColor baseColor = Colors::toQColor(Colors::SURFACE_CONTAINER_HIGH);
240+
QColor pulseColor = Colors::toQColor(Colors::SURFACE_CONTAINER_HIGHEST);
241+
242+
// Animate pulse if timer is running, otherwise use static base
243+
qreal pulse = m_skeletonPulse;
244+
int r = baseColor.red() + (pulseColor.red() - baseColor.red()) * pulse;
245+
int g = baseColor.green() + (pulseColor.green() - baseColor.green()) * pulse;
246+
int b = baseColor.blue() + (pulseColor.blue() - baseColor.blue()) * pulse;
247+
QColor activeColor(r, g, b);
248+
painter.fillRect(cardRect.toRect(), activeColor);
249+
250+
// Animated shimmer sweep across the card
251+
qreal sweepPos = m_skeletonPulse;
252+
QLinearGradient shimmer(cardRect.left() + cardRect.width() * (sweepPos - 0.3), cardRect.top(),
253+
cardRect.left() + cardRect.width() * (sweepPos + 0.3), cardRect.bottom());
254+
shimmer.setColorAt(0, QColor(255, 255, 255, 0));
255+
shimmer.setColorAt(0.5, QColor(255, 255, 255, 18));
256+
shimmer.setColorAt(1, QColor(255, 255, 255, 0));
257+
painter.fillRect(cardRect.toRect(), shimmer);
222258
} else {
223-
// Material surface container background
259+
// Truly empty card — dark surface
224260
QColor surfaceColor = Colors::toQColor(Colors::SURFACE_CONTAINER_HIGH);
225261
painter.fillRect(cardRect.toRect(), surfaceColor);
226-
227-
// Subtle tonal overlay
228-
QRadialGradient glow(cardRect.center(), cardRect.height() * 0.6);
229-
glow.setColorAt(0, QColor(255, 255, 255, 15)); // Soft bright tint for glass
230-
glow.setColorAt(1, QColor(255, 255, 255, 0));
231-
painter.fillRect(cardRect.toRect(), glow);
232-
233-
// Gamepad icon placeholder
234-
QRectF iconArea(
235-
cardRect.center().x() - 28,
236-
cardRect.center().y() - 40,
237-
56, 56
238-
);
239-
QColor iconColor = Colors::toQColor(Colors::ON_SURFACE_VARIANT);
240-
iconColor.setAlpha(60);
241-
MaterialIcons::draw(painter, iconArea, iconColor, MaterialIcons::Gamepad);
242262
}
243263

244264
// ── Bottom info area ──

src/mainwindow.cpp

Lines changed: 69 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -827,30 +827,87 @@ void MainWindow::displayRandomGames() {
827827
imgLabel->setScaledContents(true);
828828
stack->addWidget(imgLabel);
829829

830+
// Dark gradient overlay (left-to-right for portrait+text layout)
830831
QWidget* overlay = new QWidget();
831832
overlay->setFixedHeight(240);
832833
overlay->setStyleSheet(
833-
"background: qlineargradient(x1:0, y1:1, x2:0, y2:0, stop:0 rgba(0,0,0,220), stop:0.4 rgba(0,0,0,0));"
834+
"background: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 rgba(0,0,0,210), stop:0.55 rgba(0,0,0,120), stop:1 rgba(0,0,0,30));"
834835
"border-radius: 12px;"
835836
);
836-
QVBoxLayout* overlayLayout = new QVBoxLayout(overlay);
837-
overlayLayout->addStretch(1);
837+
838+
// Horizontal layout: portrait on left, text info on right
839+
QHBoxLayout* heroLayout = new QHBoxLayout(overlay);
840+
heroLayout->setContentsMargins(20, 15, 20, 15);
841+
heroLayout->setSpacing(24);
842+
843+
// Portrait thumbnail (left side)
844+
QLabel* portraitLabel = new QLabel();
845+
portraitLabel->setFixedSize(130, 200);
846+
portraitLabel->setStyleSheet(
847+
"background: rgba(255,255,255,8); border: 1px solid rgba(255,255,255,15); border-radius: 8px;"
848+
);
849+
portraitLabel->setScaledContents(true);
850+
heroLayout->addWidget(portraitLabel);
851+
852+
// Game info column
853+
QVBoxLayout* infoLayout = new QVBoxLayout();
854+
infoLayout->setSpacing(6);
855+
infoLayout->addStretch(1);
856+
857+
QLabel* badgeLbl = new QLabel(QString::fromUtf8("\xe2\x98\x85 FEATURED"));
858+
badgeLbl->setStyleSheet(
859+
"font-size: 11px; font-weight: 700; color: #FFB74D; letter-spacing: 2px;"
860+
" background: transparent; border: none; font-family: 'Roboto', 'Segoe UI';"
861+
);
862+
infoLayout->addWidget(badgeLbl);
838863

839864
QLabel* nameLbl = new QLabel(featuredName);
840-
nameLbl->setStyleSheet("font-size: 26px; font-weight: bold; color: white; background: transparent; border: none;");
841-
nameLbl->setAlignment(Qt::AlignHCenter);
842-
overlayLayout->addWidget(nameLbl);
865+
nameLbl->setStyleSheet(
866+
"font-size: 26px; font-weight: bold; color: white; background: transparent; border: none;"
867+
" font-family: 'Roboto', 'Segoe UI';"
868+
);
869+
nameLbl->setWordWrap(true);
870+
infoLayout->addWidget(nameLbl);
843871

844872
QLabel* idLbl = new QLabel(QString("App ID: %1").arg(featuredId));
845-
idLbl->setStyleSheet("font-size: 14px; color: #aaaaaa; background: transparent; border: none;");
846-
idLbl->setAlignment(Qt::AlignHCenter);
847-
overlayLayout->addWidget(idLbl);
848-
overlayLayout->addSpacing(15);
873+
idLbl->setStyleSheet(
874+
"font-size: 13px; color: rgba(255,255,255,140); background: transparent; border: none;"
875+
" font-family: 'Roboto', 'Segoe UI';"
876+
);
877+
infoLayout->addWidget(idLbl);
878+
infoLayout->addStretch(1);
879+
880+
heroLayout->addLayout(infoLayout, 1);
849881

850882
stack->addWidget(overlay);
851883
m_heroStack->addWidget(slide);
852884

853-
// Fetch high-quality hero image, with fallback to low-res header
885+
// Fetch portrait thumbnail for left side
886+
QString portraitUrl = QString("https://cdn.akamai.steamstatic.com/steam/apps/%1/library_600x900_2x.jpg").arg(featuredId);
887+
QNetworkRequest portraitReq{QUrl(portraitUrl)};
888+
portraitReq.setHeader(QNetworkRequest::UserAgentHeader, "SteamLuaPatcher/2.0");
889+
QNetworkReply* portraitReply = m_networkManager->get(portraitReq);
890+
QPointer<QLabel> safePortrait(portraitLabel);
891+
connect(portraitReply, &QNetworkReply::finished, this, [portraitReply, safePortrait]() {
892+
portraitReply->deleteLater();
893+
if (portraitReply->error() == QNetworkReply::NoError && safePortrait) {
894+
QPixmap rawPix;
895+
if (rawPix.loadFromData(portraitReply->readAll())) {
896+
QPixmap rounded(130, 200);
897+
rounded.fill(Qt::transparent);
898+
QPainter p(&rounded);
899+
p.setRenderHint(QPainter::Antialiasing);
900+
QPainterPath clipPath;
901+
clipPath.addRoundedRect(rounded.rect(), 8, 8);
902+
p.setClipPath(clipPath);
903+
p.drawPixmap(rounded.rect(), rawPix.scaled(130, 200, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation));
904+
p.end();
905+
if (safePortrait) safePortrait->setPixmap(rounded);
906+
}
907+
}
908+
});
909+
910+
// Fetch high-quality hero image for background, with fallback to low-res header
854911
QString heroUrl = QString("https://cdn.akamai.steamstatic.com/steam/apps/%1/library_hero.jpg").arg(featuredId);
855912
QNetworkRequest req{QUrl(heroUrl)};
856913
req.setHeader(QNetworkRequest::UserAgentHeader, "SteamLuaPatcher/2.0");
@@ -864,21 +921,19 @@ void MainWindow::displayRandomGames() {
864921
if (heroReply->error() == QNetworkReply::NoError && safeImgLabel) {
865922
QPixmap rawPix;
866923
if (rawPix.loadFromData(heroReply->readAll())) {
867-
// Create rounded version to ensure corners aren't sharp
868924
QPixmap rounded(rawPix.size());
869925
rounded.fill(Qt::transparent);
870926
QPainter painter(&rounded);
871927
painter.setRenderHint(QPainter::Antialiasing);
872928
QPainterPath path;
873-
path.addRoundedRect(rounded.rect(), 35, 35); // Visual radius for the raw buffer
929+
path.addRoundedRect(rounded.rect(), 35, 35);
874930
painter.setClipPath(path);
875931
painter.drawPixmap(0, 0, rawPix);
876932

877933
if (safeImgLabel) safeImgLabel->setPixmap(rounded);
878934
success = true;
879935
}
880936
}
881-
// Fallback strategy if HD library hero is missing (fetch low-res header)
882937
if (!success && safeImgLabel && nm) {
883938
QString fallbackUrl = QString("https://cdn.akamai.steamstatic.com/steam/apps/%1/header.jpg").arg(featuredId);
884939
QNetworkRequest fallbackReq{QUrl(fallbackUrl)};

0 commit comments

Comments
 (0)