diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index 744f9e70a..92695a9d1 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -161,11 +161,14 @@ jobs: uses: actions/checkout@v7 - name: "Restore compiler cache (ccache)" - id: restore_ccache + id: ccache_restore uses: actions/cache/restore@v6 + env: + BRANCH_NAME: ${{ github.head_ref || github.ref_name }} with: - key: ccache-${{ matrix.distro }}${{ matrix.version }} + key: ccache-${{ matrix.distro }}${{ matrix.version }}-${{ env.BRANCH_NAME }} path: ${{ env.CACHE }} + restore-keys: ccache-${{ matrix.distro }}${{ matrix.version }}- - name: "Build ${{ matrix.distro }} ${{ matrix.version }} Docker image" shell: bash @@ -200,10 +203,10 @@ jobs: # Delete used cache to emulate a ccache update. See https://github.com/actions/cache/issues/342 - name: "Delete remote compiler cache (ccache)" - if: github.ref == 'refs/heads/master' && steps.restore_ccache.outputs.cache-hit + if: github.ref == 'refs/heads/master' && steps.ccache_restore.outputs.cache-hit continue-on-error: true env: - CACHE_PRIMARY_KEY: ${{ steps.restore_ccache.outputs.cache-primary-key }} + CACHE_PRIMARY_KEY: ${{ steps.ccache_restore.outputs.cache-primary-key }} GH_TOKEN: ${{ github.token }} run: | if gh cache delete --repo "$GITHUB_REPOSITORY" "$CACHE_PRIMARY_KEY"; then @@ -214,7 +217,7 @@ jobs: if: github.ref == 'refs/heads/master' uses: actions/cache/save@v6 with: - key: ${{ steps.restore_ccache.outputs.cache-primary-key }} + key: ${{ steps.ccache_restore.outputs.cache-primary-key }} path: ${{ env.CACHE }} - name: "Upload artifact" @@ -261,14 +264,13 @@ jobs: - os: macOS target: 13 # EOL 2025-09-15 runner: macos-15-intel # https://github.com/actions/runner-images/blob/main/images/macos/macos-15-Readme.md - # macos-26-intel is very slow and fails in CPack during DMG config if not increasing Finder timeout ccache_eviction_age: 7d cmake_generator: Ninja make_package: 1 override_target: 13 package_suffix: "-macOS13_Intel" - qt_version: 6.11.1 + qt_version: 6.11.0 qt_modules: qtimageformats qtmultimedia qtwebsockets soc: Intel type: Release @@ -277,48 +279,47 @@ jobs: - os: macOS target: 14 # EOL 2026-?? - runner: macos-26 # https://github.com/actions/runner-images/blob/main/images/macos/macos-26-arm64-Readme.md + runner: macos-15 # https://github.com/actions/runner-images/blob/main/images/macos/macos-15-arm64-Readme.md ccache_eviction_age: 7d cmake_generator: Ninja make_package: 1 override_target: 14 package_suffix: "-macOS14" - qt_version: 6.11.1 + qt_version: 6.11.0 qt_modules: qtimageformats qtmultimedia qtwebsockets soc: Apple type: Release use_ccache: 1 - xcode: "26.6" + xcode: "26.3" - os: macOS target: 15 - runner: macos-26 # https://github.com/actions/runner-images/blob/main/images/macos/macos-26-arm64-Readme.md + runner: macos-15 # https://github.com/actions/runner-images/blob/main/images/macos/macos-15-arm64-Readme.md ccache_eviction_age: 7d cmake_generator: Ninja make_package: 1 - override_target: 15 package_suffix: "-macOS15" - qt_version: 6.11.1 + qt_version: 6.11.0 qt_modules: qtimageformats qtmultimedia qtwebsockets soc: Apple type: Release use_ccache: 1 - xcode: "26.6" + xcode: "26.3" - os: macOS - target: 26 - runner: macos-26 # https://github.com/actions/runner-images/blob/main/images/macos/macos-26-arm64-Readme.md + target: 15 + runner: macos-15 # https://github.com/actions/runner-images/blob/main/images/macos/macos-15-arm64-Readme.md ccache_eviction_age: 7d cmake_generator: Ninja - qt_version: 6.11.1 + qt_version: 6.11.0 qt_modules: qtimageformats qtmultimedia qtwebsockets soc: Apple type: Debug use_ccache: 1 - xcode: "26.6" + xcode: "26.3" - os: Windows target: 10 @@ -328,7 +329,7 @@ jobs: cmake_generator_platform: x64 make_package: 1 package_suffix: "-Win10" - qt_version: 6.11.1 + qt_version: 6.11.0 qt_modules: qtimageformats qtmultimedia qtwebsockets type: Release @@ -348,6 +349,7 @@ jobs: - name: "[Windows] Add msbuild to PATH" if: matrix.os == 'Windows' + id: add-msbuild uses: microsoft/setup-msbuild@v3 with: msbuild-architecture: x64 @@ -358,11 +360,14 @@ jobs: - name: "[macOS] Restore compiler cache (ccache)" if: matrix.os == 'macOS' && matrix.use_ccache == 1 - id: restore_ccache + id: ccache_restore uses: actions/cache/restore@v6 + env: + BRANCH_NAME: ${{ github.head_ref || github.ref_name }} with: - key: ccache-${{ matrix.runner }}_${{ matrix.override_target }}-Xcode${{ matrix.xcode }} + key: ccache-${{ matrix.runner }}-${{ matrix.soc }}-${{ matrix.type }}-${{ env.BRANCH_NAME }} path: ${{ env.CCACHE_DIR }} + restore-keys: ccache-${{ matrix.runner }}-${{ matrix.soc }}-${{ matrix.type }}- - name: "Install aqtinstall" run: pipx install aqtinstall @@ -380,7 +385,7 @@ jobs: id: restore_qt uses: actions/cache/restore@v6 with: - key: Qt-${{ steps.resolve_qt_version.outputs.version }}-macOS-${{ matrix.soc }}-${{ matrix.qt_modules }}-thin + key: thin-qt-macos-${{ matrix.soc }}-${{ steps.resolve_qt_version.outputs.version }} path: ${{ github.workspace }}/Qt # Using jurplel/install-qt-action to install Qt without using brew @@ -402,7 +407,7 @@ jobs: if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true' uses: actions/cache/save@v6 with: - key: ${{ steps.restore_qt.outputs.cache-primary-key }} + key: thin-qt-macos-${{ matrix.soc }}-${{ steps.resolve_qt_version.outputs.version }} path: ${{ github.workspace }}/Qt - name: "[Windows] Install Qt ${{ matrix.qt_version }}" @@ -412,7 +417,6 @@ jobs: # Qt 6.11.0 only works with aqtinstall directly from git until aqtinstall 3.4 is released aqtsource: git+https://github.com/miurahr/aqtinstall.git cache: true - cache-key-prefix: Qt modules: ${{ matrix.qt_modules }} version: ${{ steps.resolve_qt_version.outputs.version }} @@ -453,21 +457,21 @@ jobs: # Delete used cache to emulate a ccache update. See https://github.com/actions/cache/issues/342 - name: "[macOS] Delete remote compiler cache (ccache)" - if: matrix.os == 'macOS' && matrix.use_ccache == 1 && github.ref == 'refs/heads/master' && steps.restore_ccache.outputs.cache-hit + if: matrix.os == 'macOS' && matrix.use_ccache == 1 && github.ref == 'refs/heads/master' && steps.ccache_restore.outputs.cache-hit continue-on-error: true env: - CACHE_PRIMARY_KEY: ${{ steps.restore_ccache.outputs.cache-primary-key }} + CACHE_PRIMARY_KEY: ${{ steps.ccache_restore.outputs.cache-primary-key }} GH_TOKEN: ${{ github.token }} run: | if gh cache delete --repo "$GITHUB_REPOSITORY" "$CACHE_PRIMARY_KEY"; then echo "Cache deleted successfully" fi - - name: "[macOS] Cache updated compiler cache (ccache)" + - name: "[macOS] Save updated compiler cache (ccache)" if: matrix.os == 'macOS' && matrix.use_ccache == 1 && github.ref == 'refs/heads/master' uses: actions/cache/save@v6 with: - key: ${{ steps.restore_ccache.outputs.cache-primary-key }} + key: ${{ steps.ccache_restore.outputs.cache-primary-key }} path: ${{ env.CCACHE_DIR }} - name: "[macOS] Sign app bundle" diff --git a/cockatrice/resources/countries/cn.svg b/cockatrice/resources/countries/cn.svg index 45b608127..f510cf049 100644 --- a/cockatrice/resources/countries/cn.svg +++ b/cockatrice/resources/countries/cn.svg @@ -52,7 +52,8 @@ id="defs8"> - + diff --git a/cockatrice/src/interface/pixel_map_generator.cpp b/cockatrice/src/interface/pixel_map_generator.cpp index 5bfba1c8a..d3b0252a6 100644 --- a/cockatrice/src/interface/pixel_map_generator.cpp +++ b/cockatrice/src/interface/pixel_map_generator.cpp @@ -14,32 +14,6 @@ #define DEFAULT_COLOR_MODERATOR_RIGHT "#000000"; #define DEFAULT_COLOR_ADMIN "#ff2701"; -/** - * Clamps an svg render size so that rendering does not exceed a multiple of the requested size. - * - * Rendering at the full native size of an svg just to scale it down afterwards wastes memory, - * and canvases with extreme coordinates can exceed Qt's rasterizer coordinate limit which makes - * Qt silently drop shapes from the rendered image. - * - * @param renderSize The size the svg would be rendered at. - * @param requestedSize The size that was actually requested. - * - * @return A size with the aspect ratio of renderSize whose longest side is at most four times - * the longest side of requestedSize. - */ -static QSize capRenderSize(const QSize &renderSize, const QSize &requestedSize) -{ - const int longestRequestedSide = qMax(requestedSize.width(), requestedSize.height()); - if (longestRequestedSide <= 0) { - return renderSize; - } - - const int longestRenderSide = qMax(renderSize.width(), renderSize.height()); - const qreal scale = qMin(1.0, static_cast(longestRequestedSide * 4) / longestRenderSide); - return QSize(qMax(1, static_cast(renderSize.width() * scale)), - qMax(1, static_cast(renderSize.height() * scale))); -} - /** * Loads in an svg from file and scales it without affecting image quality. * @@ -61,9 +35,6 @@ static QPixmap loadSvg(const QString &svgPath, const QSize &size, bool expandOnl // If expandOnly, make sure the pixmap is at least as large as the svg, so that we don't lose any detail. // QIcon.pixmap(size) will automatically scale down the image, but it won't scale it up. QSize pixmapSize = expandOnly ? svgRenderer.defaultSize().expandedTo(size) : size; - if (expandOnly) { - pixmapSize = capRenderSize(pixmapSize, size); - } QPixmap pix(pixmapSize); pix.fill(Qt::transparent); @@ -276,9 +247,7 @@ static QIcon loadAndColorSvg(const QString &iconPath, QSvgRenderer svgRenderer(doc.toByteArray()); - const QSize pixmapSize = - capRenderSize(svgRenderer.defaultSize().expandedTo(QSize(minSize, minSize)), QSize(minSize, minSize)); - QPixmap pix(pixmapSize); + QPixmap pix(svgRenderer.defaultSize().expandedTo(QSize(minSize, minSize))); pix.fill(Qt::transparent); QPainter pixPainter(&pix); diff --git a/cockatrice/src/interface/widgets/server/user/user_card_art_provider.cpp b/cockatrice/src/interface/widgets/server/user/user_card_art_provider.cpp index 3a1876fa1..67fb4f684 100644 --- a/cockatrice/src/interface/widgets/server/user/user_card_art_provider.cpp +++ b/cockatrice/src/interface/widgets/server/user/user_card_art_provider.cpp @@ -39,10 +39,7 @@ void UserCardArtProvider::requestCardArt(const QString &userName, const QString const QString key = makeKey(userName, cardName, providerId); - if (pending.contains(key)) { - return; - } - if (cardArtCache.contains(key) && !cardArtCache.value(key).isNull()) { + if (cardArtCache.contains(key) || pending.contains(key)) { return; } @@ -66,10 +63,6 @@ QPixmap UserCardArtProvider::cropCardArt(const QPixmap &fullRes) void UserCardArtProvider::insertIntoCache(const QString &key, const QPixmap &pixmap) { - if (pixmap.isNull()) { - return; - } - if (!cardArtCache.contains(key)) { cacheInsertionOrder.append(key); while (cacheInsertionOrder.size() > MaxCacheEntries) { @@ -136,6 +129,8 @@ void UserCardArtProvider::processQueue() if (!fullRes.isNull()) { self->insertIntoCache(key, self->cropCardArt(fullRes)); + } else { + self->insertIntoCache(key, QPixmap()); } self->pending.remove(key); diff --git a/cockatrice/src/interface/widgets/server/user/user_card_art_provider.h b/cockatrice/src/interface/widgets/server/user/user_card_art_provider.h index 2592237c4..fb2f37812 100644 --- a/cockatrice/src/interface/widgets/server/user/user_card_art_provider.h +++ b/cockatrice/src/interface/widgets/server/user/user_card_art_provider.h @@ -26,7 +26,7 @@ public slots: private: bool dbReady = false; - static constexpr int MaxCacheEntries = 1024; + static constexpr int MaxCacheEntries = 300; QList cacheInsertionOrder; // FIFO eviction QMap cardArtCache; QSet pending; diff --git a/cockatrice/src/interface/widgets/server/user/user_list_painter.cpp b/cockatrice/src/interface/widgets/server/user/user_list_painter.cpp index 5c65b090d..82f2887c8 100644 --- a/cockatrice/src/interface/widgets/server/user/user_list_painter.cpp +++ b/cockatrice/src/interface/widgets/server/user/user_list_painter.cpp @@ -101,21 +101,23 @@ void UserListPainter::drawBackground(QPainter *painter, bg.setColorAt(0, blend(style.cardStart, accentColor, selected ? 0.75 : 0.65)); bg.setColorAt(1, blend(style.cardEnd, accentColor, selected ? 0.18 : 0.10)); } else { - // Regular users keep a scaled-down accent tint so the banner card art - // stays legible over a colored backdrop (the pre-branch painter was - // always dark-styled) while the role hierarchy still reads. - bg.setColorAt(0, blend(style.cardStart, accentColor, (selected ? 0.75 : 0.65) * 0.7)); - bg.setColorAt(1, blend(style.cardEnd, accentColor, (selected ? 0.18 : 0.10) * 0.7)); + // Regular users are the light theme's neutral paper cards. A flat + // warm card fill (the normal row surface, slightly deepened) keeps + // every row clearly visible without borrowing a role color. Selection + // shifts the fill toward a soft slate so the highlight still reads. + const QColor paper = style.cardEnd.darker(108); + bg.setColorAt(0, blend(paper, accentColor, selected ? 0.35 : 0.0)); + bg.setColorAt(1, blend(paper, accentColor, selected ? 0.25 : 0.0)); } painter->setPen(Qt::NoPen); painter->setBrush(bg); painter->drawRoundedRect(cardRect, 6, 6); - // The 3px accent bar anchors every row so the banner card art reads as a - // consistent strip in either scheme (pre-branch parity). - painter->setBrush(accentColor); - painter->drawRoundedRect(QRectF(cardRect.left(), cardRect.top(), 3, cardRect.height()), 2, 2); + if (style.dark || hasRole || selected) { + painter->setBrush(accentColor); + painter->drawRoundedRect(QRectF(cardRect.left(), cardRect.top(), 3, cardRect.height()), 2, 2); + } } static QString makeKey(const QString &user, const QString &card, const QString &providerId) diff --git a/cockatrice/src/interface/widgets/server/user/user_list_widget.cpp b/cockatrice/src/interface/widgets/server/user/user_list_widget.cpp index e71eac23b..2534ee62c 100644 --- a/cockatrice/src/interface/widgets/server/user/user_list_widget.cpp +++ b/cockatrice/src/interface/widgets/server/user/user_list_widget.cpp @@ -1372,13 +1372,9 @@ void UserListWidget::updateCardArtParams(const ServerInfo_User &user, const QStr params.zoom = cap.zoom(); cardArtParamsMap.insert(userName, params); cardArtProvider->requestCardArt(userName, params.cardName, params.cardProviderId); + } else { + cardArtParamsMap.remove(userName); // clear stale params on removal } - // Intentionally no removal branch: buddy/ignore list copies never carry - // card_art_params (the server omits the column), so a params-less copy here - // means "this snapshot doesn't include it", not "the banner was removed". - // Removing on such copies would wipe banners that the live online list set. - // The map is rebuilt from scratch (clear() + repopulate) on every rebuild, - // which is what actually drops stale entries. } void UserListWidget::processUserInfo(const ServerInfo_User &user, bool online)