diff --git a/.ci/Arch/Dockerfile b/.ci/Arch/Dockerfile
index f37315262..36cf5c4ae 100644
--- a/.ci/Arch/Dockerfile
+++ b/.ci/Arch/Dockerfile
@@ -10,10 +10,8 @@ RUN pacman --sync --refresh --sysupgrade --needed --noconfirm \
ninja \
protobuf \
qt6-base \
- qt6-declarative \
qt6-imageformats \
qt6-multimedia \
- qt6-shadertools \
qt6-svg \
qt6-tools \
qt6-translations \
diff --git a/.ci/Debian12/Dockerfile b/.ci/Debian12/Dockerfile
index 0fa227d6f..202405b84 100644
--- a/.ci/Debian12/Dockerfile
+++ b/.ci/Debian12/Dockerfile
@@ -20,9 +20,7 @@ RUN apt-get update && \
qt6-image-formats-plugins \
qt6-l10n-tools \
qt6-multimedia-dev \
- qt6-declarative-dev \
qt6-svg-dev \
- qt6-shadertools-dev \
qt6-tools-dev \
qt6-tools-dev-tools \
qt6-websockets-dev \
diff --git a/.ci/Debian13/Dockerfile b/.ci/Debian13/Dockerfile
index 13e8b35c7..d7ab6ac86 100644
--- a/.ci/Debian13/Dockerfile
+++ b/.ci/Debian13/Dockerfile
@@ -21,9 +21,7 @@ RUN apt-get update && \
qt6-image-formats-plugins \
qt6-l10n-tools \
qt6-multimedia-dev \
- qt6-declarative-dev \
qt6-svg-dev \
- qt6-shadertools-dev \
qt6-tools-dev \
qt6-tools-dev-tools \
qt6-websockets-dev \
diff --git a/.ci/Fedora43/Dockerfile b/.ci/Fedora43/Dockerfile
index 68e894543..27570cf99 100644
--- a/.ci/Fedora43/Dockerfile
+++ b/.ci/Fedora43/Dockerfile
@@ -8,7 +8,7 @@ RUN dnf install -y \
mariadb-devel \
ninja-build \
protobuf-devel \
- qt6-{qtdeclarative,qtshadertools,qttools,qtsvg,qtmultimedia,qtwebsockets}-devel \
+ qt6-{qttools,qtsvg,qtmultimedia,qtwebsockets}-devel \
qt6-qtimageformats \
rpm-build \
xz-devel \
diff --git a/.ci/Fedora44/Dockerfile b/.ci/Fedora44/Dockerfile
index ffd7c1b9b..e6c8da7f3 100644
--- a/.ci/Fedora44/Dockerfile
+++ b/.ci/Fedora44/Dockerfile
@@ -8,7 +8,7 @@ RUN dnf install -y \
mariadb-devel \
ninja-build \
protobuf-devel \
- qt6-{qtdeclarative,qtshadertools,qttools,qtsvg,qtmultimedia,qtwebsockets}-devel \
+ qt6-{qttools,qtsvg,qtmultimedia,qtwebsockets}-devel \
qt6-qtimageformats \
rpm-build \
xz-devel \
diff --git a/.ci/Ubuntu24.04/Dockerfile b/.ci/Ubuntu24.04/Dockerfile
index 12320c276..809b2e43a 100644
--- a/.ci/Ubuntu24.04/Dockerfile
+++ b/.ci/Ubuntu24.04/Dockerfile
@@ -20,9 +20,7 @@ RUN apt-get update && \
qt6-image-formats-plugins \
qt6-l10n-tools \
qt6-multimedia-dev \
- qt6-declarative-dev \
qt6-svg-dev \
- qt6-shadertools-dev \
qt6-tools-dev \
qt6-tools-dev-tools \
qt6-websockets-dev \
diff --git a/.ci/Ubuntu26.04/Dockerfile b/.ci/Ubuntu26.04/Dockerfile
index ce3d9cd6c..7b0cd389f 100644
--- a/.ci/Ubuntu26.04/Dockerfile
+++ b/.ci/Ubuntu26.04/Dockerfile
@@ -21,9 +21,7 @@ RUN apt-get update && \
qt6-image-formats-plugins \
qt6-l10n-tools \
qt6-multimedia-dev \
- qt6-declarative-dev \
qt6-svg-dev \
- qt6-shadertools-dev \
qt6-tools-dev \
qt6-tools-dev-tools \
qt6-websockets-dev \
diff --git a/.ci/release_template.md b/.ci/release_template.md
index 23b475150..ac78a193a 100644
--- a/.ci/release_template.md
+++ b/.ci/release_template.md
@@ -12,9 +12,9 @@ Available pre-compiled binaries for installation:
• Windows 10+
macOS
- • macOS 15+ Sequoia
- • macOS 14+ Sonoma
- • macOS 13+ Ventura (x86)
+ • macOS 15+ Sequoia Apple M
+ • macOS 14+ Sonoma Apple M
+ • macOS 13+ Ventura Intel
Linux
• Ubuntu 26.04 LTS Resolute Racoon
@@ -24,10 +24,10 @@ Available pre-compiled binaries for installation:
• Fedora 44
• Fedora 43
- General Linux support is available via a flatpak package hosted at Flathub (x86 & ARM)!
- Thanks to courtesy of @FFY00, the app is also available in Arch Linux's official extra repository.
+We are also packaged in Arch Linux's official extra repository, courtesy of @FFY00.
+General Linux support is available via a flatpak package at Flathub!
- We maintain a Docker image for "Servatrice" in GHCR (x86 & ARM). You can docker pull it or use our Docker Compose files!
+We provide a Docker image for "Servatrice" in GHCR. You can docker pull it or use our Docker Compose files!
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
deleted file mode 100644
index e895e2220..000000000
--- a/.github/workflows/codeql.yml
+++ /dev/null
@@ -1,95 +0,0 @@
-# GitHub Docs on Code Scanning:
-# https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning
-# https://docs.github.com/en/code-security/how-tos/find-and-fix-code-vulnerabilities/manage-your-configuration
-# https://docs.github.com/en/code-security/reference/code-scanning/workflow-configuration-options
-#
-# CodeQL Docs:
-# https://codeql.github.com/docs/
-
-name: CodeQL
-
-permissions:
- contents: read
- security-events: write # needed to post results
-
-on:
- push:
- branches:
- - master
- pull_request:
-
-# Cancel earlier, unfinished runs of this workflow on the same branch
-concurrency:
- group: "${{ github.workflow }} @ ${{ github.ref_name }}"
- cancel-in-progress: true
-
-jobs:
- analyze:
- name: Analyze (${{ matrix.language }})
- runs-on: ubuntu-latest
-
- strategy:
- fail-fast: false
- matrix:
- include:
- # https://codeql.github.com/docs/codeql-overview/supported-languages-and-frameworks/
- - language: cpp
- build-mode: manual
- - language: actions
- build-mode: none
-
- steps:
- - name: "Checkout repository"
- uses: actions/checkout@v6
-
- - name: "Initialize CodeQL"
- uses: github/codeql-action/init@v4
- with:
- languages: ${{ matrix.language }}
- build-mode: ${{ matrix.build-mode }}
- # https://docs.github.com/en/code-security/reference/code-scanning/codeql/codeql-queries/c-cpp-built-in-queries
- # https://docs.github.com/en/code-security/reference/code-scanning/codeql/codeql-queries/actions-built-in-queries
- queries: security-extended
- dependency-caching: true
-
- - name: "[C++] Install dependencies"
- if: matrix.language == 'cpp' && matrix.build-mode == 'manual'
- shell: bash
- env:
- DEBIAN_FRONTEND: noninteractive
- run: |
- sudo apt-get update
- sudo apt-get install -y --no-install-recommends \
- cmake \
- g++ \
- libgl-dev \
- liblzma-dev \
- libmariadb-dev-compat \
- libprotobuf-dev \
- ninja-build \
- protobuf-compiler \
- qt6-declarative-dev \
- qt6-multimedia-dev \
- qt6-shadertools-dev \
- qt6-svg-dev \
- qt6-tools-dev \
- qt6-tools-dev-tools \
- qt6-websockets-dev
-
-# Minimize dependency install
-# Add ccache usage for faster compilation, (install ccache dep, actions/cache step + append DUSE_CCACHE=1 in cmake config, CCACHE env values)
-
- - name: "[C++] Configure CMake"
- if: matrix.language == 'cpp' && matrix.build-mode == 'manual'
- shell: bash
- run: cmake -S . -B build -G Ninja -DWITH_SERVER=1 -DCMAKE_BUILD_TYPE=Release
-
- - name: "[C++] Build application"
- if: matrix.language == 'cpp' && matrix.build-mode == 'manual'
- shell: bash
- run: cmake --build build
-
- - name: "Perform CodeQL Analysis"
- uses: github/codeql-action/analyze@v4
- with:
- category: "/language:${{ matrix.language }}"
diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml
index 04037a74e..f1846ecf6 100644
--- a/.github/workflows/desktop-build.yml
+++ b/.github/workflows/desktop-build.yml
@@ -3,7 +3,7 @@ name: Build Desktop
permissions:
actions: write # needed to delete entries in GHA cache (update ccache)
attestations: write # needed to persist the attestation.
- contents: write # needed for e.g. vcpkg dependency graph updates
+ contents: write
id-token: write # needed for signing certificate in attestation
on:
@@ -34,7 +34,7 @@ on:
- 'vcpkg.json'
- 'vcpkg' # needed to match submodule bumps (gitlink)
-# Cancel earlier, unfinished runs of this workflow on the same branch (unless on tag --> release)
+# Cancel earlier, unfinished runs of this workflow on the same branch (unless on release)
concurrency:
group: "${{ github.workflow }} @ ${{ github.ref_name }}"
cancel-in-progress: ${{ github.ref_type != 'tag' }}
@@ -42,7 +42,7 @@ concurrency:
jobs:
configure:
name: Configure
- runs-on: ubuntu-slim # https://github.com/actions/runner-images/blob/main/images/ubuntu-slim/ubuntu-slim-Readme.md
+ runs-on: ubuntu-slim
outputs:
tag: ${{ steps.configure.outputs.tag }}
sha: ${{ steps.configure.outputs.sha }}
@@ -146,7 +146,7 @@ jobs:
name: ${{ matrix.distro }} ${{ matrix.version }}
needs: configure
- runs-on: ubuntu-latest # https://github.com/actions/runner-images
+ runs-on: ubuntu-latest
continue-on-error: ${{ matrix.allow-failure == 'yes' }}
timeout-minutes: 70
env:
@@ -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"
@@ -259,77 +262,74 @@ jobs:
matrix:
include:
- 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
+ target: 13
+ runner: macos-15-intel
ccache_eviction_age: 7d
cmake_generator: Ninja
make_package: 1
override_target: 13
package_suffix: "-macOS13_Intel"
- qt_version: 6.11.1
- qt_modules: qtimageformats qtmultimedia qtwebsockets qtshadertools
+ qt_version: 6.11.0
+ qt_modules: qtimageformats qtmultimedia qtwebsockets
soc: Intel
type: Release
use_ccache: 1
- xcode: "26.3"
+ xcode: "16.4"
- os: macOS
- target: 14 # EOL 2026-??
- runner: macos-26 # https://github.com/actions/runner-images/blob/main/images/macos/macos-26-arm64-Readme.md
+ target: 14
+ runner: macos-14
ccache_eviction_age: 7d
cmake_generator: Ninja
make_package: 1
- override_target: 14
package_suffix: "-macOS14"
- qt_version: 6.11.1
- qt_modules: qtimageformats qtmultimedia qtwebsockets qtshadertools
+ qt_version: 6.11.0
+ qt_modules: qtimageformats qtmultimedia qtwebsockets
soc: Apple
type: Release
use_ccache: 1
- xcode: "26.6"
+ xcode: "15.4"
- 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
ccache_eviction_age: 7d
cmake_generator: Ninja
make_package: 1
- override_target: 15
package_suffix: "-macOS15"
- qt_version: 6.11.1
- qt_modules: qtimageformats qtmultimedia qtwebsockets qtshadertools
+ qt_version: 6.11.0
+ qt_modules: qtimageformats qtmultimedia qtwebsockets
soc: Apple
type: Release
use_ccache: 1
- xcode: "26.6"
+ xcode: "16.4"
- 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
ccache_eviction_age: 7d
cmake_generator: Ninja
- qt_version: 6.11.1
- qt_modules: qtimageformats qtmultimedia qtwebsockets qtshadertools
+ qt_version: 6.11.0
+ qt_modules: qtimageformats qtmultimedia qtwebsockets
soc: Apple
type: Debug
use_ccache: 1
- xcode: "26.6"
+ xcode: "16.4"
- os: Windows
target: 10
- runner: windows-2025 # https://github.com/actions/runner-images/blob/main/images/windows/Windows2025-VS2026-Readme.md
+ runner: windows-2025
cmake_generator: "Visual Studio 18 2026"
cmake_generator_platform: x64
make_package: 1
package_suffix: "-Win10"
- qt_version: 6.11.1
- qt_modules: qtimageformats qtmultimedia qtwebsockets qtshadertools
+ qt_version: 6.11.0
+ qt_modules: qtimageformats qtmultimedia qtwebsockets
type: Release
name: ${{ matrix.os }} ${{ matrix.target }}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }}
@@ -348,6 +348,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 +359,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 +384,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 +406,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 +416,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 }}
@@ -437,7 +440,6 @@ jobs:
CMAKE_GENERATOR: ${{ matrix.cmake_generator }}
CMAKE_GENERATOR_PLATFORM: ${{ matrix.cmake_generator_platform }}
DEVELOPER_DIR: '/Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer'
- GITHUB_TOKEN: ${{ github.token }} # needed for vcpkg dependency graph updates, see VCPKG_FEATURE_FLAGS
MACOS_CERTIFICATE: ${{ secrets.PROD_MACOS_CERTIFICATE }}
MACOS_CERTIFICATE_NAME: ${{ secrets.PROD_MACOS_CERTIFICATE_NAME }}
MACOS_CERTIFICATE_PWD: ${{ secrets.PROD_MACOS_CERTIFICATE_PWD }}
@@ -448,26 +450,25 @@ jobs:
USE_CCACHE: ${{ matrix.use_ccache }}
VCPKG_BINARY_SOURCES: 'clear;files,${{ steps.vcpkg-cache.outputs.path }},readwrite'
VCPKG_DISABLE_METRICS: 1
- VCPKG_FEATURE_FLAGS: dependencygraph
run: .ci/compile.sh --server --test --vcpkg
# 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/.github/workflows/desktop-lint.yml b/.github/workflows/desktop-lint.yml
index 93ba79464..5f31ea59c 100644
--- a/.github/workflows/desktop-lint.yml
+++ b/.github/workflows/desktop-lint.yml
@@ -1,8 +1,5 @@
name: Code Style (C++)
-permissions:
- contents: read
-
on:
# Push trigger not needed for linting, we do not allow direct pushes to master
pull_request:
diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml
index df4fe233c..5384c9e64 100644
--- a/.github/workflows/docker-release.yml
+++ b/.github/workflows/docker-release.yml
@@ -1,8 +1,8 @@
-name: Build Docker
+name: Build Docker Image
permissions:
- contents: read # needed to checkout repo
- packages: write # needed for interacting with GHCR
+ contents: read
+ packages: write
on:
push:
@@ -13,10 +13,7 @@ on:
- master
paths:
- '.github/workflows/docker-release.yml'
- - '.dockerignore'
- 'Dockerfile'
- - 'docker-compose.yml'
- - 'docker-compose.yml.windows'
release:
types:
- released # publishing of stable releases
@@ -26,38 +23,36 @@ concurrency:
group: "${{ github.workflow }} @ ${{ github.ref_name }}"
cancel-in-progress: ${{ github.event_name != 'release' }}
-env:
- GHCR_IMAGE: ghcr.io/cockatrice/servatrice
- OCI_DESCRIPTION: Server for Cockatrice, a cross-platform virtual tabletop for multiplayer card games
- OCI_TITLE: Servatrice
- OCI_URL: https://cockatrice.github.io/
-
jobs:
- # Create one platform-specific image and publish its OCI image manifest per matrix job
- build:
- name: "Servatrice (${{ matrix.label }})"
- if: github.repository_owner == 'Cockatrice'
- runs-on: ${{ matrix.runner }}
-
- strategy:
- fail-fast: false
- matrix:
- include:
- - label: x86
- platform: linux/amd64
- runner: ubuntu-latest # https://github.com/actions/runner-images
-
- - label: arm
- platform: linux/arm64
- runner: ubuntu-24.04-arm # https://github.com/actions/runner-images/blob/main/images/ubuntu/Ubuntu2404-Arm64-Readme.md, replace with "ubuntu-latest-arm" once available
-
- env:
- CACHE_SCOPE: servatrice-${{ matrix.label }}
-
+ docker:
+ name: amd64 & arm64
+ if: ${{ github.repository_owner == 'Cockatrice' }}
+ runs-on: ubuntu-latest
+
steps:
- name: "Checkout"
uses: actions/checkout@v7
+ - name: "Docker metadata"
+ id: metadata
+ uses: docker/metadata-action@v6
+ env:
+ DOCKER_METADATA_ANNOTATIONS_LEVELS: index # needed for GHCR
+ with:
+ annotations: |
+ org.opencontainers.image.title=Servatrice
+ org.opencontainers.image.url=https://cockatrice.github.io/
+ org.opencontainers.image.description=Server for Cockatrice, a cross-platform virtual tabletop for multiplayer card games
+ images: |
+ ghcr.io/cockatrice/servatrice
+ labels: |
+ org.opencontainers.image.title=Servatrice
+ org.opencontainers.image.url=https://cockatrice.github.io/
+ org.opencontainers.image.description=Server for Cockatrice, a cross-platform virtual tabletop for multiplayer card games
+
+ - name: "Set up QEMU"
+ uses: docker/setup-qemu-action@v4
+
- name: "Set up Docker buildx"
uses: docker/setup-buildx-action@v4
@@ -66,117 +61,18 @@ jobs:
id: login
uses: docker/login-action@v4
with:
+ password: ${{ github.token }}
registry: ghcr.io
username: ${{ github.actor }}
- password: ${{ github.token }}
- # Don't push for non-release triggers
- - name: "Build image"
- if: steps.login.outcome != 'success'
+ - name: "Build and push Docker image"
uses: docker/build-push-action@v7
with:
- cache-from: type=gha,scope=${{ env.CACHE_SCOPE }}
- cache-to: type=gha,mode=max,scope=${{ env.CACHE_SCOPE }}
+ annotations: ${{ steps.metadata.outputs.annotations }}
+ cache-from: type=gha,scope=servatrice
+ cache-to: type=gha,mode=max,scope=servatrice
context: .
- platforms: ${{ matrix.platform }}
- push: false
-
- # Add OCI labels and push single-platform image by digest (without tags)
- - name: "Build image and push by digest"
- if: steps.login.outcome == 'success'
- id: build
- uses: docker/build-push-action@v7
- with:
- cache-from: type=gha,scope=${{ env.CACHE_SCOPE }}
- cache-to: type=gha,mode=max,scope=${{ env.CACHE_SCOPE }}
- context: .
- labels: |
- org.opencontainers.image.description=${{ env.OCI_DESCRIPTION }}
- org.opencontainers.image.title=${{ env.OCI_TITLE }}
- org.opencontainers.image.url=${{ env.OCI_URL }}
- outputs: type=image,name=${{ env.GHCR_IMAGE }},name-canonical=true,push=true,push-by-digest=true
- platforms: ${{ matrix.platform }}
- provenance: mode=max # Do not pass secrets as build arguments with this option
- sbom: true
-
- - name: "Export digest"
- if: steps.login.outcome == 'success'
- env:
- DIGEST: ${{ steps.build.outputs.digest }}
- run: |
- mkdir -p "$RUNNER_TEMP/digests"
- touch "$RUNNER_TEMP/digests/${DIGEST#sha256:}"
-
- - name: "Upload digest"
- if: steps.login.outcome == 'success'
- uses: actions/upload-artifact@v7
- with:
- archive: false
- if-no-files-found: error
- name: digest-${{ matrix.label }}
- path: ${{ runner.temp }}/digests/*
- retention-days: 1
-
-
- # Create an OCI image index from the platform-specific image manifests
- index:
- name: "Publish multi-platform Servatrice image"
- if: github.repository_owner == 'Cockatrice' && github.event_name == 'release' && github.event.release.prerelease == false
- needs: build
- runs-on: ubuntu-slim # https://github.com/actions/runner-images/blob/main/images/ubuntu-slim/ubuntu-slim-Readme.md
-
- steps:
- - name: "Download digests"
- uses: actions/download-artifact@v7
- with:
- path: ${{ runner.temp }}/digests
- pattern: digest-*
- merge-multiple: true
-
- - name: "Login to GitHub Container Registry (GHCR)"
- uses: docker/login-action@v4
- with:
- registry: ghcr.io
- username: ${{ github.actor }}
- password: ${{ github.token }}
-
- - name: "Docker metadata"
- id: metadata
- uses: docker/metadata-action@v6
- with:
- images: ${{ env.GHCR_IMAGE }}
- flavor: |
- latest=auto
- tags: |
- type=ref,event=tag # if semver, also: type=semver,pattern={{version}} / {{major}}.{{minor}}
-
- # Add OCI annotations to image index and publish tags
- - name: "Create image index"
- env:
- DOCKER_TAGS: ${{ steps.metadata.outputs.tags }}
- working-directory: ${{ runner.temp }}/digests
- run: |
- TAG_ARGS=()
- while IFS= read -r tag; do
- TAG_ARGS+=(--tag "$tag")
- done <<< "$DOCKER_TAGS"
-
- DIGEST_ARGS=()
- for digest in *; do
- DIGEST_ARGS+=("$GHCR_IMAGE@sha256:$digest")
- done
-
- docker buildx imagetools create \
- --prefer-index=true \
- --annotation "index:org.opencontainers.image.description=$OCI_DESCRIPTION" \
- --annotation "index:org.opencontainers.image.title=$OCI_TITLE" \
- --annotation "index:org.opencontainers.image.url=$OCI_URL" \
- "${TAG_ARGS[@]}" \
- "${DIGEST_ARGS[@]}"
-
- - name: "Inspect images"
- env:
- GITHUB_TAG: ${{ github.ref_name }}
- run: |
- docker buildx imagetools inspect "$GHCR_IMAGE:latest"
- docker buildx imagetools inspect "$GHCR_IMAGE:$GITHUB_TAG"
+ labels: ${{ steps.metadata.outputs.labels }}
+ platforms: linux/amd64,linux/arm64
+ push: ${{ steps.login.outcome == 'success' }}
+ tags: ${{ steps.metadata.outputs.tags }}
diff --git a/.github/workflows/documentation-build.yml b/.github/workflows/documentation-build.yml
index 419cbfbfb..4b9ca79ab 100644
--- a/.github/workflows/documentation-build.yml
+++ b/.github/workflows/documentation-build.yml
@@ -1,8 +1,5 @@
name: Generate Docs
-permissions:
- contents: read # write permission to the destination repo come from 'deploy_key'
-
on:
pull_request:
paths:
@@ -14,11 +11,6 @@ on:
- published # publishing of stable releases and pre-releases
workflow_dispatch:
-# Cancel earlier, unfinished runs of this workflow on the same branch (unless on release)
-concurrency:
- group: "${{ github.workflow }} @ ${{ github.ref_name }}"
- cancel-in-progress: ${{ github.event_name != 'release' }}
-
env:
COCKATRICE_REF: ${{ github.ref_name }} # tag name if the commit is tagged, otherwise branch name
diff --git a/.github/workflows/translations-pull.yml b/.github/workflows/translations-pull.yml
index 71b0b4c22..a3db5f86d 100644
--- a/.github/workflows/translations-pull.yml
+++ b/.github/workflows/translations-pull.yml
@@ -1,9 +1,5 @@
name: Update Translations
-permissions:
- contents: read
- pull-requests: write
-
on:
pull_request:
paths:
diff --git a/.github/workflows/translations-push.yml b/.github/workflows/translations-push.yml
index 37fe5d317..e90dc0151 100644
--- a/.github/workflows/translations-push.yml
+++ b/.github/workflows/translations-push.yml
@@ -1,9 +1,5 @@
name: Update Translation Source
-permissions:
- contents: read
- pull-requests: write
-
on:
pull_request:
paths:
diff --git a/Dockerfile b/Dockerfile
index 382309d47..7c5c773c9 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -3,45 +3,35 @@ FROM ubuntu:26.04 AS build
ARG DEBIAN_FRONTEND=noninteractive
-RUN apt-get update \
- && apt-get install -y --no-install-recommends \
- build-essential \
- cmake \
- ninja-build \
- file \
- g++ \
- git \
- libmariadb-dev-compat \
- libprotobuf-dev \
- libqt6sql6-mysql \
- qt6-websockets-dev \
- protobuf-compiler \
- qt6-tools-dev \
- qt6-tools-dev-tools
+RUN apt-get update && apt-get install -y --no-install-recommends \
+ build-essential \
+ cmake \
+ file \
+ g++ \
+ git \
+ libmariadb-dev-compat \
+ libprotobuf-dev \
+ libqt6sql6-mysql \
+ qt6-websockets-dev \
+ protobuf-compiler \
+ qt6-tools-dev \
+ qt6-tools-dev-tools
WORKDIR /src
COPY . .
-RUN cmake \
- -S . \
- -B build \
- -G Ninja \
- -DWITH_CLIENT=0 \
- -DWITH_ORACLE=0 \
- -DWITH_SERVER=1 \
- && cmake --build build \
- && cmake --install build
+RUN mkdir build && cd build && \
+ cmake .. -DWITH_SERVER=1 -DWITH_CLIENT=0 -DWITH_ORACLE=0 && \
+ make -j$(nproc) && \
+ make install
# -------- Runtime Stage (clean) --------
FROM ubuntu:26.04
-ARG DEBIAN_FRONTEND=noninteractive
-
-RUN apt-get update \
- && apt-get install -y --no-install-recommends \
- libprotobuf32t64 \
- libqt6sql6-mysql \
- libqt6websockets6 \
+RUN apt-get update && apt-get install -y --no-install-recommends \
+ libprotobuf32t64 \
+ libqt6sql6-mysql \
+ libqt6websockets6 \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
diff --git a/cmake/FindQtRuntime.cmake b/cmake/FindQtRuntime.cmake
index 1ee1f215f..78c9aa1b0 100644
--- a/cmake/FindQtRuntime.cmake
+++ b/cmake/FindQtRuntime.cmake
@@ -18,13 +18,10 @@ if(WITH_CLIENT)
Multimedia
Network
PrintSupport
- ShaderTools
Svg
WebSockets
Widgets
Xml
- Quick
- QuickWidgets
)
endif()
if(WITH_ORACLE)
diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt
index 85a95a9bd..a15fda582 100644
--- a/cockatrice/CMakeLists.txt
+++ b/cockatrice/CMakeLists.txt
@@ -15,9 +15,6 @@ set(cockatrice_SOURCES
src/client/network/update/client/client_update_checker.cpp
src/client/network/update/client/release_channel.cpp
src/client/network/update/card_spoiler/spoiler_background_updater.cpp
- src/client/latency_graph_widget.cpp
- src/client/latency_status_widget.cpp
- src/client/lag_monitor.cpp
src/client/sound_engine.cpp
src/client/settings/cache_settings.cpp
src/client/settings/card_counter_settings.cpp
@@ -39,16 +36,13 @@ set(cockatrice_SOURCES
src/interface/widgets/dialogs/dlg_forgot_password_challenge.cpp
src/interface/widgets/dialogs/dlg_forgot_password_request.cpp
src/interface/widgets/dialogs/dlg_forgot_password_reset.cpp
- src/interface/widgets/dialogs/dlg_invite_to_game.cpp
src/interface/widgets/dialogs/dlg_load_deck.cpp
src/interface/widgets/dialogs/dlg_load_deck_from_clipboard.cpp
src/interface/widgets/dialogs/dlg_load_deck_from_website.cpp
src/interface/widgets/dialogs/dlg_load_remote_deck.cpp
src/interface/widgets/dialogs/dlg_local_game_options.cpp
src/interface/widgets/dialogs/dlg_manage_sets.cpp
- src/interface/widgets/dialogs/dlg_my_reports.cpp
src/interface/widgets/dialogs/dlg_register.cpp
- src/interface/widgets/dialogs/dlg_report_user.cpp
src/interface/widgets/dialogs/dlg_select_set_for_cards.cpp
src/interface/widgets/dialogs/dlg_settings.cpp
src/interface/widgets/dialogs/dlg_startup_card_check.cpp
@@ -149,11 +143,7 @@ set(cockatrice_SOURCES
src/interface/intents/intent_wait_for_database_load.h
src/interface/layouts/flow_layout.cpp
src/interface/layouts/overlap_layout.cpp
- src/interface/widgets/utility/card_completer_delegate.cpp
- src/interface/widgets/utility/card_completer_styler.cpp
- src/interface/widgets/utility/completer_utils.cpp
src/interface/widgets/utility/line_edit_completer.cpp
- src/interface/widgets/utility/reversed_completer_model.cpp
src/interface/pixel_map_generator.cpp
src/interface/theme_config.cpp
src/interface/theme_manager.cpp
@@ -165,7 +155,6 @@ set(cockatrice_SOURCES
src/interface/widgets/cards/additional_info/color_identity_widget.cpp
src/interface/widgets/cards/additional_info/mana_cost_widget.cpp
src/interface/widgets/cards/additional_info/mana_symbol_widget.cpp
- src/interface/widgets/cards/art_crop_attribution.cpp
src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp
src/interface/widgets/cards/card_group_display_widgets/flat_card_group_display_widget.cpp
src/interface/widgets/cards/card_group_display_widgets/overlapped_card_group_display_widget.cpp
@@ -230,7 +219,6 @@ set(cockatrice_SOURCES
src/interface/widgets/general/display/charts/bars/segmented_bar_widget.cpp
src/interface/widgets/general/display/charts/pies/color_pie.cpp
src/interface/widgets/general/home_styled_button.cpp
- src/interface/widgets/general/home_tab_button_color.h
src/interface/widgets/general/home_widget.cpp
src/interface/widgets/general/layout_containers/flow_widget.cpp
src/interface/widgets/general/layout_containers/overlap_control_widget.cpp
@@ -246,10 +234,6 @@ set(cockatrice_SOURCES
src/interface/widgets/printing_selector/printing_selector_card_selection_widget.cpp
src/interface/widgets/printing_selector/printing_selector_card_sorting_widget.cpp
src/interface/widgets/printing_selector/set_name_and_collectors_number_display_widget.cpp
- src/interface/widgets/playmat/playmat_collection_dialog.cpp
- src/interface/widgets/playmat/playmat_collection_dialog.h
- src/interface/widgets/playmat/playmat_preview_widget.cpp
- src/interface/widgets/playmat/playmat_settings_dialog.cpp
src/interface/widgets/quick_settings/settings_button_widget.cpp
src/interface/widgets/quick_settings/settings_popup_widget.cpp
src/interface/widgets/replay/replay_manager.cpp
@@ -258,7 +242,6 @@ set(cockatrice_SOURCES
src/interface/widgets/replay/replay_widget.cpp
src/interface/widgets/server/chat_view/chat_view.cpp
src/interface/widgets/server/game_filter_configs.cpp
- src/interface/widgets/server/game_link.cpp
src/interface/widgets/server/game_selector.cpp
src/interface/widgets/server/game_selector_quick_filter_toolbar.cpp
src/interface/widgets/server/games_model.cpp
@@ -273,7 +256,6 @@ set(cockatrice_SOURCES
src/interface/widgets/server/user/user_info_connection.cpp
src/interface/widgets/server/user/user_list_manager.cpp
src/interface/widgets/server/user/user_list_painter.cpp
- src/interface/widgets/server/user/user_list_panel_widget.cpp
src/interface/widgets/server/user/user_list_widget.cpp
src/interface/widgets/settings_page/abstract_settings_page.cpp
src/interface/widgets/settings_page/appearance_settings_page.cpp
@@ -288,8 +270,6 @@ set(cockatrice_SOURCES
src/interface/widgets/settings_page/user_interface_settings_page.cpp
src/interface/widgets/utility/custom_line_edit.cpp
src/interface/widgets/utility/get_text_with_max.cpp
- src/interface/widgets/utility/report_utils.cpp
- src/interface/widgets/utility/report_utils.h
src/interface/widgets/utility/sequence_edit.cpp
src/interface/widgets/utility/visibility_change_listener.cpp
src/interface/widgets/utility/visibility_change_listener.h
@@ -315,10 +295,8 @@ set(cockatrice_SOURCES
src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_item_widget.cpp
src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp
src/interface/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.cpp
- src/interface/widgets/visual_deck_storage/visual_deck_storage_model.cpp
src/interface/widgets/visual_deck_storage/visual_deck_storage_quick_settings_widget.cpp
src/interface/widgets/visual_deck_storage/visual_deck_storage_search_widget.cpp
- src/interface/widgets/visual_deck_storage/visual_deck_storage_sort_filter_proxy_model.cpp
src/interface/widgets/visual_deck_storage/visual_deck_storage_sort_widget.cpp
src/interface/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.cpp
src/interface/widgets/visual_deck_storage/visual_deck_storage_widget.cpp
@@ -383,8 +361,6 @@ set(cockatrice_SOURCES
src/interface/widgets/tabs/tab_home.cpp
src/interface/widgets/tabs/tab_logs.cpp
src/interface/widgets/tabs/tab_message.cpp
- src/interface/widgets/tabs/tab_moderation.cpp
- src/interface/widgets/tabs/tab_report.cpp
src/interface/widgets/tabs/tab_replays.cpp
src/interface/widgets/tabs/tab_room.cpp
src/interface/widgets/tabs/tab_server.cpp
@@ -398,27 +374,6 @@ set(cockatrice_SOURCES
src/interface/widgets/tabs/api/commander_spellbook/commander_bracket_service.cpp
src/interface/widgets/tabs/api/commander_spellbook/commander_bracket_widget.cpp
src/interface/widgets/tabs/api/commander_spellbook/handle_commander_brackets.cpp
- src/interface/widgets/onboarding/banner_shader_config.h
- src/interface/widgets/onboarding/first_run_wizard.cpp
- src/interface/widgets/onboarding/first_run_wizard.h
- src/interface/widgets/onboarding/first_run_wizard_page.cpp
- src/interface/widgets/onboarding/first_run_wizard_page.h
- src/interface/widgets/onboarding/pages/account_setup_page.cpp
- src/interface/widgets/onboarding/pages/account_setup_page.h
- src/interface/widgets/onboarding/pages/card_database_setup_page.cpp
- src/interface/widgets/onboarding/pages/card_database_setup_page.h
- src/interface/widgets/onboarding/pages/finish_page.cpp
- src/interface/widgets/onboarding/pages/finish_page.h
- src/interface/widgets/onboarding/pages/preferences_setup_page.cpp
- src/interface/widgets/onboarding/pages/preferences_setup_page.h
- src/interface/widgets/onboarding/pages/theme_setup_page.cpp
- src/interface/widgets/onboarding/pages/theme_setup_page.h
- src/interface/widgets/onboarding/pages/welcome_page.cpp
- src/interface/widgets/onboarding/pages/welcome_page.h
- src/interface/widgets/onboarding/shader_banner_widget.cpp
- src/interface/widgets/onboarding/shader_banner_widget.h
- src/interface/widgets/onboarding/step_indicator_widget.cpp
- src/interface/widgets/onboarding/step_indicator_widget.h
src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.cpp
src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.h
src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.cpp
@@ -437,8 +392,6 @@ set(cockatrice_SOURCES
src/interface/intents/intent_join_server_room.h
src/interface/intents/intent_login.cpp
src/interface/intents/intent_login.h
- src/interface/intents/intent_open_server_room_by_name.cpp
- src/interface/intents/intent_open_server_room_by_name.h
src/interface/intents/url_parser.cpp
src/interface/intents/url_parser.h
src/interface/widgets/server/user/user_info_popup.cpp
@@ -515,28 +468,6 @@ qt6_add_executable(
MANUAL_FINALIZATION
)
-qt6_add_shaders(
- cockatrice
- "onboarding_shaders"
- PREFIX
- "/onboarding/shaders"
- BASE
- "src/interface/widgets/onboarding/shaders"
- FILES
- src/interface/widgets/onboarding/shaders/brand_banner.frag
-)
-
-qt6_add_resources(
- cockatrice
- "onboarding_qml"
- PREFIX
- "/onboarding/qml"
- BASE
- "src/interface/widgets/onboarding/qml"
- FILES
- src/interface/widgets/onboarding/qml/BrandBanner.qml
-)
-
target_link_libraries(
cockatrice
PUBLIC libcockatrice_card
diff --git a/cockatrice/cockatrice.qrc b/cockatrice/cockatrice.qrc
index e21bdb0be..9c34929b7 100644
--- a/cockatrice/cockatrice.qrc
+++ b/cockatrice/cockatrice.qrc
@@ -2,7 +2,6 @@
resources/cardback.svg
resources/cockatrice.svg
- resources/cockatrice-logo-white.svg
resources/hand.svg
resources/hr.jpg
diff --git a/cockatrice/resources/cockatrice-logo-white.svg b/cockatrice/resources/cockatrice-logo-white.svg
deleted file mode 100644
index b3b31077f..000000000
--- a/cockatrice/resources/cockatrice-logo-white.svg
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
diff --git a/cockatrice/resources/cockatrice.svg b/cockatrice/resources/cockatrice.svg
index 89ba62dcf..d2e22da31 100644
--- a/cockatrice/resources/cockatrice.svg
+++ b/cockatrice/resources/cockatrice.svg
@@ -2,20 +2,20 @@
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/client/lag_monitor.cpp b/cockatrice/src/client/lag_monitor.cpp
deleted file mode 100644
index 383d64644..000000000
--- a/cockatrice/src/client/lag_monitor.cpp
+++ /dev/null
@@ -1,63 +0,0 @@
-#include "lag_monitor.h"
-
-#include
-#include
-#include
-
-LagMonitor::LagMonitor(QObject *parent) : QObject(parent)
-{
- qApp->installEventFilter(this);
-
- timer = new QTimer(this);
- timer->setInterval(TICK_INTERVAL_MS);
- connect(timer, &QTimer::timeout, this, &LagMonitor::checkTick);
- tickClock.start();
- timer->start();
-}
-
-QList LagMonitor::recentStalls() const
-{
- return stalls;
-}
-
-void LagMonitor::clearStalls()
-{
- stalls.clear();
-}
-
-bool LagMonitor::eventFilter(QObject *obj, QEvent *event)
-{
- if (event->type() == QEvent::ApplicationStateChange) {
- // The transition may span a suspend or an arbitrary unfocused period;
- // discard the gap so it cannot be mistaken for a stall.
- tickClock.restart();
- }
- return QObject::eventFilter(obj, event);
-}
-
-void LagMonitor::checkTick()
-{
- recordGap(tickClock.restart());
-}
-
-void LagMonitor::recordGap(qint64 gapMs)
-{
- if (gapMs <= STALL_THRESHOLD_MS) {
- return;
- }
-
- if (gapMs > MAX_PLAUSIBLE_STALL_MS) {
- qCDebug(LagMonitorLog, "Ignoring implausible %lld ms gap (likely suspend)", static_cast(gapMs));
- return;
- }
-
- const StallRecord record{.timestampMsSinceEpoch = QDateTime::currentMSecsSinceEpoch(), .durationMs = gapMs};
-
- stalls.append(record);
- while (stalls.size() > MAX_RECORDED_STALLS) {
- stalls.removeFirst();
- }
-
- qCWarning(LagMonitorLog, "Event loop stalled for %lld ms (threshold: %d ms)", static_cast(gapMs),
- STALL_THRESHOLD_MS);
-}
diff --git a/cockatrice/src/client/lag_monitor.h b/cockatrice/src/client/lag_monitor.h
deleted file mode 100644
index 9fdf6b283..000000000
--- a/cockatrice/src/client/lag_monitor.h
+++ /dev/null
@@ -1,87 +0,0 @@
-/**
- * @file lag_monitor.h
- * @ingroup Client
- */
-
-#ifndef LAG_MONITOR_H
-#define LAG_MONITOR_H
-
-#include
-#include
-#include
-#include
-#include
-
-inline Q_LOGGING_CATEGORY(LagMonitorLog, "lag_monitor");
-
-class QEvent;
-class QTimer;
-
-/**
- * @brief Detects main-thread event loop stalls ("UI freezes") from the inside.
- *
- * A timer is expected to fire every TICK_INTERVAL_MS of wall time. When the
- * observed gap greatly exceeds that interval, some other task blocked the
- * event loop for roughly the overshooting duration. This is what separates
- * "my client froze" from "the network is lagging" in user reports.
- *
- * Gaps that span an application state change (suspend, minimize, focus
- * loss) are discarded, and implausibly huge gaps are dropped, so operating
- * system power events do not fabricate stalls. This handling is load-bearing
- * on Windows, where the monotonic clock used by Qt counts sleep time.
- *
- * Healthy operation costs one timer wakeup per tick and two integer
- * comparisons. Allocations happen only when a stall is actually recorded.
- */
-class LagMonitor : public QObject
-{
- Q_OBJECT
-
-public:
- struct StallRecord
- {
- qint64 timestampMsSinceEpoch = 0; ///< when the stalled period ended
- qint64 durationMs = 0; ///< approximate length of the freeze; measured tick to tick, so it can exceed the true
- ///< stall by up to TICK_INTERVAL_MS
- };
-
- static constexpr int TICK_INTERVAL_MS = 500;
- static constexpr int STALL_THRESHOLD_MS = 2000;
- static constexpr int MAX_RECORDED_STALLS = 32;
-
- /// Gaps beyond this are treated as suspend artifacts rather than stalls.
- static constexpr qint64 MAX_PLAUSIBLE_STALL_MS = 600000;
-
- explicit LagMonitor(QObject *parent = nullptr);
-
- /**
- * @brief Stalls recorded during this session, oldest first.
- *
- * Intended consumers are log output and the diagnostics export. The list
- * holds at most MAX_RECORDED_STALLS entries.
- */
- QList recentStalls() const;
-
- void clearStalls();
-
- /**
- * @brief Feeds a measured tick-to-tick gap through the detection logic.
- *
- * Split out of checkTick so threshold, plausibility, and trim behavior
- * stay unit-testable without real timing.
- */
- void recordGap(qint64 gapMs);
-
-protected:
- bool eventFilter(QObject *obj, QEvent *event) override;
-
-private slots:
- void checkTick();
-
-private:
- QTimer *timer;
- QElapsedTimer tickClock; ///< monotonic clock, so wall clock steps do not fabricate stalls
- QList stalls;
-};
-
-#endif
diff --git a/cockatrice/src/client/latency_graph_widget.cpp b/cockatrice/src/client/latency_graph_widget.cpp
deleted file mode 100644
index 46d6ccdd3..000000000
--- a/cockatrice/src/client/latency_graph_widget.cpp
+++ /dev/null
@@ -1,51 +0,0 @@
-/**
- * @file latency_graph_widget.cpp
- * @ingroup Client
- */
-
-#include "latency_graph_widget.h"
-
-#include
-
-LatencyGraphWidget::LatencyGraphWidget(QWidget *parent) : QWidget(parent)
-{
-}
-
-void LatencyGraphWidget::setSamples(const QList &samplesMs)
-{
- samples = samplesMs;
- update();
-}
-
-void LatencyGraphWidget::paintEvent(QPaintEvent * /* event */)
-{
- if (samples.isEmpty()) {
- return;
- }
-
- QPainter painter(this);
-
- // Heights are relative to the window's own worst sample (floored at
- // MinScaleMs) so the shape of the variance stays readable even when every
- // value is small.
- qint64 heightScaleMs = MinScaleMs;
- for (int sample : samples) {
- heightScaleMs = qMax(heightScaleMs, static_cast(sample));
- }
-
- const qreal widthPerBar = static_cast(width()) / samples.size();
- for (int i = 0; i < samples.size(); ++i) {
- const qreal heightRatio = qBound(0.0, static_cast(samples.at(i)) / heightScaleMs, 1.0);
- const qreal barHeight = heightRatio * height();
-
- // Colors follow an absolute quality ramp: a steady good ping stays
- // green no matter how uniform the window is.
- const qreal colorRatio = qBound(0.0, static_cast(samples.at(i)) / ColorScaleMs, 1.0);
- QColor color;
- color.setHsv(qRound(120.0 * (1.0 - colorRatio)), 255, 255);
-
- const QRectF bar(static_cast(i) * widthPerBar + 1.0, static_cast(height()) - barHeight,
- qMax(1.0, widthPerBar - 2.0), barHeight);
- painter.fillRect(bar, color);
- }
-}
diff --git a/cockatrice/src/client/latency_graph_widget.h b/cockatrice/src/client/latency_graph_widget.h
deleted file mode 100644
index 4f6f38f8b..000000000
--- a/cockatrice/src/client/latency_graph_widget.h
+++ /dev/null
@@ -1,43 +0,0 @@
-/**
- * @file latency_graph_widget.h
- * @ingroup Client
- */
-
-#ifndef LATENCY_GRAPH_WIDGET_H
-#define LATENCY_GRAPH_WIDGET_H
-
-#include
-#include
-
-/**
- * @brief Bar graph of recent network round-trip samples.
- *
- * Draws one bar per sample, oldest on the left. Bar height is relative to the
- * window's own scale so the shape of the variance stays readable, while bar
- * color maps each sample onto an absolute quality ramp (green at rest through
- * red at ColorScaleMs) so a steady good ping never looks alarming. Size
- * agnostic: the status bar embeds a small instance while the latency detail
- * popup shows a large one.
- */
-class LatencyGraphWidget : public QWidget
-{
- Q_OBJECT
-public:
- explicit LatencyGraphWidget(QWidget *parent = nullptr);
-
- /// Sample in milliseconds that maps to a fully red bar.
- static constexpr qint64 ColorScaleMs = 500;
-
- void setSamples(const QList &samplesMs);
-
-protected:
- void paintEvent(QPaintEvent *event) override;
-
-private:
- /// Floor of the vertical scale in milliseconds. Keeps small windows readable.
- static constexpr qint64 MinScaleMs = 100;
-
- QList samples;
-};
-
-#endif
diff --git a/cockatrice/src/client/latency_status_widget.cpp b/cockatrice/src/client/latency_status_widget.cpp
deleted file mode 100644
index 779c726eb..000000000
--- a/cockatrice/src/client/latency_status_widget.cpp
+++ /dev/null
@@ -1,111 +0,0 @@
-/**
- * @file latency_status_widget.cpp
- * @ingroup Client
- */
-
-#include "latency_status_widget.h"
-
-#include "latency_graph_widget.h"
-
-#include
-#include
-#include
-#include
-
-LatencyStatusWidget::LatencyStatusWidget(QWidget *parent) : QWidget(parent)
-{
- pingLabel = new QLabel(this);
- pingLabel->setAccessibleName(tr("Ping"));
-
- latencyGraph = new LatencyGraphWidget(this);
- latencyGraph->setFixedSize(90, 14);
-
- auto *layout = new QHBoxLayout(this);
- layout->setContentsMargins(0, 0, 0, 0);
- layout->setSpacing(4);
- layout->addWidget(latencyGraph);
- layout->addWidget(pingLabel);
-
- // Clicking anywhere in the area opens the detail view.
- for (QObject *child : QList{pingLabel, latencyGraph}) {
- child->installEventFilter(this);
- }
- setCursor(Qt::PointingHandCursor);
-
- hide();
-}
-
-void LatencyStatusWidget::updateData(const LatencyTracker::Stats &stats, const QList &samplesMs)
-{
- latestSamples = samplesMs;
- latencyGraph->setSamples(samplesMs);
- if (popup && popup->isVisible() && detailGraph) {
- detailGraph->setSamples(samplesMs);
- }
-
- if (stats.sampleCount == 0) {
- hide();
- return;
- }
-
- const QString statsStr = statsText(stats);
-
- pingLabel->setText(tr("Ping: %1 ms").arg(stats.lastMs));
- pingLabel->setToolTip(statsStr);
- pingLabel->setAccessibleDescription(statsStr);
- if (popup && popup->isVisible() && detailLabel) {
- detailLabel->setText(statsStr);
- }
- show();
-}
-
-bool LatencyStatusWidget::eventFilter(QObject *watched, QEvent *event)
-{
- if ((watched == pingLabel || watched == latencyGraph) && event->type() == QEvent::MouseButtonPress) {
- togglePopup();
- return true;
- }
- return QWidget::eventFilter(watched, event);
-}
-
-void LatencyStatusWidget::togglePopup()
-{
- if (!popup) {
- popup = new QWidget(this, Qt::Popup | Qt::FramelessWindowHint);
- auto *layout = new QVBoxLayout(popup);
- layout->setContentsMargins(8, 8, 8, 8);
-
- detailLabel = new QLabel(popup);
- detailLabel->setAccessibleName(tr("Connection latency details"));
- detailLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
-
- detailGraph = new LatencyGraphWidget(popup);
- detailGraph->setFixedSize(280, 80);
-
- layout->addWidget(detailLabel, 0, Qt::AlignLeft);
- layout->addWidget(detailGraph, 0, Qt::AlignHCenter);
- }
-
- if (popup->isVisible()) {
- popup->hide();
- return;
- }
-
- // Qt::Popup closes itself on any outside click, so just position and show.
- if (latestSamples.isEmpty()) {
- return;
- }
- detailGraph->setSamples(latestSamples);
- detailLabel->setText(pingLabel->toolTip());
- popup->adjustSize();
- const QPoint anchor = mapToGlobal(QPoint(width() / 2, 0));
- popup->move(anchor.x() - popup->width() / 2, anchor.y() - popup->height() - 6);
- popup->show();
-}
-
-QString LatencyStatusWidget::statsText(const LatencyTracker::Stats &stats) const
-{
- return tr("Connection quality over the last %n sample(s):", "", stats.sampleCount) + "\n" +
- tr("Last: %1 ms").arg(stats.lastMs) + "\n" + tr("Median: %1 ms").arg(stats.medianMs) + "\n" +
- tr("95th percentile: %1 ms").arg(stats.p95Ms) + "\n" + tr("Maximum: %1 ms").arg(stats.maxMs);
-}
diff --git a/cockatrice/src/client/latency_status_widget.h b/cockatrice/src/client/latency_status_widget.h
deleted file mode 100644
index d9e1d130c..000000000
--- a/cockatrice/src/client/latency_status_widget.h
+++ /dev/null
@@ -1,50 +0,0 @@
-/**
- * @file latency_status_widget.h
- * @ingroup Client
- */
-
-#ifndef LATENCY_STATUS_WIDGET_H
-#define LATENCY_STATUS_WIDGET_H
-
-#include
-#include
-#include
-
-class QLabel;
-class LatencyGraphWidget;
-
-/**
- * @brief Status bar presentation of server round-trip health.
- *
- * Combines the textual "Ping" readout with a small LatencyGraphWidget
- * sparkline of the rolling sample window. Clicking anywhere in the area opens
- * a popup with a larger graph and the numeric statistics. It closes on any
- * outside click. Stays hidden while disconnected or before any samples exist.
- * Owns all latency display state so MainWindow only needs to forward one
- * signal here.
- */
-class LatencyStatusWidget : public QWidget
-{
- Q_OBJECT
-public:
- explicit LatencyStatusWidget(QWidget *parent = nullptr);
-
-public slots:
- void updateData(const LatencyTracker::Stats &stats, const QList &samplesMs);
-
-protected:
- bool eventFilter(QObject *watched, QEvent *event) override;
-
-private:
- void togglePopup();
- QString statsText(const LatencyTracker::Stats &stats) const;
-
- QLabel *pingLabel = nullptr;
- LatencyGraphWidget *latencyGraph = nullptr;
- QWidget *popup = nullptr;
- LatencyGraphWidget *detailGraph = nullptr;
- QLabel *detailLabel = nullptr;
- QList latestSamples;
-};
-
-#endif
diff --git a/cockatrice/src/client/network/connection_controller/remote_connection_controller.cpp b/cockatrice/src/client/network/connection_controller/remote_connection_controller.cpp
index 890a621c8..4e425fb66 100644
--- a/cockatrice/src/client/network/connection_controller/remote_connection_controller.cpp
+++ b/cockatrice/src/client/network/connection_controller/remote_connection_controller.cpp
@@ -44,8 +44,6 @@ void ConnectionController::wireClientSignals()
connect(remoteClient, &RemoteClient::statusChanged, this, &ConnectionController::onStatusChanged);
- connect(remoteClient, &AbstractClient::pingStatsUpdated, this, &ConnectionController::pingStatsUpdated);
-
connect(remoteClient, &RemoteClient::userInfoChanged, this, &ConnectionController::onUserInfoReceived,
Qt::BlockingQueuedConnection);
@@ -298,15 +296,6 @@ void ConnectionController::onLoginError(int r,
return;
}
- case Response::RespPasswordChangeRequired: {
- QMessageBox::information(
- dialogParent, tr("Password Change Required"),
- tr("An administrator has reset your password. Please contact your server administrator to obtain "
- "your temporary password, then log in and change it via Account -> Change Password."));
- remoteClient->disconnectFromServer();
- return;
- }
-
case Response::RespServerFull: {
QMessageBox::critical(dialogParent, tr("Server Full"),
tr("The server has reached its maximum user capacity, please check back later."));
diff --git a/cockatrice/src/client/network/connection_controller/remote_connection_controller.h b/cockatrice/src/client/network/connection_controller/remote_connection_controller.h
index bae99a3e0..7486bc81a 100644
--- a/cockatrice/src/client/network/connection_controller/remote_connection_controller.h
+++ b/cockatrice/src/client/network/connection_controller/remote_connection_controller.h
@@ -54,10 +54,6 @@ signals:
// action enable/disable logic
void statusChanged(ClientStatus status);
- // Forwarded from AbstractClient::pingStatsUpdated. See that signal for the
- // meaning of the parameters.
- void pingStatsUpdated(const LatencyTracker::Stats &stats, const QList &samplesMs);
-
private slots:
// Slots wired directly to RemoteClient signals
void onStatusChanged(ClientStatus status);
diff --git a/cockatrice/src/client/network/interfaces/deck_stats_interface.cpp b/cockatrice/src/client/network/interfaces/deck_stats_interface.cpp
index 42292b2aa..8689a19e9 100644
--- a/cockatrice/src/client/network/interfaces/deck_stats_interface.cpp
+++ b/cockatrice/src/client/network/interfaces/deck_stats_interface.cpp
@@ -72,7 +72,7 @@ void DeckStatsInterface::copyDeckWithoutTokens(const DeckList &source, DeckList
{
auto copyIfNotAToken = [&destination](const auto node, const auto card) {
CardInfoPtr dbCard = CardDatabaseManager::query()->getCardInfo(card->getName());
- if (dbCard && !dbCard->getIsToken() && node->getName() != DECK_ZONE_MAYBEBOARD) {
+ if (dbCard && !dbCard->getIsToken()) {
DecklistCardNode *addedCard = destination.addCard(card->getName(), node->getName(), -1);
addedCard->setNumber(card->getNumber());
}
diff --git a/cockatrice/src/client/network/interfaces/tapped_out_interface.cpp b/cockatrice/src/client/network/interfaces/tapped_out_interface.cpp
index 627b7fe34..5dc77fa2c 100644
--- a/cockatrice/src/client/network/interfaces/tapped_out_interface.cpp
+++ b/cockatrice/src/client/network/interfaces/tapped_out_interface.cpp
@@ -99,7 +99,7 @@ void TappedOutInterface::copyDeckSplitMainAndSide(const DeckList &source, DeckLi
{
auto copyMainOrSide = [&mainboard, &sideboard](const auto node, const auto card) {
CardInfoPtr dbCard = CardDatabaseManager::query()->getCardInfo(card->getName());
- if (!dbCard || dbCard->getIsToken() || node->getName() == DECK_ZONE_MAYBEBOARD) {
+ if (!dbCard || dbCard->getIsToken()) {
return;
}
diff --git a/cockatrice/src/client/settings/shortcuts_settings.h b/cockatrice/src/client/settings/shortcuts_settings.h
index f4ebc204e..95155b8d1 100644
--- a/cockatrice/src/client/settings/shortcuts_settings.h
+++ b/cockatrice/src/client/settings/shortcuts_settings.h
@@ -786,10 +786,6 @@ private:
ShortcutGroup::Tabs)},
{"Tabs/aTabLogs",
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Logs"), parseSequenceString(""), ShortcutGroup::Tabs)},
- {"Tabs/aTabReport",
- ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Report Queue"), parseSequenceString(""), ShortcutGroup::Tabs)},
- {"Tabs/aTabModeration",
- ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Moderation"), parseSequenceString(""), ShortcutGroup::Tabs)},
};
};
diff --git a/cockatrice/src/filters/deck_filter_string.cpp b/cockatrice/src/filters/deck_filter_string.cpp
index 4abb8210c..dd873cfa5 100644
--- a/cockatrice/src/filters/deck_filter_string.cpp
+++ b/cockatrice/src/filters/deck_filter_string.cpp
@@ -52,14 +52,18 @@ static void setupParserRules()
search["Start"] = passthru;
search["QueryPartList"] = [](const peg::SemanticValues &sv) -> DeckFilter {
- return [=](const DeckSearchData &data) {
- auto matchesFilter = [&data](const std::any &query) { return std::any_cast(query)(data); };
+ return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &info) {
+ auto matchesFilter = [&deck, &info](const std::any &query) {
+ return std::any_cast(query)(deck, info);
+ };
return std::all_of(sv.begin(), sv.end(), matchesFilter);
};
};
search["ComplexQueryPart"] = [](const peg::SemanticValues &sv) -> DeckFilter {
- return [=](const DeckSearchData &data) {
- auto matchesFilter = [&data](const std::any &query) { return std::any_cast(query)(data); };
+ return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &info) {
+ auto matchesFilter = [&deck, &info](const std::any &query) {
+ return std::any_cast(query)(deck, info);
+ };
return std::any_of(sv.begin(), sv.end(), matchesFilter);
};
};
@@ -67,7 +71,9 @@ static void setupParserRules()
search["QueryPart"] = passthru;
search["NotQuery"] = [](const peg::SemanticValues &sv) -> DeckFilter {
const auto dependent = std::any_cast(sv[0]);
- return [=](const DeckSearchData &data) -> bool { return !dependent(data); };
+ return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &info) -> bool {
+ return !dependent(deck, info);
+ };
};
search["String"] = [](const peg::SemanticValues &sv) -> QString {
@@ -119,9 +125,9 @@ static void setupParserRules()
auto cardFilter = FilterString(std::any_cast(sv[0]));
auto numberMatcher = sv.size() > 1 ? std::any_cast(sv[1]) : [](int count) { return count > 0; };
- return [=](const DeckSearchData &data) -> bool {
+ return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &) -> bool {
int count = 0;
- auto cardNodes = data.deck->deckList.getCardNodes();
+ auto cardNodes = deck->deckLoader->getDeck().deckList.getCardNodes();
for (auto node : cardNodes) {
auto cardInfoPtr = CardDatabaseManager::query()->getCardInfo(node->getName());
if (!cardInfoPtr.isNull() && cardFilter.check(cardInfoPtr)) {
@@ -140,49 +146,53 @@ static void setupParserRules()
search["DeckNameQuery"] = [](const peg::SemanticValues &sv) -> DeckFilter {
auto name = std::any_cast(sv[0]);
- return [=](const DeckSearchData &data) {
- return data.deck->deckList.getName().contains(name, Qt::CaseInsensitive);
+ return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &) {
+ return deck->deckLoader->getDeck().deckList.getName().contains(name, Qt::CaseInsensitive);
};
};
search["FileNameQuery"] = [](const peg::SemanticValues &sv) -> DeckFilter {
auto name = std::any_cast(sv[0]);
- return [=](const DeckSearchData &data) {
- auto filename = QFileInfo(data.filePath).fileName();
+ return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &) {
+ auto filename = QFileInfo(deck->filePath).fileName();
return filename.contains(name, Qt::CaseInsensitive);
};
};
search["PathQuery"] = [](const peg::SemanticValues &sv) -> DeckFilter {
auto name = std::any_cast(sv[0]);
- return [=](const DeckSearchData &data) { return data.relativeFilePath.contains(name, Qt::CaseInsensitive); };
+ return [=](const DeckPreviewWidget *, const ExtraDeckSearchInfo &info) {
+ return info.relativeFilePath.contains(name, Qt::CaseInsensitive);
+ };
};
search["FormatQuery"] = [](const peg::SemanticValues &sv) -> DeckFilter {
auto format = std::any_cast(sv[0]);
- return [=](const DeckSearchData &data) {
- auto gameFormat = data.deck->deckList.getGameFormat();
+ return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &) {
+ auto gameFormat = deck->deckLoader->getDeck().deckList.getGameFormat();
return QString::compare(format, gameFormat, Qt::CaseInsensitive) == 0;
};
};
search["CommentQuery"] = [](const peg::SemanticValues &sv) -> DeckFilter {
auto value = std::any_cast(sv[0]);
- return [=](const DeckSearchData &data) {
- auto comments = data.deck->deckList.getComments();
+ return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &) {
+ auto comments = deck->deckLoader->getDeck().deckList.getComments();
return comments.contains(value, Qt::CaseInsensitive);
};
};
search["GenericQuery"] = [](const peg::SemanticValues &sv) -> DeckFilter {
auto name = std::any_cast(sv[0]);
- return [=](const DeckSearchData &data) { return data.displayName.contains(name, Qt::CaseInsensitive); };
+ return [=](const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &) {
+ return deck->getDisplayName().contains(name, Qt::CaseInsensitive);
+ };
};
}
DeckFilterString::DeckFilterString()
{
- filter = [](const DeckSearchData &) { return false; };
+ filter = [](const DeckPreviewWidget *, const ExtraDeckSearchInfo &) { return false; };
_error = "Not initialized";
}
@@ -195,7 +205,7 @@ DeckFilterString::DeckFilterString(const QString &expr)
_error = QString();
if (ba.isEmpty()) {
- filter = [](const DeckSearchData &) { return true; };
+ filter = [](const DeckPreviewWidget *, const ExtraDeckSearchInfo &) { return true; };
return;
}
@@ -205,6 +215,6 @@ DeckFilterString::DeckFilterString(const QString &expr)
if (!search.parse(ba.data(), filter)) {
qCInfo(DeckFilterStringLog).nospace() << "DeckFilterString error for " << expr << "; " << qPrintable(_error);
- filter = [](const DeckSearchData &) { return false; };
+ filter = [](const DeckPreviewWidget *, const ExtraDeckSearchInfo &) { return false; };
}
-}
+}
\ No newline at end of file
diff --git a/cockatrice/src/filters/deck_filter_string.h b/cockatrice/src/filters/deck_filter_string.h
index 90a6a17eb..916b629ee 100644
--- a/cockatrice/src/filters/deck_filter_string.h
+++ b/cockatrice/src/filters/deck_filter_string.h
@@ -7,7 +7,7 @@
#ifndef DECK_FILTER_STRING_H
#define DECK_FILTER_STRING_H
-#include "../interface/deck_loader/loaded_deck.h"
+#include "../interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h"
#include
#include
@@ -16,29 +16,26 @@
inline Q_LOGGING_CATEGORY(DeckFilterStringLog, "deck_filter_string");
/**
- * The data a deck search expression is evaluated against.
- *
- * This is a data view rather than a widget pointer, so the same filter
- * expression can be evaluated against a model or a live widget.
+ * Extra info relevant to filtering that isn't present in the DeckPreviewWidget
*/
-struct DeckSearchData
+struct ExtraDeckSearchInfo
{
- const LoadedDeck *deck = nullptr; ///< The loaded deck. Must not be null.
- QString filePath; ///< Absolute path of the deck file.
- QString displayName; ///< Deck name, or the file name if the deck has no name.
- QString relativeFilePath; ///< File path relative to the deck folder.
+ /**
+ * The relative filepath starting from the deck folder
+ */
+ QString relativeFilePath;
};
-typedef std::function DeckFilter;
+typedef std::function DeckFilter;
class DeckFilterString
{
public:
DeckFilterString();
explicit DeckFilterString(const QString &expr);
- bool check(const DeckSearchData &data) const
+ bool check(const DeckPreviewWidget *deck, const ExtraDeckSearchInfo &info) const
{
- return filter(data);
+ return filter(deck, info);
}
[[nodiscard]] bool valid() const
diff --git a/cockatrice/src/game/game_event_handler.cpp b/cockatrice/src/game/game_event_handler.cpp
index bc68d4d7c..4a96eebdb 100644
--- a/cockatrice/src/game/game_event_handler.cpp
+++ b/cockatrice/src/game/game_event_handler.cpp
@@ -229,11 +229,7 @@ void GameEventHandler::handleArrowDeletion(int creatorId, int arrowId)
void GameEventHandler::handleArrowDeletionFinished(const Response &response, int creatorId, int arrowId)
{
- // The server confirms the arrow no longer exists whether it deleted it itself
- // (RespOk, followed by an Event_DeleteArrow broadcast) or never had it
- // (RespNameNotFound). In both cases the local copy has to go. deleteArrow is
- // a no-op if the arrow was already removed by the event broadcast.
- if (response.response_code() == Response::RespOk || response.response_code() == Response::RespNameNotFound) {
+ if (response.response_code() == Response::RespNameNotFound) {
emit arrowDeleted(creatorId, arrowId);
}
}
@@ -285,19 +281,12 @@ void GameEventHandler::eventGameStateChanged(const Event_GameStateChanged &event
emit playerJoined(prop);
}
player->processPlayerInfo(playerInfo);
- // Extract playmat from player properties for opponent display
- if (prop.has_playmat_params()) {
- player->setPlaymatFromProperties(prop);
- }
if (player->getPlayerInfo()->getLocal()) {
emit localPlayerDeckSelected(player, playerId, playerInfo);
} else {
if (!game->getGameMetaInfo()->proto().share_decklists_on_load()) {
continue;
}
- if (!playerInfo.has_deck_list()) {
- continue;
- }
opponentDecksToDisplay.append(
qMakePair(playerId, qMakePair(playerName, QString::fromStdString(playerInfo.deck_list()))));
@@ -355,11 +344,6 @@ void GameEventHandler::eventPlayerPropertiesChanged(const Event_PlayerProperties
const ServerInfo_PlayerProperties &prop = event.player_properties();
emit playerPropertiesChanged(prop, eventPlayerId);
- // Update playmat from player properties
- if (prop.has_playmat_params()) {
- player->setPlaymatFromProperties(prop);
- }
-
const auto contextType = static_cast(getPbExtension(context));
switch (contextType) {
case GameEventContext::READY_START: {
diff --git a/cockatrice/src/game/player/player_logic.cpp b/cockatrice/src/game/player/player_logic.cpp
index 45ba09aac..485e2fc5c 100644
--- a/cockatrice/src/game/player/player_logic.cpp
+++ b/cockatrice/src/game/player/player_logic.cpp
@@ -250,22 +250,6 @@ void PlayerLogic::setDeck(const DeckList &_deck)
emit deckChanged();
}
-void PlayerLogic::setPlaymatFromProperties(const ServerInfo_PlayerProperties &props)
-{
- if (props.has_playmat_params() && !props.playmat_params().card_name().empty()) {
- const auto &pp = props.playmat_params();
- remotePlaymatCard = {QString::fromStdString(pp.card_name()), QString::fromStdString(pp.card_provider_id())};
- remotePlaymatParams = {qBound(0.0, pp.margin_pct_l(), 0.95), qBound(0.0, pp.margin_pct_r(), 0.95),
- qBound(0.0, pp.vertical_offset(), 1.0), qBound(0.1, pp.zoom(), 4.0)};
- hasRemotePlaymat = true;
- } else {
- remotePlaymatCard = CardRef{};
- remotePlaymatParams = PlaymatParams{};
- hasRemotePlaymat = false;
- }
- emit playmatChanged();
-}
-
CounterState *PlayerLogic::addCounter(const ServerInfo_Counter &counter)
{
return addCounter(counter.id(), QString::fromStdString(counter.name()),
diff --git a/cockatrice/src/game/player/player_logic.h b/cockatrice/src/game/player/player_logic.h
index 6923b3afe..a89cb6eed 100644
--- a/cockatrice/src/game/player/player_logic.h
+++ b/cockatrice/src/game/player/player_logic.h
@@ -17,7 +17,6 @@
#include "../zones/table_zone_logic.h"
#include "player_event_handler.h"
#include "player_info.h"
-#include "player_manager.h"
#include
#include
@@ -73,8 +72,6 @@ signals:
const QList &cardList,
bool withWritePermission);
void deckChanged();
- /** @brief Emitted when the remote playmat (card/params) is updated from player properties. */
- void playmatChanged();
void newCardAdded(AbstractCardItem *card);
void requestCardMenuUpdate(const CardItem *card);
void counterAdded(CounterState *state);
@@ -229,20 +226,6 @@ public:
void setZoneId(int _zoneId);
- void setPlaymatFromProperties(const ServerInfo_PlayerProperties &props);
- const CardRef &getRemotePlaymatCard() const
- {
- return remotePlaymatCard;
- }
- const PlaymatParams &getRemotePlaymatParams() const
- {
- return remotePlaymatParams;
- }
- bool getHasRemotePlaymat() const
- {
- return hasRemotePlaymat;
- }
-
private:
AbstractGame *game;
PlayerInfo *playerInfo;
@@ -260,11 +243,6 @@ private:
bool dialogSemaphore;
QList cardsToDelete;
-
- // Playmat from player properties (for opponent display)
- CardRef remotePlaymatCard;
- PlaymatParams remotePlaymatParams;
- bool hasRemotePlaymat = false;
};
class AnnotationDialog : public QInputDialog
diff --git a/cockatrice/src/game_graphics/animated_item.h b/cockatrice/src/game_graphics/animated_item.h
deleted file mode 100644
index 700e0f62d..000000000
--- a/cockatrice/src/game_graphics/animated_item.h
+++ /dev/null
@@ -1,26 +0,0 @@
-#ifndef ANIMATED_ITEM_H
-#define ANIMATED_ITEM_H
-
-/**
- * @file animated_item.h
- * @ingroup GameGraphics
- * @brief Interface for scene items driven by GameScene's shared animation timer.
- *
- * Items that want per-tick animation while a single QBasicTimer runs (instead of
- * owning their own QTimer) implement this interface and register with the scene
- * via GameScene::registerAnimationItem.
- */
-
-class IAnimatedItem
-{
-public:
- virtual ~IAnimatedItem() = default;
-
- /**
- * @brief Advances the item's animation by one timer tick.
- * @return true while the animation is still running, false once it has finished.
- */
- virtual bool animationEvent() = 0;
-};
-
-#endif
diff --git a/cockatrice/src/game_graphics/board/abstract_card_item.cpp b/cockatrice/src/game_graphics/board/abstract_card_item.cpp
index 1410d0c80..e0029ee2d 100644
--- a/cockatrice/src/game_graphics/board/abstract_card_item.cpp
+++ b/cockatrice/src/game_graphics/board/abstract_card_item.cpp
@@ -305,11 +305,6 @@ void AbstractCardItem::setTapped(bool _tapped, bool canAnimate)
}
}
-bool AbstractCardItem::animationEvent()
-{
- return false;
-}
-
void AbstractCardItem::setFaceDown(bool _facedown)
{
facedown = _facedown;
diff --git a/cockatrice/src/game_graphics/board/abstract_card_item.h b/cockatrice/src/game_graphics/board/abstract_card_item.h
index 8cbe95282..bdb5f7cf1 100644
--- a/cockatrice/src/game_graphics/board/abstract_card_item.h
+++ b/cockatrice/src/game_graphics/board/abstract_card_item.h
@@ -7,7 +7,6 @@
#ifndef ABSTRACTCARDITEM_H
#define ABSTRACTCARDITEM_H
-#include "../animated_item.h"
#include "../card_dimensions.h"
#include "arrow_target.h"
#include "graphics_item_type.h"
@@ -17,7 +16,7 @@
class PlayerLogic;
-class AbstractCardItem : public ArrowTarget, public IAnimatedItem
+class AbstractCardItem : public ArrowTarget
{
Q_OBJECT
protected:
@@ -127,9 +126,6 @@ public:
emit deleteCardInfoPopup(cardRef.name);
}
- /** @brief Default: no per-tick animation. Subclasses override to animate. */
- bool animationEvent() override;
-
protected:
void transformPainter(QPainter *painter, const QSizeF &translatedSize, int angle);
void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
diff --git a/cockatrice/src/game_graphics/board/abstract_counter.cpp b/cockatrice/src/game_graphics/board/abstract_counter.cpp
index e63117e13..a20fb1b3c 100644
--- a/cockatrice/src/game_graphics/board/abstract_counter.cpp
+++ b/cockatrice/src/game_graphics/board/abstract_counter.cpp
@@ -29,9 +29,8 @@ AbstractCounter::AbstractCounter(CounterState *state,
{
setAcceptHoverEvents(true);
- connect(state, &CounterState::valueChanged, this, [this](int oldValue, int newValue) {
+ connect(state, &CounterState::valueChanged, this, [this](int, int newValue) {
value = newValue;
- onValueChanged(oldValue, newValue);
update();
});
@@ -229,9 +228,3 @@ void AbstractCounterDialog::changeValue(int diff)
curValue += diff;
setTextValue(QString::number(curValue));
}
-
-void AbstractCounter::onValueChanged(int /*oldValue*/, int /*newValue*/)
-{
- // Default: no feedback. Subclasses such as PlayerCounter override this to
- // flash the counter on meaningful changes (life gain/loss).
-}
diff --git a/cockatrice/src/game_graphics/board/abstract_counter.h b/cockatrice/src/game_graphics/board/abstract_counter.h
index 9ddcc6d58..b319a722d 100644
--- a/cockatrice/src/game_graphics/board/abstract_counter.h
+++ b/cockatrice/src/game_graphics/board/abstract_counter.h
@@ -35,13 +35,6 @@ protected:
bool hovered = false;
bool useNameForShortcut;
- /**
- * @brief Hook for subclasses that need per-value-change feedback (e.g. life-total flash).
- *
- * Called whenever the counter's value changes, before the item repaints.
- */
- virtual void onValueChanged(int oldValue, int newValue);
-
void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override;
void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override;
diff --git a/cockatrice/src/game_graphics/board/arrow_item.cpp b/cockatrice/src/game_graphics/board/arrow_item.cpp
index 664d44ecc..ce8967bb5 100644
--- a/cockatrice/src/game_graphics/board/arrow_item.cpp
+++ b/cockatrice/src/game_graphics/board/arrow_item.cpp
@@ -4,14 +4,12 @@
#include "../../client/settings/cache_settings.h"
#include "../../game/player/player_actions.h"
#include "../../game/player/player_logic.h"
-#include "../game_scene.h"
#include "../player/player_target.h"
#include "../z_values.h"
#include "../zones/card_zone.h"
#include "card_item.h"
#include
-#include
#include
#include
#include
@@ -20,27 +18,10 @@
#include
#include
#include
-#include
#include
#include
#include
-namespace
-{
-constexpr qreal kMinStrokeDurationMs = 200.0;
-constexpr qreal kMaxStrokeDurationMs = 450.0;
-constexpr qreal kMsPerPixel = 0.8;
-constexpr qreal kGlowFadeDurationMs = 120.0;
-constexpr qreal kSheenHalfWidth = 14.0;
-
-/// @brief Ease-out cubic, for a natural "slow in / slow out" reveal.
-qreal easeOutCubic(qreal t)
-{
- const qreal inverse = 1.0 - t;
- return 1.0 - inverse * inverse * inverse;
-}
-} // namespace
-
ArrowItem::ArrowItem(QSharedPointer _data, ArrowTarget *_startItem, ArrowTarget *_targetItem)
: data(std::move(_data)), startItem(_startItem), targetItem(_targetItem)
{
@@ -66,23 +47,8 @@ ArrowItem::ArrowItem(QSharedPointer _data, ArrowTarget *_startI
}
}
-ArrowItem::~ArrowItem()
-{
- if (auto *scene = qobject_cast(this->scene())) {
- scene->unregisterAnimationItem(this);
- }
-}
-
void ArrowItem::onTargetDestroyed()
{
- if (data->id == -1) {
- // Drag and attach arrows are never inserted into the arrow registry and
- // have no server-side counterpart, so no deletion event can clean them
- // up. Delete them locally when either endpoint is destroyed.
- delArrow();
- return;
- }
-
emit requestDeletion(data->creatorId, data->id);
}
@@ -125,21 +91,16 @@ void ArrowItem::updatePath(const QPointF &endPoint)
prepareGeometryChange();
if (lineLength < 30) {
path = QPainterPath();
- bodyPath = QPainterPath();
- headPath = QPainterPath();
- shaftOutlinePath = QPainterPath();
- centerLine = QPainterPath();
- headBaseFraction = 1.0;
} else {
QPointF c(lineLength / 2, qTan(phi * M_PI / 180) * lineLength);
- centerLine = QPainterPath();
+ QPainterPath centerLine;
centerLine.moveTo(0, 0);
centerLine.quadTo(c, QPointF(lineLength, 0));
- headBaseFraction = 1 - headLength / lineLength;
- QPointF arrowBodyEndPoint = centerLine.pointAtPercent(headBaseFraction);
- QLineF testLine(arrowBodyEndPoint, centerLine.pointAtPercent(headBaseFraction + 0.001));
+ double percentage = 1 - headLength / lineLength;
+ QPointF arrowBodyEndPoint = centerLine.pointAtPercent(percentage);
+ QLineF testLine(arrowBodyEndPoint, centerLine.pointAtPercent(percentage + 0.001));
qreal alpha = testLine.angle() - 90;
QPointF endPoint1 =
arrowBodyEndPoint + arrowWidth / 2 * QPointF(qCos(alpha * M_PI / 180), -qSin(alpha * M_PI / 180));
@@ -150,89 +111,20 @@ void ArrowItem::updatePath(const QPointF &endPoint)
QPointF point2 =
endPoint2 + (headWidth - arrowWidth) / 2 * QPointF(-qCos(alpha * M_PI / 180), qSin(alpha * M_PI / 180));
- QPointF start1 = -arrowWidth / 2 * QPointF(qCos((phi - 90) * M_PI / 180), qSin((phi - 90) * M_PI / 180));
- QPointF start2 = arrowWidth / 2 * QPointF(qCos((phi - 90) * M_PI / 180), qSin((phi - 90) * M_PI / 180));
-
- path = QPainterPath(start1);
+ path = QPainterPath(-arrowWidth / 2 * QPointF(qCos((phi - 90) * M_PI / 180), qSin((phi - 90) * M_PI / 180)));
path.quadTo(c, endPoint1);
path.lineTo(point1);
path.lineTo(QPointF(lineLength, 0));
path.lineTo(point2);
path.lineTo(endPoint2);
- path.quadTo(c, start2);
- path.lineTo(start1);
-
- bodyPath = QPainterPath(start1);
- bodyPath.quadTo(c, endPoint1);
- bodyPath.lineTo(endPoint2);
- bodyPath.quadTo(c, start2);
- bodyPath.lineTo(start1);
-
- headPath = QPainterPath(endPoint1);
- headPath.lineTo(point1);
- headPath.lineTo(QPointF(lineLength, 0));
- headPath.lineTo(point2);
- headPath.lineTo(endPoint2);
-
- shaftOutlinePath = QPainterPath(start1);
- shaftOutlinePath.quadTo(c, endPoint1);
- shaftOutlinePath.moveTo(endPoint2);
- shaftOutlinePath.quadTo(c, start2);
- shaftOutlinePath.lineTo(start1);
+ path.quadTo(c, arrowWidth / 2 * QPointF(qCos((phi - 90) * M_PI / 180), qSin((phi - 90) * M_PI / 180)));
+ path.lineTo(-arrowWidth / 2 * QPointF(qCos((phi - 90) * M_PI / 180), qSin((phi - 90) * M_PI / 180)));
}
setPos(startPoint);
setTransform(QTransform().rotate(-line.angle()));
}
-void ArrowItem::startDrawAnimation()
-{
- if (!SettingsCache::instance().cardsDisplay().getArrowDrawAnimation() || centerLine.isEmpty()) {
- return;
- }
-
- strokeDurationMs = qBound(kMinStrokeDurationMs, centerLine.length() * kMsPerPixel, kMaxStrokeDurationMs);
- glowFadeDurationMs = kGlowFadeDurationMs;
- // The clock is started on the first animationEvent() tick so that t=0
- // corresponds to the first rendered frame. Starting it here would count
- // the time spent before the item's first paint (event-loop delays, bursts
- // of arrows created together), making the arrow appear already partway
- // drawn when it first shows up.
- animationStarted = false;
- drawProgress = 0.0;
- glowAlpha = 1.0;
- update();
- if (auto *scene = qobject_cast(this->scene())) {
- scene->registerAnimationItem(this);
- }
-}
-
-bool ArrowItem::animationEvent()
-{
- if (!animationStarted) {
- animationClock.start();
- animationStarted = true;
- }
-
- const qint64 elapsed = animationClock.elapsed();
- if (elapsed >= strokeDurationMs + glowFadeDurationMs) {
- drawProgress = 1.0;
- glowAlpha = 0.0;
- update();
- return false;
- }
-
- if (elapsed < strokeDurationMs) {
- drawProgress = easeOutCubic(qBound(0.0, elapsed / strokeDurationMs, 1.0));
- glowAlpha = 1.0;
- } else {
- drawProgress = 1.0;
- glowAlpha = 1.0 - (elapsed - strokeDurationMs) / glowFadeDurationMs;
- }
- update();
- return true;
-}
-
void ArrowItem::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
{
QColor paintColor(data->color);
@@ -241,66 +133,8 @@ void ArrowItem::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*opti
} else {
paintColor.setAlpha(150);
}
-
- painter->save();
- const QPen outlinePen = painter->pen();
painter->setBrush(paintColor);
-
- const auto drawShaft = [this, painter, &outlinePen, paintColor]() {
- painter->setPen(Qt::NoPen);
- painter->drawPath(bodyPath);
- painter->setPen(outlinePen);
- painter->setBrush(Qt::NoBrush);
- painter->drawPath(shaftOutlinePath);
- painter->setBrush(paintColor);
- };
-
- if (drawProgress >= 1.0 || path.isEmpty()) {
- painter->drawPath(path);
- } else if (drawProgress < headBaseFraction) {
- // The reveal edge and the sheen share the same arc-length parameterization,
- // so the stroke stays exactly in sync with the trailing sheen.
- const qreal revealX = centerLine.pointAtPercent(drawProgress).x();
- QPainterPath clip;
- clip.addRect(QRectF(-glowExtent, path.boundingRect().top() - glowExtent, revealX + glowExtent,
- path.boundingRect().height() + 2 * glowExtent));
- painter->setClipPath(clip);
- drawShaft();
- } else {
- // Once the reveal reaches the head base, pop the whole head in with a fade
- // instead of slicing the triangle into a growing stub.
- drawShaft();
- const qreal headFadeIn = (drawProgress - headBaseFraction) / (1.0 - headBaseFraction);
- painter->setOpacity(headFadeIn);
- painter->setPen(Qt::NoPen);
- painter->drawPath(headPath);
- painter->setPen(outlinePen);
- painter->setBrush(Qt::NoBrush);
- painter->drawPath(headPath);
- painter->setOpacity(1.0);
- painter->setBrush(paintColor);
- }
-
- if (glowAlpha > 0.0 && !centerLine.isEmpty()) {
- // Sweep a bright band across the arrow. Clipping to the
- // silhouette keeps it flat against the shaft so it reads as a light reflection.
- const qreal anticipation = qMin(1.0, drawProgress / 0.08);
- const QPointF sweep = centerLine.pointAtPercent(qMin(drawProgress, 1.0));
- QLinearGradient sheen(sweep.x() - kSheenHalfWidth, 0.0, sweep.x() + kSheenHalfWidth, 0.0);
- sheen.setColorAt(0.0, QColor(paintColor.red(), paintColor.green(), paintColor.blue(), 0));
- sheen.setColorAt(0.5, QColor(255, 255, 255, 200));
- sheen.setColorAt(1.0, QColor(paintColor.red(), paintColor.green(), paintColor.blue(), 0));
- painter->save();
- painter->setPen(Qt::NoPen);
- painter->setClipPath(path);
- painter->setBrush(sheen);
- painter->setOpacity(glowAlpha * anticipation);
- painter->drawRect(QRectF(sweep.x() - kSheenHalfWidth - glowExtent, path.boundingRect().top() - glowExtent,
- (kSheenHalfWidth + glowExtent) * 2.0,
- path.boundingRect().height() + glowExtent * 2.0));
- painter->restore();
- }
- painter->restore();
+ painter->drawPath(path);
}
void ArrowItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
@@ -392,12 +226,6 @@ void ArrowDragItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
void ArrowDragItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
if (!startItem) {
- // The source card was destroyed while the arrow was being drawn.
- // Clean up the arrow and its children instead of leaking them.
- delArrow();
- for (auto *child : childArrows) {
- child->mouseReleaseEvent(event);
- }
return;
}
@@ -521,12 +349,6 @@ void ArrowAttachItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
void ArrowAttachItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
if (!startItem) {
- // The source card was destroyed while the arrow was being drawn.
- // Clean up the arrow and its children instead of leaking them.
- delArrow();
- for (auto *child : childArrows) {
- child->mouseReleaseEvent(event);
- }
return;
}
diff --git a/cockatrice/src/game_graphics/board/arrow_item.h b/cockatrice/src/game_graphics/board/arrow_item.h
index 76a2d5d6c..1c306e065 100644
--- a/cockatrice/src/game_graphics/board/arrow_item.h
+++ b/cockatrice/src/game_graphics/board/arrow_item.h
@@ -2,13 +2,9 @@
#define ARROWITEM_H
#include "../../game/board/arrow_data.h"
-#include "../animated_item.h"
#include "arrow_target.h"
-#include "graphics_item_type.h"
-#include
#include
-#include
#include
#include
@@ -16,7 +12,7 @@ class CardItem;
class QGraphicsSceneMouseEvent;
class PlayerLogic;
-class ArrowItem : public QObject, public QGraphicsItem, public IAnimatedItem
+class ArrowItem : public QObject, public QGraphicsItem
{
Q_OBJECT
Q_INTERFACES(QGraphicsItem)
@@ -25,19 +21,6 @@ signals:
private:
QPainterPath path;
- QPainterPath bodyPath;
- QPainterPath headPath;
- QPainterPath shaftOutlinePath;
- QPainterPath centerLine;
- qreal headBaseFraction = 1.0;
- QElapsedTimer animationClock;
- qreal strokeDurationMs = 0;
- qreal glowFadeDurationMs = 0;
- qreal drawProgress = 1.0;
- qreal glowAlpha = 0.0;
- bool animationStarted = false;
-
- static constexpr qreal glowExtent = 12.0;
protected:
QSharedPointer data;
@@ -49,28 +32,17 @@ protected:
void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
public:
- enum
- {
- Type = typeArrow
- };
- [[nodiscard]] int type() const override
- {
- return Type;
- }
ArrowItem(QSharedPointer _data, ArrowTarget *_startItem, ArrowTarget *_targetItem);
- ~ArrowItem() override;
void onTargetDestroyed();
void delArrow();
void updatePath();
void updatePath(const QPointF &endPoint);
- void startDrawAnimation();
- bool animationEvent() override;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
[[nodiscard]] QRectF boundingRect() const override
{
- return path.boundingRect().adjusted(-glowExtent, -glowExtent, glowExtent, glowExtent);
+ return path.boundingRect();
}
[[nodiscard]] QPainterPath shape() const override
{
@@ -134,4 +106,4 @@ protected:
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
};
-#endif
+#endif
\ No newline at end of file
diff --git a/cockatrice/src/game_graphics/board/card_item.h b/cockatrice/src/game_graphics/board/card_item.h
index 2ba43d03d..37f3bab50 100644
--- a/cockatrice/src/game_graphics/board/card_item.h
+++ b/cockatrice/src/game_graphics/board/card_item.h
@@ -137,7 +137,7 @@ public:
void resetState(bool keepAnnotations = false);
void processCardInfo(const ServerInfo_Card &_info);
- bool animationEvent() override;
+ bool animationEvent();
CardDragItem *createDragItem(int _id, const QPointF &_pos, const QPointF &_scenePos, bool forceFaceDown);
void deleteDragItem();
void drawArrow(const QColor &arrowColor);
diff --git a/cockatrice/src/game_graphics/board/graphics_item_type.h b/cockatrice/src/game_graphics/board/graphics_item_type.h
index afac7881f..7eac132b0 100644
--- a/cockatrice/src/game_graphics/board/graphics_item_type.h
+++ b/cockatrice/src/game_graphics/board/graphics_item_type.h
@@ -16,8 +16,7 @@ enum GraphicsItemType
typeZone = QGraphicsItem::UserType + 3,
typePlayerTarget = QGraphicsItem::UserType + 4,
typeDeckViewCardContainer = QGraphicsItem::UserType + 5,
- typeOther = QGraphicsItem::UserType + 6,
- typeArrow = QGraphicsItem::UserType + 7
+ typeOther = QGraphicsItem::UserType + 6
};
#endif // COCKATRICE_GRAPHICS_ITEM_TYPE_H
diff --git a/cockatrice/src/game_graphics/deckview/deck_view_container.cpp b/cockatrice/src/game_graphics/deckview/deck_view_container.cpp
index bc07ac183..69941040f 100644
--- a/cockatrice/src/game_graphics/deckview/deck_view_container.cpp
+++ b/cockatrice/src/game_graphics/deckview/deck_view_container.cpp
@@ -9,21 +9,17 @@
#include "../../interface/widgets/dialogs/dlg_load_deck_from_website.h"
#include "../../interface/widgets/dialogs/dlg_load_remote_deck.h"
#include "../../interface/widgets/tabs/tab_game.h"
-#include "../../interface/widgets/visual_deck_storage/visual_deck_storage_widget.h"
#include "deck_view.h"
#include
#include
#include
-#include
#include
#include
-#include
#include
#include
#include
#include
-#include
#include
#include
@@ -104,9 +100,6 @@ DeckViewContainer::DeckViewContainer(int _playerId, TabGame *parent)
connect(&SettingsCache::instance().visualDeckStorage(), &VisualDeckStorageSettings::visualDeckStorageInGameChanged,
this, &DeckViewContainer::setVisualDeckStorageExists);
- connect(&SettingsCache::instance().userInterface(), &InterfaceSettings::playmatSettingsChanged, this,
- &DeckViewContainer::onPlaymatSettingsChanged);
-
switchToDeckSelectView();
}
@@ -284,8 +277,6 @@ void DeckViewContainer::loadDeckFromFile(const QString &filePath)
void DeckViewContainer::loadDeckFromDeckList(const DeckList &deck)
{
- currentDeck = deck;
-
QString deckString = deck.writeToString_Native();
if (deckString.length() > MAX_FILE_LENGTH) {
@@ -298,52 +289,6 @@ void DeckViewContainer::loadDeckFromDeckList(const DeckList &deck)
PendingCommand *pend = parentGame->getGame()->getGameEventHandler()->prepareGameCommand(cmd);
connect(pend, &PendingCommand::finished, this, &DeckViewContainer::deckSelectFinished);
parentGame->getGame()->getGameEventHandler()->sendGameCommand(pend, playerId);
-
- resolveAndSendPlaymat();
-}
-
-void DeckViewContainer::resolveAndSendPlaymat()
-{
- if (currentDeck.getCardRefList().isEmpty() && currentDeck.getPlaymat().card.isEmpty()) {
- return;
- }
-
- const auto &settings = SettingsCache::instance().userInterface();
- const auto fallbackBehavior = static_cast(settings.getPlaymatFallbackBehavior());
-
- QList fallbackList = settings.getPlaymatFallbackList();
-
- // In random mode with 2+ entries, remove the last-resolved mat to avoid repeats.
- if (fallbackBehavior == PlaymatFallbackModeRandom && fallbackList.size() > 1) {
- fallbackList.removeAll(lastResolvedPlaymat);
- }
-
- const PlaymatInfo resolved =
- resolvePlaymatForDeck(currentDeck, fallbackList, static_cast(settings.getPlaymatMode()),
- fallbackBehavior, playmatRotationIndex);
-
- lastResolvedPlaymat = resolved;
-
- Command_SetPlaymat playmatCmd;
- auto *pp = playmatCmd.mutable_playmat_params();
- pp->set_card_name(resolved.card.name.toStdString());
- pp->set_card_provider_id(resolved.card.providerId.toStdString());
- pp->set_margin_pct_l(resolved.params.marginPctL);
- pp->set_margin_pct_r(resolved.params.marginPctR);
- pp->set_vertical_offset(resolved.params.verticalOffset);
- pp->set_zoom(resolved.params.zoom);
- PendingCommand *playmatPend = parentGame->getGame()->getGameEventHandler()->prepareGameCommand(playmatCmd);
- parentGame->getGame()->getGameEventHandler()->sendGameCommand(playmatPend, playerId);
-}
-
-void DeckViewContainer::onPlaymatSettingsChanged()
-{
- resolveAndSendPlaymat();
-}
-
-void DeckViewContainer::advancePlaymatRotation()
-{
- playmatRotationIndex++;
}
void DeckViewContainer::loadRemoteDeck()
@@ -434,10 +379,6 @@ void DeckViewContainer::sideboardPlanChanged()
*/
void DeckViewContainer::sendReadyStartCommand(bool ready)
{
- if (ready) {
- resolveAndSendPlaymat();
- }
-
Command_ReadyStart cmd;
cmd.set_ready(ready);
parentGame->getGame()->getGameEventHandler()->sendGameCommand(cmd, playerId);
@@ -475,7 +416,6 @@ void DeckViewContainer::setSideboardLocked(bool locked)
void DeckViewContainer::setDeck(const DeckList &deck)
{
- currentDeck = deck;
deckView->setDeck(deck);
switchToDeckLoadedView();
}
\ No newline at end of file
diff --git a/cockatrice/src/game_graphics/deckview/deck_view_container.h b/cockatrice/src/game_graphics/deckview/deck_view_container.h
index b5317c39a..ec024bace 100644
--- a/cockatrice/src/game_graphics/deckview/deck_view_container.h
+++ b/cockatrice/src/game_graphics/deckview/deck_view_container.h
@@ -57,9 +57,6 @@ private:
VisualDeckStorageWidget *visualDeckStorageWidget;
TabGame *parentGame;
int playerId;
- int playmatRotationIndex = 0; ///< Per-match cursor for round-robin playmat mode.
- DeckList currentDeck; ///< Cached deck for live settings re-resolution.
- PlaymatInfo lastResolvedPlaymat; ///< Tracks last sent playmat to avoid repeats in random mode.
void tryCreateVisualDeckStorageWidget();
void sendReadyStartCommand(bool ready);
@@ -78,7 +75,6 @@ private slots:
void sideboardLockButtonClicked();
void updateSideboardLockButtonText();
void refreshShortcuts();
- void onPlaymatSettingsChanged();
signals:
void newCardAdded(AbstractCardItem *card);
void notIdle();
@@ -91,8 +87,6 @@ public:
void setSideboardLocked(bool locked);
void setDeck(const DeckList &deck);
void setVisualDeckStorageExists(bool exists);
- void advancePlaymatRotation();
- void resolveAndSendPlaymat();
public slots:
void loadDeckFromFile(const QString &filePath);
diff --git a/cockatrice/src/game_graphics/game_scene.cpp b/cockatrice/src/game_graphics/game_scene.cpp
index 87af4c73c..db2088104 100644
--- a/cockatrice/src/game_graphics/game_scene.cpp
+++ b/cockatrice/src/game_graphics/game_scene.cpp
@@ -17,6 +17,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -44,14 +45,7 @@ GameScene::GameScene(PhasesToolbar *_phasesToolbar, QObject *parent)
GameScene::~GameScene()
{
- // Sever all incoming connections (animated item destroy-tracking) before the
- // members below are destroyed: the base QGraphicsScene destructor destroys the
- // remaining items, and their destroyed() signals must not reach slots that
- // reference members that no longer exist.
- QObject::disconnect(nullptr, nullptr, this, nullptr);
-
delete animationTimer;
- animationTimer = nullptr;
// Delete all ArrowItems before QGraphicsScene's base destructor runs.
// QGraphicsScene::~QGraphicsScene() destroys items in arbitrary order.
@@ -252,27 +246,17 @@ void GameScene::adjustPlayerRotation(int rotationAdjustment)
*/
void GameScene::rearrange()
{
- if (rearranging) {
- needsReArrange = true;
- return;
- }
- rearranging = true;
- do {
- needsReArrange = false;
+ int firstPlayerIndex = 0;
+ auto playersPlaying = collectActivePlayers(firstPlayerIndex);
+ playersPlaying = rotatePlayers(playersPlaying, firstPlayerIndex);
- int firstPlayerIndex = 0;
- auto playersPlaying = collectActivePlayers(firstPlayerIndex);
- playersPlaying = rotatePlayers(playersPlaying, firstPlayerIndex);
+ int columns = determineColumnCount(playersPlaying.size());
+ QSizeF sceneSize = computeSceneSizeAndPlayerLayout(playersPlaying, columns);
- int columns = determineColumnCount(playersPlaying.size());
- QSizeF sceneSize = computeSceneSizeAndPlayerLayout(playersPlaying, columns);
+ phasesToolbar->setHeight(sceneSize.height());
+ setSceneRect(0, 0, sceneSize.width(), sceneSize.height());
- phasesToolbar->setHeight(sceneSize.height());
- setSceneRect(0, 0, sceneSize.width(), sceneSize.height());
-
- processViewSizeChange(viewSize);
- } while (needsReArrange);
- rearranging = false;
+ processViewSizeChange(viewSize);
}
// ---------- View Size ----------
@@ -469,14 +453,8 @@ void GameScene::resizeColumnsAndPlayers(const QList &minWidthByColumn, qr
qreal extraWidthPerColumn = (newWidth - minWidth) / playersByColumn.size();
qreal newx = phasesToolbar->getWidth();
- // Snapshot the columns: resizing a player's table can synchronously trigger
- // GameScene::rearrange (table width -> sizeChanged -> updateBoundingRect ->
- // sizeChanged -> rearrange), and rearrange rebuilds playersByColumn. Iterating
- // the live container across that re-entrant call would use invalidated iterators.
- const QList> columns = playersByColumn;
-
- for (int col = 0; col < columns.size(); ++col) {
- for (PlayerGraphicsItem *player : columns[col]) {
+ for (int col = 0; col < playersByColumn.size(); ++col) {
+ for (PlayerGraphicsItem *player : playersByColumn[col]) {
player->processSceneSizeChange(minWidthByColumn[col] + extraWidthPerColumn);
player->setPos(newx, player->y());
}
@@ -518,7 +496,6 @@ void GameScene::addArrow(QSharedPointer data)
auto *arrow = new ArrowItem(data, startCard, targetItem);
addItem(arrow);
- arrow->startDrawAnimation();
arrowRegistry.insert(data, arrow);
connect(arrow, &ArrowItem::requestDeletion, this, &GameScene::requestArrowDeletion);
}
@@ -759,45 +736,30 @@ bool GameScene::event(QEvent *event)
void GameScene::timerEvent(QTimerEvent * /*event*/)
{
- QMutableHashIterator i(animatedItems);
+ QMutableSetIterator i(cardsToAnimate);
while (i.hasNext()) {
i.next();
if (!i.value()->animationEvent()) {
i.remove();
}
}
- if (animatedItems.isEmpty()) {
+ if (cardsToAnimate.isEmpty()) {
animationTimer->stop();
}
}
-void GameScene::registerAnimationItem(IAnimatedItem *item)
+void GameScene::registerAnimationItem(AbstractCardItem *card)
{
- auto *object = dynamic_cast(item);
- if (!object) {
- return;
- }
- if (!animatedItems.contains(object)) {
- connect(object, &QObject::destroyed, this, &GameScene::removeAnimatedItem);
- }
- animatedItems.insert(object, item);
- if (animationTimer && !animationTimer->isActive()) {
+ cardsToAnimate.insert(static_cast(card));
+ if (!animationTimer->isActive()) {
animationTimer->start(10, this);
}
}
-void GameScene::unregisterAnimationItem(IAnimatedItem *item)
+void GameScene::unregisterAnimationItem(AbstractCardItem *card)
{
- animatedItems.remove(dynamic_cast(item));
- if (animationTimer && animatedItems.isEmpty()) {
- animationTimer->stop();
- }
-}
-
-void GameScene::removeAnimatedItem(QObject *item)
-{
- animatedItems.remove(item);
- if (animationTimer && animatedItems.isEmpty()) {
+ cardsToAnimate.remove(static_cast(card));
+ if (cardsToAnimate.isEmpty()) {
animationTimer->stop();
}
}
diff --git a/cockatrice/src/game_graphics/game_scene.h b/cockatrice/src/game_graphics/game_scene.h
index c12696189..74e979556 100644
--- a/cockatrice/src/game_graphics/game_scene.h
+++ b/cockatrice/src/game_graphics/game_scene.h
@@ -4,14 +4,13 @@
#include "../game/arrow_registry.h"
#include "../game/board/arrow_data.h"
#include "../game/zones/card_zone_logic.h"
-#include "animated_item.h"
#include "board/arrow_item.h"
#include
-#include
#include
#include
#include
+#include
inline Q_LOGGING_CATEGORY(GameSceneLog, "game_scene");
inline Q_LOGGING_CATEGORY(GameScenePlayerAdditionRemovalLog, "game_scene.player_addition_removal");
@@ -25,7 +24,6 @@ class CardItem;
class ServerInfo_Card;
class PhasesToolbar;
class QBasicTimer;
-class QObject;
/**
* @class GameScene
@@ -52,11 +50,9 @@ private:
QList zoneViews; ///< Active zone view widgets
QSize viewSize; ///< Current view size
QPointer hoveredCard; ///< Currently hovered card
- QBasicTimer *animationTimer; ///< Timer for scene animations
- QHash animatedItems; ///< Items currently animating
+ QBasicTimer *animationTimer; ///< Timer for card animations
+ QSet cardsToAnimate; ///< Cards currently animating
int playerRotation; ///< Rotation offset for player layout
- bool rearranging = false; ///< Guard against re-entrant rearrange
- bool needsReArrange = false; ///< Pending rearrange requested during a pass
/**
* @brief Updates which card is currently hovered based on scene coordinates.
@@ -186,24 +182,15 @@ public:
/** @brief Updates hovered card highlighting. */
void updateHoveredCard(CardItem *newCard);
- /**
- * @brief Registers an item for animation updates with the shared scene timer.
- *
- * The item must inherit QObject; it is unregistered automatically when it is
- * destroyed, so it may be deleted mid-animation without a dangling pointer.
- */
- void registerAnimationItem(IAnimatedItem *item);
+ /** @brief Registers a card for animation updates. */
+ void registerAnimationItem(AbstractCardItem *card);
- /** @brief Unregisters an item from animation updates. */
- void unregisterAnimationItem(IAnimatedItem *item);
+ /** @brief Unregisters a card from animation updates. */
+ void unregisterAnimationItem(AbstractCardItem *card);
void startRubberBand(const QPointF &selectionOrigin);
void resizeRubberBand(const QPointF &cursorPoint, int selectedCount);
void stopRubberBand();
-private slots:
- /** @brief Removes a destroyed item from the animation set. */
- void removeAnimatedItem(QObject *item);
-
public slots:
void onCardSelectionChanged(AbstractCardItem *card, bool selected);
void onCardRightClicked(AbstractCardItem *card, QPoint screenPos);
diff --git a/cockatrice/src/game_graphics/game_view.cpp b/cockatrice/src/game_graphics/game_view.cpp
index ed6355157..b768c8317 100644
--- a/cockatrice/src/game_graphics/game_view.cpp
+++ b/cockatrice/src/game_graphics/game_view.cpp
@@ -114,7 +114,6 @@ void GameView::startRubberBand(const QPointF &_selectionOrigin)
}
selectionOrigin = _selectionOrigin;
- previousBandRect = QRect();
rubberBand->setGeometry(QRect(mapFromScene(selectionOrigin), QSize(0, 0)));
rubberBand->show();
}
@@ -129,17 +128,7 @@ void GameView::resizeRubberBand(const QPointF &cursorPoint, int selectedCount)
QPoint cursor = cursorPoint.toPoint();
QRect rect = QRect(mapFromScene(selectionOrigin), cursor).normalized();
-
rubberBand->setGeometry(rect);
- if (viewport()) {
- // Repaint the union of the previous and current band rects: the vacated
- // strip of a child widget is not reliably invalidated on all platforms
- // (notably macOS), leaving stale pixels under the selection.
- QRect dirty = previousBandRect.isNull() ? rect : previousBandRect.united(rect);
- dirty.adjust(-1, -1, 1, 1);
- viewport()->update(dirty);
- previousBandRect = rect;
- }
if (!SettingsCache::instance().userInterface().getShowDragSelectionCount()) {
dragCountLabel->hide();
@@ -182,13 +171,7 @@ void GameView::stopRubberBand()
return;
}
- // Same rationale as resizeRubberBand: repaint the last known band area
- // since hiding a child widget doesn't reliably invalidate its region.
rubberBand->hide();
- if (viewport() && !previousBandRect.isNull()) {
- viewport()->update(previousBandRect.adjusted(-1, -1, 1, 1));
- previousBandRect = QRect();
- }
dragCountLabel->hide();
}
diff --git a/cockatrice/src/game_graphics/game_view.h b/cockatrice/src/game_graphics/game_view.h
index a23655513..3f6b60dbc 100644
--- a/cockatrice/src/game_graphics/game_view.h
+++ b/cockatrice/src/game_graphics/game_view.h
@@ -27,7 +27,6 @@ private:
QWidget *tallyContainer;
QGridLayout *tallyLayout;
QPointF selectionOrigin;
- QRect previousBandRect; ///< Last rubber-band rect for targeted repaint
QList cachedTallyRows; ///< Cached entries to avoid redundant rebuilds
QSize rebuildTallyLabels(const QList &entries);
diff --git a/cockatrice/src/game_graphics/player/player_graphics_item.cpp b/cockatrice/src/game_graphics/player/player_graphics_item.cpp
index 8bf2703e1..d443853ce 100644
--- a/cockatrice/src/game_graphics/player/player_graphics_item.cpp
+++ b/cockatrice/src/game_graphics/player/player_graphics_item.cpp
@@ -1,9 +1,6 @@
#include "player_graphics_item.h"
#include "../../game/player/player_actions.h"
-#include "../../interface/card_picture_loader/card_picture_loader.h"
-#include "../../interface/widgets/cards/art_crop_attribution.h"
-#include "../../interface/widgets/playmat/playmat_utils.h"
#include "../../interface/widgets/tabs/tab_game.h"
#include "../board/abstract_card_item.h"
#include "../board/counter_general.h"
@@ -16,9 +13,6 @@
#include "player_dialogs.h"
#include
-#include
-#include
-#include
#include
PlayerGraphicsItem::PlayerGraphicsItem(PlayerLogic *_player) : player(_player)
@@ -34,10 +28,6 @@ PlayerGraphicsItem::PlayerGraphicsItem(PlayerLogic *_player) : player(_player)
connect(player, &PlayerLogic::counterAdded, this, &PlayerGraphicsItem::onCounterAdded);
connect(player, &PlayerLogic::counterRemoved, this, &PlayerGraphicsItem::onCounterRemoved);
- connect(player, &PlayerLogic::deckChanged, this, &PlayerGraphicsItem::updatePlaymat);
- connect(player, &PlayerLogic::playmatChanged, this, &PlayerGraphicsItem::updatePlaymat);
- connect(&SettingsCache::instance().userInterface(), &InterfaceSettings::playmatVisibilityChanged, this,
- [this](int) { updatePlaymat(); });
playerMenu = new PlayerMenu(this);
@@ -77,9 +67,6 @@ PlayerGraphicsItem::PlayerGraphicsItem(PlayerLogic *_player) : player(_player)
connect(tableZoneGraphicsItem, &TableZone::sizeChanged, this, &PlayerGraphicsItem::updateBoundingRect);
- connect(this, &PlayerGraphicsItem::playmatChanged, tableZoneGraphicsItem, &TableZone::onPlaymatChanged);
- connect(this, &PlayerGraphicsItem::playmatChanged, stackZoneGraphicsItem, &StackZone::onPlaymatChanged);
-
updateBoundingRect();
rearrangeZones();
@@ -125,6 +112,7 @@ void PlayerGraphicsItem::initializeZones()
rfgZoneGraphicsItem->setPos(base + QPointF(0, 2 * h + h2 + 10));
tableZoneGraphicsItem = new TableZone(player->getTableZone(), mirrored, this);
+ connect(tableZoneGraphicsItem, &TableZone::sizeChanged, this, &PlayerGraphicsItem::updateBoundingRect);
connect(this, &PlayerGraphicsItem::mirroredChanged, tableZoneGraphicsItem, &TableZone::setMirrored);
stackZoneGraphicsItem =
@@ -167,61 +155,10 @@ qreal PlayerGraphicsItem::getMinimumWidth() const
return result;
}
-void PlayerGraphicsItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *)
+void PlayerGraphicsItem::paint(QPainter * /*painter*/,
+ const QStyleOptionGraphicsItem * /*option*/,
+ QWidget * /*widget*/)
{
- if (!hasPlaymat || playmatPixmap.isNull()) {
- return;
- }
-
- // Calculate the combined bounding rect of stack + table zones
- QPointF stackPos = stackZoneGraphicsItem->pos();
- QPointF tablePos = tableZoneGraphicsItem->pos();
- QSizeF stackSize = stackZoneGraphicsItem->boundingRect().size();
- QSizeF tableSize = tableZoneGraphicsItem->boundingRect().size();
-
- // Combined area: from stack left edge to table right edge
- double combinedLeft = qMin(stackPos.x(), tablePos.x());
- double combinedTop = qMin(stackPos.y(), tablePos.y());
- double combinedRight = qMax(stackPos.x() + stackSize.width(), tablePos.x() + tableSize.width());
- double combinedBottom = qMax(stackPos.y() + stackSize.height(), tablePos.y() + tableSize.height());
-
- QRectF combinedArea(combinedLeft, combinedTop, combinedRight - combinedLeft, combinedBottom - combinedTop);
-
- const QRectF srcRect = PlaymatUtils::computeArtSourceRect(playmatPixmap.size(), playmatParams);
- const QRectF dstRect = PlaymatUtils::coverFitRect(combinedArea, srcRect.size());
-
- painter->save();
- painter->setClipRect(combinedArea);
- painter->setRenderHint(QPainter::SmoothPixmapTransform, true);
-
- // Render from a down-scaled copy of the art so the full-resolution source
- // pixmap is never re-sampled at a tiny device size (also much cheaper than
- // scaling it on every frame).
- const QPixmap scaledPixmap = scaledPlaymatFor(srcRect, painter->worldTransform().mapRect(dstRect).size());
- painter->drawPixmap(dstRect, scaledPixmap, QRectF(scaledPixmap.rect()));
-
- painter->restore();
-
- if (!playmatAttribution.isEmpty()) {
- paintArtAttribution(*painter, combinedArea, playmatAttribution, Qt::AlignRight | Qt::AlignBottom, 0.8);
- }
-}
-
-QPixmap PlayerGraphicsItem::scaledPlaymatFor(const QRectF &srcRect, const QSizeF &deviceDstSize)
-{
- // Bucket the render size so the source pixmap is re-scaled at most once per
- // zoom step instead of once per frame.
- constexpr int bucketSize = 32;
- const QSize target = QSize(qMax(1, qRound(deviceDstSize.width() / bucketSize) * bucketSize),
- qMax(1, qRound(deviceDstSize.height() / bucketSize) * bucketSize))
- .boundedTo(srcRect.toAlignedRect().size());
-
- if (scaledPlaymatKey != target) {
- const QPixmap crop = playmatPixmap.copy(srcRect.toAlignedRect());
- scaledPlaymatPixmap = crop.scaled(target, Qt::KeepAspectRatio, Qt::SmoothTransformation);
- scaledPlaymatKey = target;
- }
- return scaledPlaymatPixmap;
}
void PlayerGraphicsItem::processSceneSizeChange(int newPlayerWidth)
@@ -251,11 +188,6 @@ void PlayerGraphicsItem::onCounterAdded(CounterState *state)
AbstractCounter *widget;
if (state->getName() == "life") {
widget = playerTarget->addCounter(state);
- connect(state, &CounterState::valueChanged, this, [this](int oldValue, int newValue) {
- if (newValue < oldValue) {
- tableZoneGraphicsItem->triggerDamageShimmer();
- }
- });
} else {
widget = new GeneralCounter(state, player, true, this);
}
@@ -366,100 +298,3 @@ void PlayerGraphicsItem::updateBoundingRect()
emit sizeChanged();
}
-
-void PlayerGraphicsItem::updatePlaymat()
-{
- int visibility = SettingsCache::instance().userInterface().getPlaymatVisibility();
-
- // "Don't use playmats" — never show
- if (visibility == PlaymatVisibilityNone) {
- clearPlaymat();
- return;
- }
-
- // "Show own playmat only" — hide playmats for remote players
- if (visibility == PlaymatVisibilityOwnOnly && !player->getPlayerInfo()->getLocal()) {
- clearPlaymat();
- return;
- }
-
- CardRef playmatCard;
- PlaymatParams params;
-
- if (player->getHasRemotePlaymat()) {
- // Prefer the server-confirmed playmat (updated by Command_SetPlaymat).
- playmatCard = player->getRemotePlaymatCard();
- params = player->getRemotePlaymatParams();
- } else if (player->getPlayerInfo()->getLocal()) {
- // Local player without a server broadcast yet: apply the full
- // settings-based resolution chain (mode, fallback list, behavior).
- const auto &settings = SettingsCache::instance().userInterface();
- const PlaymatInfo resolved = resolvePlaymatForDeck(
- player->getDeck(), settings.getPlaymatFallbackList(), static_cast(settings.getPlaymatMode()),
- static_cast(settings.getPlaymatFallbackBehavior()), 0);
- playmatCard = resolved.card;
- params = resolved.params;
- } else {
- // Opponent without a server broadcast: use the deck-embedded playmat.
- const DeckList &deck = player->getDeck();
- const PlaymatInfo &deckPlaymat = deck.getPlaymat();
- if (!deckPlaymat.card.isEmpty()) {
- playmatCard = deckPlaymat.card;
- params = deckPlaymat.params;
- }
- }
-
- if (playmatCard.isEmpty()) {
- clearPlaymat();
- return;
- }
-
- playmatParams = params;
- scaledPlaymatKey = QSize(); // the art crop depends on the params, drop any cached scale
-
- ExactCard card = CardDatabaseManager::query()->getCard(playmatCard);
- if (!card) {
- clearPlaymat();
- return;
- }
-
- playmatAttribution = buildArtAttribution(card);
-
- QPixmap fullRes;
- CardPictureLoader::getPixmap(fullRes, card, QSize(745, 1040));
-
- if (fullRes.isNull()) {
- disconnect(playmatPixmapConnection);
- CardInfo *cardInfo = card.getCardPtr().data();
- if (cardInfo) {
- playmatPixmapConnection =
- connect(cardInfo, &CardInfo::pixmapUpdated, this, &PlayerGraphicsItem::onPlaymatPixmapReady);
- }
- return;
- }
-
- if (!hasPlaymat) {
- hasPlaymat = true;
- emit playmatChanged(true);
- }
- playmatPixmap = fullRes;
- update();
-}
-
-void PlayerGraphicsItem::clearPlaymat()
-{
- disconnect(playmatPixmapConnection);
- playmatAttribution.clear();
- if (hasPlaymat) {
- hasPlaymat = false;
- playmatPixmap = QPixmap();
- scaledPlaymatKey = QSize();
- emit playmatChanged(false);
- update();
- }
-}
-
-void PlayerGraphicsItem::onPlaymatPixmapReady()
-{
- updatePlaymat();
-}
diff --git a/cockatrice/src/game_graphics/player/player_graphics_item.h b/cockatrice/src/game_graphics/player/player_graphics_item.h
index e5ae59a61..d02234ded 100644
--- a/cockatrice/src/game_graphics/player/player_graphics_item.h
+++ b/cockatrice/src/game_graphics/player/player_graphics_item.h
@@ -11,7 +11,6 @@
#include "../game_scene.h"
#include
-#include
class HandZone;
class PileZone;
@@ -127,7 +126,6 @@ signals:
void playerCountChanged();
void mirroredChanged(bool isMirrored);
void cardInfoRequested(const CardRef &cardRef);
- void playmatChanged(bool hasPlaymat);
private:
PlayerLogic *player;
@@ -148,23 +146,9 @@ private:
bool mirrored;
bool handVisible = false;
- QPixmap playmatPixmap;
- QPixmap scaledPlaymatPixmap; // down-scaled copy of playmatPixmap for the current render size
- QSize scaledPlaymatKey; // size bucket scaledPlaymatPixmap was rendered for
- PlaymatParams playmatParams;
- QString playmatAttribution;
- bool hasPlaymat = false;
- QMetaObject::Connection playmatPixmapConnection;
-
private slots:
void updateBoundingRect();
void rearrangeZones();
- void clearPlaymat();
- void updatePlaymat();
- void onPlaymatPixmapReady();
-
-private:
- QPixmap scaledPlaymatFor(const QRectF &srcRect, const QSizeF &deviceDstSize);
};
#endif // COCKATRICE_PLAYER_GRAPHICS_ITEM_H
diff --git a/cockatrice/src/game_graphics/player/player_target.cpp b/cockatrice/src/game_graphics/player/player_target.cpp
index 910ee9c17..567f3d44d 100644
--- a/cockatrice/src/game_graphics/player/player_target.cpp
+++ b/cockatrice/src/game_graphics/player/player_target.cpp
@@ -1,11 +1,8 @@
#include "player_target.h"
-#include "../../client/settings/cache_settings.h"
#include "../../game/player/player_logic.h"
#include "../../interface/pixel_map_generator.h"
-#include "../game_scene.h"
-#include
#include
#include
#include
@@ -24,24 +21,17 @@ QRectF PlayerCounter::boundingRect() const
void PlayerCounter::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
{
- const int radius = 15;
- const qreal border = 1.5;
- // The box is drawn with a border-wide stroke straddling the path, so the
- // visible outline spans [inset, inset + border]. Fills that must not cover
- // the outline (e.g. the life-change flash) use a path inset by `border`.
- const auto makePath = [](qreal inset) {
- QPainterPath path(QPointF(50 - inset, inset));
- path.lineTo(radius, inset);
- path.arcTo(inset, inset, 2 * radius, 2 * radius, 90, 90);
- path.lineTo(inset, 30 - inset);
- path.lineTo(50 - inset, 30 - inset);
- path.closeSubpath();
- return path;
- };
- QPainterPath path = makePath(border / 2);
+ const int radius = 8;
+ const qreal border = 1;
+ QPainterPath path(QPointF(50 - border / 2, border / 2));
+ path.lineTo(radius, border / 2);
+ path.arcTo(border / 2, border / 2, 2 * radius, 2 * radius, 90, 90);
+ path.lineTo(border / 2, 30 - border / 2);
+ path.lineTo(50 - border / 2, 30 - border / 2);
+ path.closeSubpath();
QPen pen(QColor(100, 100, 100));
- pen.setWidthF(border);
+ pen.setWidth(border);
painter->setPen(pen);
painter->setBrush(hovered ? QColor(50, 50, 50, 160) : QColor(0, 0, 0, 160));
@@ -55,48 +45,6 @@ void PlayerCounter::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*
painter->setFont(font);
painter->setPen(Qt::white);
painter->drawText(translatedRect, Qt::AlignCenter, QString::number(value));
-
- // Life-change flash: emerald on gain, red on loss, decaying over a few ticks.
- if (flashAlpha > 0) {
- painter->save();
- QColor flashColor = flashDelta > 0 ? QColor(52, 224, 122) : QColor(239, 68, 68);
- flashColor.setAlphaF(0.45 * flashAlpha);
- painter->setPen(Qt::NoPen);
- painter->setBrush(flashColor);
- painter->setOpacity(0.85);
- painter->drawPath(makePath(border));
- painter->restore();
- }
-}
-
-void PlayerCounter::onValueChanged(int oldValue, int newValue)
-{
- flashDelta = newValue - oldValue;
- if (flashDelta == 0) {
- return;
- }
-
- if (!SettingsCache::instance().userInterface().getLifeCounterAnimationsEnabled()) {
- flashAlpha = 0.0;
- return;
- }
-
- flashAlpha = 1.0;
- flashClock.start();
- if (scene()) {
- static_cast(scene())->registerAnimationItem(this);
- }
-}
-
-bool PlayerCounter::animationEvent()
-{
- flashAlpha = 1.0 - flashClock.elapsed() / flashDurationMs;
- if (flashAlpha <= 0.0) {
- flashAlpha = 0.0;
- return false;
- }
- update();
- return true;
}
PlayerTarget::PlayerTarget(PlayerLogic *_owner, QGraphicsItem *parentItem)
diff --git a/cockatrice/src/game_graphics/player/player_target.h b/cockatrice/src/game_graphics/player/player_target.h
index af0e9c8b7..67e155660 100644
--- a/cockatrice/src/game_graphics/player/player_target.h
+++ b/cockatrice/src/game_graphics/player/player_target.h
@@ -7,34 +7,21 @@
#ifndef PLAYERTARGET_H
#define PLAYERTARGET_H
-#include "../animated_item.h"
#include "../board/abstract_counter.h"
#include "../board/arrow_target.h"
#include "../board/graphics_item_type.h"
-#include
#include
class PlayerLogic;
-class PlayerCounter : public AbstractCounter, public IAnimatedItem
+class PlayerCounter : public AbstractCounter
{
Q_OBJECT
-protected:
- void onValueChanged(int oldValue, int newValue) override;
-
-private:
- static constexpr qreal flashDurationMs = 450.0;
-
- QElapsedTimer flashClock;
- qreal flashAlpha = 0.0;
- int flashDelta = 0;
-
public:
PlayerCounter(CounterState *state, PlayerLogic *player, QGraphicsItem *parent);
QRectF boundingRect() const override;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
- bool animationEvent() override;
};
class PlayerTarget : public ArrowTarget
diff --git a/cockatrice/src/game_graphics/zones/stack_zone.cpp b/cockatrice/src/game_graphics/zones/stack_zone.cpp
index e9b14f13d..184f96d62 100644
--- a/cockatrice/src/game_graphics/zones/stack_zone.cpp
+++ b/cockatrice/src/game_graphics/zones/stack_zone.cpp
@@ -31,22 +31,8 @@ QRectF StackZone::boundingRect() const
void StackZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
{
- if (playmatActive) {
- // Subtle overlay to distinguish stack zone from table zone (slightly darker)
- painter->fillRect(boundingRect(), QColor(0, 0, 0, 80));
- } else {
- QBrush brush = themeManager->getExtraBgBrush(ThemeManager::Stack, getLogic()->getPlayer()->getZoneId());
- painter->fillRect(boundingRect(), brush);
- }
-}
-
-void StackZone::onPlaymatChanged(bool active)
-{
- playmatActive = active;
- // See TableZone::onPlaymatChanged for the rationale. Translucent overlay
- // over a dynamic playmat should not be held in the device cache.
- setCacheMode(active ? QGraphicsItem::NoCache : QGraphicsItem::DeviceCoordinateCache);
- update();
+ QBrush brush = themeManager->getExtraBgBrush(ThemeManager::Stack, getLogic()->getPlayer()->getZoneId());
+ painter->fillRect(boundingRect(), brush);
}
void StackZone::handleDropEvent(const QList &dragItems,
diff --git a/cockatrice/src/game_graphics/zones/stack_zone.h b/cockatrice/src/game_graphics/zones/stack_zone.h
index 96b3f96a6..147c3e2fc 100644
--- a/cockatrice/src/game_graphics/zones/stack_zone.h
+++ b/cockatrice/src/game_graphics/zones/stack_zone.h
@@ -15,13 +15,9 @@ class StackZone : public SelectZone
Q_OBJECT
private:
qreal zoneHeight;
- bool playmatActive = false;
private slots:
void updateBg();
-public slots:
- void onPlaymatChanged(bool active);
-
public:
StackZone(StackZoneLogic *_logic, int _zoneHeight, QGraphicsItem *parent);
/** @brief Resizes the stack zone height, e.g. when sharing vertical space with the command zone. */
diff --git a/cockatrice/src/game_graphics/zones/table_zone.cpp b/cockatrice/src/game_graphics/zones/table_zone.cpp
index 88e9abe6c..4ef01853f 100644
--- a/cockatrice/src/game_graphics/zones/table_zone.cpp
+++ b/cockatrice/src/game_graphics/zones/table_zone.cpp
@@ -8,7 +8,6 @@
#include "../board/arrow_item.h"
#include "../board/card_drag_item.h"
#include "../board/card_item.h"
-#include "../game_scene.h"
#include "../z_values.h"
#include
@@ -48,31 +47,6 @@ void TableZone::updateBg()
update();
}
-void TableZone::triggerDamageShimmer()
-{
- if (!SettingsCache::instance().userInterface().getBattlefieldFlashEnabled()) {
- damageShimmerAlpha = 0.0;
- return;
- }
-
- damageShimmerAlpha = 1.0;
- shimmerClock.start();
- if (scene()) {
- static_cast(scene())->registerAnimationItem(this);
- }
-}
-
-bool TableZone::animationEvent()
-{
- damageShimmerAlpha = 1.0 - shimmerClock.elapsed() / shimmerDurationMs;
- if (damageShimmerAlpha <= 0.0) {
- damageShimmerAlpha = 0.0;
- return false;
- }
- update();
- return true;
-}
-
QRectF TableZone::boundingRect() const
{
return QRectF(0, 0, width, height);
@@ -92,43 +66,20 @@ bool TableZone::isInverted() const
void TableZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
{
- if (playmatActive) {
- // Subtle overlay to distinguish table zone from stack zone
- painter->fillRect(boundingRect(), QColor(0, 0, 0, 60));
- } else {
- QBrush brush = themeManager->getExtraBgBrush(ThemeManager::Table, getLogic()->getPlayer()->getZoneId());
- painter->fillRect(boundingRect(), brush);
- }
+ QBrush brush = themeManager->getExtraBgBrush(ThemeManager::Table, getLogic()->getPlayer()->getZoneId());
+ painter->fillRect(boundingRect(), brush);
if (active) {
paintZoneOutline(painter);
} else {
// inactive player gets a darker table zone with a semi transparent black mask
- // this means if the user provides a custom background or playmat it will fade
+ // this means if the user provides a custom background it will fade
painter->fillRect(boundingRect(), FADE_MASK);
}
- // Decaying crimson wash from taking damage.
- if (damageShimmerAlpha > 0.0) {
- QColor shimmerColor(239, 68, 68);
- shimmerColor.setAlphaF(0.22 * damageShimmerAlpha);
- painter->fillRect(boundingRect(), shimmerColor);
- }
-
paintLandDivider(painter);
}
-void TableZone::onPlaymatChanged(bool active)
-{
- playmatActive = active;
- // While a playmat is shown the zone paints a translucent overlay over the
- // dynamic playmat behind it. Keep it out of the device cache so the cached
- // pixels are never stale relative to the playmat (and to avoid compositing
- // artifacts of cached translucent content on some platforms).
- setCacheMode(active ? QGraphicsItem::NoCache : QGraphicsItem::DeviceCoordinateCache);
- update();
-}
-
/**
Render a soft outline around the edge of the TableZone.
diff --git a/cockatrice/src/game_graphics/zones/table_zone.h b/cockatrice/src/game_graphics/zones/table_zone.h
index 92915a2ed..0d7e58206 100644
--- a/cockatrice/src/game_graphics/zones/table_zone.h
+++ b/cockatrice/src/game_graphics/zones/table_zone.h
@@ -8,19 +8,16 @@
#define TABLEZONE_H
#include "../../game/zones/table_zone_logic.h"
-#include "../animated_item.h"
#include "../board/abstract_card_item.h"
#include "select_zone.h"
-#include
-
/**
* @brief TableZone is the grid based rect where CardItems may be placed.
*
* It is the main play zone and can be customized with background images.
*/
//! \todo Refactor methods to make more readable, extract logic to private methods (especially reorganizeCards()).
-class TableZone : public SelectZone, public IAnimatedItem
+class TableZone : public SelectZone
{
Q_OBJECT
@@ -86,7 +83,6 @@ private:
*/
bool active = false;
bool mirrored = false;
- bool playmatActive = false;
[[nodiscard]] bool isInverted() const;
@@ -96,9 +92,6 @@ private slots:
*/
void updateBg();
-public slots:
- void onPlaymatChanged(bool active);
-
public slots:
/**
Reorganizes CardItems in the TableZone
@@ -128,16 +121,6 @@ public:
*/
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
- /**
- Flashes the table surface after a player loses life.
-
- Wired up through the life counter so the battlefield glows when life drops.
- */
- void triggerDamageShimmer();
-
- /** @brief Decays the damage shimmer by one timer tick. */
- bool animationEvent() override;
-
/**
Toggles the selected items as tapped.
*/
@@ -188,17 +171,8 @@ public:
}
void setWidth(qreal _width)
{
- // The width is stored as an int; truncate to match the previous implicit conversion.
- const int newWidth = static_cast(_width);
- if (width == newWidth) {
- return;
- }
prepareGeometryChange();
- width = newWidth;
- // The parent player item's boundingRect (which clips the playmat painting) is
- // derived from this zone's size. Without this signal the playmat is cut off at
- // the stale boundingRect edge whenever the scene is resized wider.
- emit sizeChanged();
+ width = _width;
}
[[nodiscard]] qreal getWidth() const
{
@@ -211,11 +185,6 @@ public:
}
private:
- static constexpr qreal shimmerDurationMs = 450.0;
-
- QElapsedTimer shimmerClock;
- qreal damageShimmerAlpha = 0.0;
-
void paintZoneOutline(QPainter *painter);
void paintLandDivider(QPainter *painter);
diff --git a/cockatrice/src/interface/intents/contexts/context_join_game.h b/cockatrice/src/interface/intents/contexts/context_join_game.h
index 2e5a88ea2..102e2a520 100644
--- a/cockatrice/src/interface/intents/contexts/context_join_game.h
+++ b/cockatrice/src/interface/intents/contexts/context_join_game.h
@@ -6,7 +6,6 @@ struct ContextJoinGame
{
ContextJoinRoom roomContext;
int gameId;
- bool asSpectator = false;
};
#endif // COCKATRICE_CONTEXT_JOIN_GAME_H
diff --git a/cockatrice/src/interface/intents/intent_join_server_game.cpp b/cockatrice/src/interface/intents/intent_join_server_game.cpp
index 205c4dc70..fb9c4d5ce 100644
--- a/cockatrice/src/interface/intents/intent_join_server_game.cpp
+++ b/cockatrice/src/interface/intents/intent_join_server_game.cpp
@@ -55,7 +55,7 @@ bool IntentJoinServerGame::tryJoinGame(TabRoom *room)
return false;
}
- if (room->getGameSelector()->joinGameById(context->gameId, context->asSpectator)) {
+ if (room->getGameSelector()->joinGameById(context->gameId)) {
emitFinished();
return true;
}
diff --git a/cockatrice/src/interface/intents/intent_open_server_room_by_name.cpp b/cockatrice/src/interface/intents/intent_open_server_room_by_name.cpp
deleted file mode 100644
index d50f509a0..000000000
--- a/cockatrice/src/interface/intents/intent_open_server_room_by_name.cpp
+++ /dev/null
@@ -1,183 +0,0 @@
-#include "intent_open_server_room_by_name.h"
-
-#include "../widgets/tabs/tab_room.h"
-#include "../widgets/tabs/tab_supervisor.h"
-#include "intent_connect_to_server.h"
-
-#include
-#include
-#include
-#include
-
-IntentOpenServerRoomByName::IntentOpenServerRoomByName(TabSupervisor *_tabSupervisor,
- RemoteClient *_remoteClient,
- std::unique_ptr _context,
- const QString &_roomName)
- : Intent(), tabSupervisor(_tabSupervisor), remoteClient(_remoteClient), context(_context.release()),
- roomName(_roomName)
-{
- checkTimer.setInterval(250);
- connect(&checkTimer, &QTimer::timeout, this, [this]() {
- if (selectOpenRoom()) {
- checkTimer.stop();
- }
- });
-}
-
-bool IntentOpenServerRoomByName::checkPrecondition() const
-{
- if (remoteClient->getStatus() != ClientStatus::StatusLoggedIn) {
- return false;
- }
- // peerPort() reflects the actual TCP peer, which may differ from the
- // configured server port (e.g. when connecting through a proxy), so only
- // the hostname is compared here.
- if (remoteClient->peerName() != context->serverContext.hostname) {
- return false;
- }
- if (QString::number(remoteClient->peerPort()) != context->serverContext.port) {
- return false;
- }
-
- return true;
-}
-
-void IntentOpenServerRoomByName::onPreconditionSatisfied()
-{
- if (listening) {
- return;
- }
- listening = true;
-
- if (selectOpenRoom()) {
- return;
- }
-
- // The room selector is the component that requests the room list, so the
- // server tab must exist for the room to be resolved by name.
- if (!tabSupervisor->getTabServer()) {
- tabSupervisor->openTabServer();
- }
- if (!tabSupervisor->getTabServer()) {
- emitFailed(tr("No server tab available"));
- return;
- }
-
- connect(remoteClient, &RemoteClient::listRoomsEventReceived, this, &IntentOpenServerRoomByName::processListRooms);
- connect(remoteClient, &RemoteClient::statusChanged, this, &IntentOpenServerRoomByName::onClientStatusChanged);
-
- // The room tab may be opened by our own join, by the room selector's auto-join, or by a
- // join that was already in flight. Poll until it shows up.
- checkTimer.start();
-
- // While no join has been sent yet, keep the room list fresh: the list may have been
- // requested before we subscribed to it, or a response may have been dropped during a
- // busy login burst. A stale list would otherwise leave the room unresolved forever.
- connect(&refreshTimer, &QTimer::timeout, this, [this]() {
- if (!joinPending) {
- remoteClient->sendCommand(remoteClient->prepareSessionCommand(Command_ListRooms()));
- }
- });
- refreshTimer.setInterval(5000);
- refreshTimer.start();
-
- // Last-resort failure for "the room genuinely is not in a fresh list". This must NOT
- // fire while a join is in flight: a loaded server may take longer than that to answer
- // during a login burst, and killing the intent early would leave the connection
- // registered in the room with no tab to display it and every later join attempt
- // would then be rejected with RespContextError.
- QTimer::singleShot(20000, this, [this]() {
- if (!joinPending) {
- emitFailed(tr("Timed out while looking for the server room %1").arg(roomName));
- }
- });
-}
-
-void IntentOpenServerRoomByName::onPreconditionNotSatisfied()
-{
- runDependency(new IntentConnectToServer(remoteClient, &context->serverContext));
-}
-
-void IntentOpenServerRoomByName::onClientStatusChanged(ClientStatus status)
-{
- if (status != ClientStatus::StatusLoggedIn) {
- emitFailed(tr("Disconnected while looking for the server room %1").arg(roomName));
- }
-}
-
-bool IntentOpenServerRoomByName::selectOpenRoom()
-{
- const auto &roomTabs = tabSupervisor->getRoomTabs();
- for (auto i = roomTabs.cbegin(), end = roomTabs.cend(); i != end; ++i) {
- TabRoom *room = i.value();
- if (room->getRoomName() == roomName) {
- tabSupervisor->setCurrentWidget(room);
- emitFinished();
- return true;
- }
- }
- return false;
-}
-
-void IntentOpenServerRoomByName::processListRooms(const Event_ListRooms &event)
-{
- if (selectOpenRoom()) {
- return;
- }
-
- for (int i = 0; i < event.room_list_size(); ++i) {
- const ServerInfo_Room &room = event.room_list(i);
- if (room.has_name() && QString::fromStdString(room.name()) == roomName) {
- openRoom(room);
- return;
- }
- }
-}
-
-void IntentOpenServerRoomByName::openRoom(const ServerInfo_Room &roomInfo)
-{
- if (joinPending) {
- return;
- }
- joinPending = true;
-
- // Rooms flagged auto_join are joined by the room selector automatically. Sending our own
- // Command_JoinRoom on top of that would be answered with RespContextError.
- if (roomInfo.has_auto_join() && roomInfo.auto_join()) {
- return;
- }
-
- Command_JoinRoom cmd;
- cmd.set_room_id(roomInfo.room_id());
- PendingCommand *pend = remoteClient->prepareSessionCommand(cmd);
- connect(pend, &PendingCommand::finished, this,
- [this](const Response &r, const CommandContainer &, const QVariant &) { handleJoinResponse(r); });
- remoteClient->sendCommand(pend);
-}
-
-void IntentOpenServerRoomByName::handleJoinResponse(const Response &response)
-{
- switch (response.response_code()) {
- case Response::RespOk: {
- const Response_JoinRoom &resp = response.GetExtension(Response_JoinRoom::ext);
- if (!tabSupervisor->getRoomTabs().contains(resp.room_info().room_id())) {
- tabSupervisor->addRoomTab(resp.room_info(), true);
- }
- emitFinished();
- return;
- }
- case Response::RespNameNotFound:
- emitFailed(tr("Failed to join the server room %1: it doesn't exist on the server.").arg(roomName));
- return;
- case Response::RespUserLevelTooLow:
- emitFailed(tr("You do not have the required permission to join the server room %1.").arg(roomName));
- return;
- case Response::RespContextError:
- // The room was already joined by someone else (e.g. the room selector's
- // auto-join). It will show up in the room tabs shortly, so keep waiting.
- return;
- default:
- emitFailed(tr("Failed to join the server room %1 due to an unknown error.").arg(roomName));
- return;
- }
-}
diff --git a/cockatrice/src/interface/intents/intent_open_server_room_by_name.h b/cockatrice/src/interface/intents/intent_open_server_room_by_name.h
deleted file mode 100644
index 2f9e716af..000000000
--- a/cockatrice/src/interface/intents/intent_open_server_room_by_name.h
+++ /dev/null
@@ -1,61 +0,0 @@
-#ifndef COCKATRICE_INTENT_OPEN_SERVER_ROOM_BY_NAME_H
-#define COCKATRICE_INTENT_OPEN_SERVER_ROOM_BY_NAME_H
-
-#include "contexts/context_join_room.h"
-#include "intent.h"
-#include "remote_client.h"
-
-#include
-#include
-#include
-#include
-
-class TabRoom;
-class TabSupervisor;
-class Event_ListRooms;
-class ServerInfo_Room;
-
-/**
- * @brief Connects to the configured server and opens a room identified by its name.
- *
- * Room ids are assigned by the server per session, so the room is resolved by name from the
- * room list once the client is logged in. If the room is already open it is simply selected.
- *
- * The join itself is sent directly through the client instead of `TabServer::joinRoom`, so a
- * failed join only fails the intent silently instead of popping a modal error box during
- * startup. Success is routed to `TabSupervisor::addRoomTab`, the same tab-creation machinery
- * the normal join flow uses.
- */
-class IntentOpenServerRoomByName : public Intent
-{
- Q_OBJECT
-
-public:
- IntentOpenServerRoomByName(TabSupervisor *_tabSupervisor,
- RemoteClient *_remoteClient,
- std::unique_ptr _context,
- const QString &_roomName);
-
-protected:
- bool checkPrecondition() const override;
- void onPreconditionSatisfied() override;
- void onPreconditionNotSatisfied() override;
-
-private:
- void processListRooms(const Event_ListRooms &event);
- void openRoom(const ServerInfo_Room &roomInfo);
- void handleJoinResponse(const Response &response);
- void onClientStatusChanged(ClientStatus status);
- bool selectOpenRoom();
-
- TabSupervisor *tabSupervisor;
- RemoteClient *remoteClient;
- QScopedPointer context;
- QString roomName;
- bool listening = false;
- bool joinPending = false;
- QTimer checkTimer;
- QTimer refreshTimer;
-};
-
-#endif // COCKATRICE_INTENT_OPEN_SERVER_ROOM_BY_NAME_H
diff --git a/cockatrice/src/interface/intents/url_parser.cpp b/cockatrice/src/interface/intents/url_parser.cpp
index 509390611..8b5309603 100644
--- a/cockatrice/src/interface/intents/url_parser.cpp
+++ b/cockatrice/src/interface/intents/url_parser.cpp
@@ -1,7 +1,5 @@
#include "url_parser.h"
-#include "../widgets/tabs/tab_room.h"
-#include "../widgets/tabs/tab_supervisor.h"
#include "../window_main.h"
#include "contexts/context_join_game.h"
#include "intent_join_server_game.h"
@@ -11,7 +9,6 @@
#include
#include
#include
-#include
#include
IntentUrlParser::IntentUrlParser(QObject *parent, MainWindow *_mainWindow) : QObject(parent), mainWindow(_mainWindow)
@@ -74,15 +71,6 @@ void IntentUrlParser::handleJoinGame(const QUrlQuery &query)
return;
}
- const QString gameDescription = query.queryItemValue("game", QUrl::FullyDecoded);
- const QString message = generateJoinGameMessage(*ctx, gameDescription);
-
- const QMessageBox::StandardButton answer = QMessageBox::question(
- mainWindow, tr("Join game"), message, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
- if (answer != QMessageBox::Yes) {
- return;
- }
-
// The join game intent owns the context and the credential lookup; once the
// chain finishes (or fails) it deletes the whole tree.
ContextConnectToServer *serverContext = &ctx->roomContext.serverContext;
@@ -99,38 +87,3 @@ void IntentUrlParser::handleJoinGame(const QUrlQuery &query)
getLoginCredentialsIntent->execute();
}
-
-QString IntentUrlParser::generateJoinGameMessage(const ContextJoinGame &context, const QString &gameDescription)
-{
- const QString hostname = context.roomContext.serverContext.hostname;
- const QString port = context.roomContext.serverContext.port;
- const int roomId = context.roomContext.roomId;
- const int gameId = context.gameId;
- const QString server = QStringLiteral("%1:%2").arg(hostname, port);
-
- // Prefer the room name over the raw numeric id: it means something to the
- // user. The name is only known when we are already connected to the same
- // server and sitting in that room — otherwise fall back to a plain prompt.
- AbstractClient *client = mainWindow->getTabSupervisor()->getClient();
- const bool sameServer = client != nullptr && client->getStatus() == StatusLoggedIn &&
- hostname.compare(client->serverName(), Qt::CaseInsensitive) == 0 &&
- QString::number(client->serverPort()) == port;
- TabRoom *roomTab = sameServer ? mainWindow->getTabSupervisor()->getRoomTabs().value(roomId) : nullptr;
-
- const QString gameIdStr = QString::number(gameId);
- // Links built by newer clients embed the game description ("game" item);
- // restate it in the confirm so it matches what the chat anchor showed.
- // Unknown query items are ignored, so old links without it keep working.
- // The multi-arg .arg() overloads replace in a single pass, so a description
- // containing "%…" cannot corrupt later placeholders.
- // FullyDecoded undoes every %XX escape and must match the chat anchor's
- // decode mode, so a description containing "%" reads identically in both.
- if (gameDescription.isEmpty()) {
- return roomTab ? tr("Join game #%1 in \"%2\" on %3?").arg(gameIdStr, roomTab->getRoomName(), server)
- : tr("Join game #%1 on %2?").arg(gameIdStr, server);
- }
-
- return roomTab ? tr("Join game \"%1\" (#%2) in \"%3\" on %4?")
- .arg(gameDescription, gameIdStr, roomTab->getRoomName(), server)
- : tr("Join game \"%1\" (#%2) on %3?").arg(gameDescription, gameIdStr, server);
-}
diff --git a/cockatrice/src/interface/intents/url_parser.h b/cockatrice/src/interface/intents/url_parser.h
index 6d705e013..bac0e3d25 100644
--- a/cockatrice/src/interface/intents/url_parser.h
+++ b/cockatrice/src/interface/intents/url_parser.h
@@ -4,7 +4,6 @@
#include
class MainWindow;
-struct ContextJoinGame;
class IntentUrlParser : public QObject
{
Q_OBJECT
@@ -15,8 +14,6 @@ public:
void handleJoinGame(const QUrlQuery &query);
private:
- QString generateJoinGameMessage(const ContextJoinGame &context, const QString &gameDescription);
-
MainWindow *mainWindow;
};
diff --git a/cockatrice/src/interface/palette_editor/palette_editor_dialog.cpp b/cockatrice/src/interface/palette_editor/palette_editor_dialog.cpp
index 9cde72c01..adae6e152 100644
--- a/cockatrice/src/interface/palette_editor/palette_editor_dialog.cpp
+++ b/cockatrice/src/interface/palette_editor/palette_editor_dialog.cpp
@@ -290,35 +290,17 @@ void PaletteEditorDialog::onSave()
// Persist every scheme that changed, not just the one on screen. Each scheme
// has its own file, so edits to the non-active scheme would otherwise be
// silently discarded when the dialog closes.
- //
- // Save the loaded scheme last so commitPalette's global colour-scheme
- // update (ThemeConfig::colorScheme) points at the active scheme.
for (auto it = workingConfig.begin(); it != workingConfig.end(); ++it) {
- if (it.key() == loadedScheme) {
- continue;
+ const QString &scheme = it.key();
+ if (it.value().colors == savedConfig.value(scheme).colors) {
+ continue; // unchanged — leave the on-disk file alone
}
- if (it.value().colors == savedConfig.value(it.key()).colors) {
- continue;
- }
- if (!ThemeManager::commitPalette(saveDir, it.key(), it.value())) {
- QMessageBox::warning(this, tr("Save failed"),
- tr("Could not write %1 to:\n%2").arg(PaletteConfig::fileName(it.key()), saveDir));
- return;
- }
- }
- // Commit the active scheme last so the global colour scheme matches.
- if (workingConfig[loadedScheme].colors != savedConfig.value(loadedScheme).colors) {
- if (!ThemeManager::commitPalette(saveDir, loadedScheme, workingConfig[loadedScheme])) {
+ if (!ThemeManager::savePaletteConfig(saveDir, scheme, it.value())) {
QMessageBox::warning(this, tr("Save failed"),
- tr("Could not write %1 to:\n%2").arg(PaletteConfig::fileName(loadedScheme), saveDir));
+ tr("Could not write %1 to:\n%2").arg(PaletteConfig::fileName(scheme), saveDir));
return;
}
- } else {
- // No palette change but scheme may have switched -- still update global config.
- ThemeConfig globalCfg = ThemeConfig::fromThemeDir(saveDir);
- globalCfg.colorScheme = loadedScheme;
- globalCfg.save(saveDir);
}
// Keep the saved snapshot in sync so Reset behaves correctly afterwards.
@@ -326,6 +308,10 @@ void PaletteEditorDialog::onSave()
savedConfig[it.key()] = it.value();
}
+ ThemeConfig globalCfg = ThemeConfig::fromThemeDir(saveDir);
+ globalCfg.colorScheme = loadedScheme;
+ globalCfg.save(saveDir);
+
themeManager->reloadCurrentTheme();
accept();
}
diff --git a/cockatrice/src/interface/pixel_map_generator.cpp b/cockatrice/src/interface/pixel_map_generator.cpp
index 9b8c4bcdc..d3b0252a6 100644
--- a/cockatrice/src/interface/pixel_map_generator.cpp
+++ b/cockatrice/src/interface/pixel_map_generator.cpp
@@ -3,7 +3,6 @@
#include
#include
#include
-#include
#include
#include
#include
@@ -15,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.
*
@@ -62,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);
@@ -277,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);
@@ -419,57 +387,6 @@ QPixmap DropdownIconPixmapGenerator::generatePixmap(int height, bool expanded)
QMap DropdownIconPixmapGenerator::pmCache;
-namespace
-{
-/// Longest side mana symbols are rendered at before being scaled to their final size.
-constexpr int MASTER_ICON_SIZE = 128;
-
-QString manaSymbolCacheKey(const QString &symbol, const QSize &size)
-{
- return symbol + QLatin1Char('|') + QString::number(size.width()) + QLatin1Char('x') +
- QString::number(size.height());
-}
-} // namespace
-
-const QPixmap &ManaSymbolPixmapGenerator::masterIcon(const QString &symbol)
-{
- auto it = masterCache.constFind(symbol);
- if (it != masterCache.constEnd()) {
- return it.value();
- }
-
- QImageReader reader("theme:icons/mana/" + symbol);
- QSize sourceSize = reader.size();
- if (!sourceSize.isEmpty()) {
- sourceSize.scale(QSize(MASTER_ICON_SIZE, MASTER_ICON_SIZE), Qt::KeepAspectRatio);
- reader.setScaledSize(sourceSize);
- }
- const QPixmap rendered = QPixmap::fromImageReader(&reader);
-
- return masterCache.insert(symbol, rendered).value();
-}
-
-QPixmap ManaSymbolPixmapGenerator::generatePixmap(const QString &symbol, const QSize &size)
-{
- const QString key = manaSymbolCacheKey(symbol, size);
- auto it = scaledCache.constFind(key);
- if (it != scaledCache.constEnd()) {
- return it.value();
- }
-
- const QPixmap &icon = masterIcon(symbol);
- if (icon.isNull()) {
- return {};
- }
-
- QPixmap scaled = icon.scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
- scaledCache.insert(key, scaled);
- return scaled;
-}
-
-QHash ManaSymbolPixmapGenerator::masterCache;
-QHash ManaSymbolPixmapGenerator::scaledCache;
-
QPixmap loadColorAdjustedPixmap(const QString &name)
{
if (qApp->palette().windowText().color().lightness() > 200) {
diff --git a/cockatrice/src/interface/pixel_map_generator.h b/cockatrice/src/interface/pixel_map_generator.h
index 17720166a..22f44d8db 100644
--- a/cockatrice/src/interface/pixel_map_generator.h
+++ b/cockatrice/src/interface/pixel_map_generator.h
@@ -7,7 +7,6 @@
#ifndef PIXMAPGENERATOR_H
#define PIXMAPGENERATOR_H
-#include
#include
#include
#include
@@ -126,34 +125,6 @@ public:
}
};
-class ManaSymbolPixmapGenerator
-{
-private:
- static QHash masterCache;
- static QHash scaledCache;
-
- /**
- * @brief Renders \a symbol once at a fixed moderate size, so repeated scalings never
- * re-rasterize the source file (SVG sources can be very expensive to rasterize).
- */
- static const QPixmap &masterIcon(const QString &symbol);
-
-public:
- /**
- * @brief Returns a smooth-scaled rendering of the given mana symbol icon.
- *
- * Results are shared between all callers via a process-wide cache keyed by symbol
- * and size, so scaling work is done once per distinct combination instead of once
- * per widget creation or resize.
- */
- static QPixmap generatePixmap(const QString &symbol, const QSize &size);
- static void clear()
- {
- masterCache.clear();
- scaledCache.clear();
- }
-};
-
QPixmap loadColorAdjustedPixmap(const QString &name);
#endif
diff --git a/cockatrice/src/interface/theme_manager.cpp b/cockatrice/src/interface/theme_manager.cpp
index e6b4b3c7f..518a97bc6 100644
--- a/cockatrice/src/interface/theme_manager.cpp
+++ b/cockatrice/src/interface/theme_manager.cpp
@@ -90,17 +90,13 @@ struct PaletteColorInfo
}
}
-static QString usableDefaultStyle(const QString &style)
-{
- // The Windows 11 native style is broken: when the OS default
- // ("Default" theme selection) would use it, fall back to the Vista style.
- // Explicitly choosing "windows11" in a theme is still honored.
- return style.compare("windows11", Qt::CaseInsensitive) == 0 ? QStringLiteral("windowsvista") : style;
-}
-
ThemeManager::ThemeManager(QObject *parent) : QObject(parent)
{
- defaultStyleName = usableDefaultStyle(qApp->style()->objectName());
+ defaultStyleName = qApp->style()->objectName();
+ //! \todo Workaround for windows11 style being broken.
+ if (defaultStyleName == "windows11") {
+ defaultStyleName = "windowsvista";
+ }
// Capture the untouched application palette before any theme is applied.
defaultPalette = qApp->palette();
ensureThemeDirectoryExists();
@@ -123,7 +119,7 @@ void ThemeManager::ensureThemeDirectoryExists()
}
}
-bool ThemeManager::isDarkMode(const QString &themeDirPath) const
+bool ThemeManager::isDarkMode(const QString &themeDirPath)
{
ThemeConfig themeConfig = ThemeConfig::fromThemeDir(themeDirPath);
if (themeConfig.colorScheme.compare("Dark", Qt::CaseInsensitive) == 0) {
@@ -272,19 +268,6 @@ PaletteConfig ThemeManager::loadDefaultPaletteConfig(const QString &themeDirPath
return cfg;
}
-bool ThemeManager::commitPalette(const QString &themeDirPath, const QString &colorScheme, const PaletteConfig &cfg)
-{
- if (!savePaletteConfig(themeDirPath, colorScheme, cfg)) {
- return false;
- }
-
- ThemeConfig globalCfg = ThemeConfig::fromThemeDir(themeDirPath);
- globalCfg.colorScheme = colorScheme;
- globalCfg.save(themeDirPath);
-
- return true;
-}
-
void ThemeManager::setColorScheme(const QString &scheme)
{
const QString dirPath = getAvailableThemes().value(SettingsCache::instance().getThemeName());
@@ -333,13 +316,13 @@ void ThemeManager::applyStyleAndPalette(const QString &themeName,
if (themeName == FUSION_THEME_NAME) {
styleName = "Fusion";
} else {
- styleName = usableDefaultStyle(defaultStyleName);
+ styleName = defaultStyleName;
}
}
QStyle *style = QStyleFactory::create(styleName);
if (!style) {
- style = QStyleFactory::create(usableDefaultStyle(defaultStyleName));
+ style = QStyleFactory::create(defaultStyleName);
}
// Base palette
diff --git a/cockatrice/src/interface/theme_manager.h b/cockatrice/src/interface/theme_manager.h
index 79a1b6470..861ab838b 100644
--- a/cockatrice/src/interface/theme_manager.h
+++ b/cockatrice/src/interface/theme_manager.h
@@ -66,14 +66,7 @@ protected:
public:
bool isBuiltInTheme();
- // Explicit color scheme of the theme: theme.cfg's ColorScheme setting
- // (Dark/Light), falling back to the OS color scheme when it is "System".
- bool isDarkMode(const QString &themeDirPath) const;
- // The resolved scheme of the currently active theme.
- bool isDarkModeActive() const
- {
- return isDarkMode(currentThemePath);
- }
+ bool isDarkMode(const QString &themeDirPath);
QStringMap &getAvailableThemes();
// Returns the path to the currently active theme directory (empty = default)
QString getCurrentThemePath() const
@@ -91,10 +84,6 @@ public:
// theme directory when it is absent from the resolved (user) directory.
static PaletteConfig
loadDefaultPaletteConfig(const QString &themeDirPath, const QString &themeName, const QString &colorScheme);
- /** @brief Writes cfg to disk as the theme's palette-.toml and updates the
- * theme's stored colour scheme to match. Shared by PaletteEditorDialog::onSave
- * and FirstRunWizard's theme step so the two "generate + keep" paths can't drift. */
- static bool commitPalette(const QString &themeDirPath, const QString &colorScheme, const PaletteConfig &cfg);
void setColorScheme(const QString &scheme);
void setStyleName(const QString &styleName);
diff --git a/cockatrice/src/interface/widgets/cards/additional_info/color_identity_widget.cpp b/cockatrice/src/interface/widgets/cards/additional_info/color_identity_widget.cpp
index 1ea1bcb10..3f0f30a27 100644
--- a/cockatrice/src/interface/widgets/cards/additional_info/color_identity_widget.cpp
+++ b/cockatrice/src/interface/widgets/cards/additional_info/color_identity_widget.cpp
@@ -41,11 +41,6 @@ void ColorIdentityWidget::populateManaSymbolWidgets()
// clear old layout
QtUtils::clearLayoutRec(layout);
- // The freshly created symbols haven't been sized yet, so force the next resize pass
- // to apply the symbol size again.
- lastIconSize = -1;
- lastWidth = -1;
-
// populate mana symbols
if (SettingsCache::instance().visualDeckStorage().getVisualDeckStorageDrawUnusedColorIdentities()) {
for (const QString symbol : fullColorIdentity) {
@@ -78,33 +73,20 @@ void ColorIdentityWidget::toggleUnusedVisibility()
void ColorIdentityWidget::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
+ QList manaSymbols = findChildren();
- const int totalWidth = event->size().width();
- if (totalWidth == lastWidth && lastIconSize != -1) {
- return;
- }
- lastWidth = totalWidth;
+ if (!manaSymbols.isEmpty()) {
+ int totalWidth = event->size().width();
+ int totalHeight = totalWidth / 6; // Set height to 1/4 of the width
+ setFixedHeight(totalHeight);
- const int totalHeight = totalWidth / 6; // Set height to 1/4 of the width
- setFixedHeight(totalHeight);
+ int spacing = layout->spacing();
+ int count = manaSymbols.size();
+ int availableWidth = totalWidth - (spacing * (count - 1));
+ int iconSize = qMin(availableWidth / count, totalHeight); // Ensure icons fit within the new height
- const int count = layout->count();
- if (count == 0) {
- return;
- }
-
- const int spacing = layout->spacing();
- const int availableWidth = totalWidth - (spacing * (count - 1));
- const int iconSize = qMin(availableWidth / count, totalHeight); // Ensure icons fit within the new height
-
- if (iconSize == lastIconSize) {
- return;
- }
- lastIconSize = iconSize;
-
- for (int i = 0; i < count; ++i) {
- if (auto *w = qobject_cast(layout->itemAt(i)->widget())) {
- w->setFixedSize(iconSize, iconSize);
+ for (ManaSymbolWidget *manaSymbol : manaSymbols) {
+ manaSymbol->setFixedSize(iconSize, iconSize);
}
}
}
diff --git a/cockatrice/src/interface/widgets/cards/additional_info/color_identity_widget.h b/cockatrice/src/interface/widgets/cards/additional_info/color_identity_widget.h
index 315ac07d6..f776d4c77 100644
--- a/cockatrice/src/interface/widgets/cards/additional_info/color_identity_widget.h
+++ b/cockatrice/src/interface/widgets/cards/additional_info/color_identity_widget.h
@@ -30,8 +30,6 @@ public slots:
private:
QString colorIdentity;
QHBoxLayout *layout;
- int lastIconSize = -1; ///< The symbol size last applied, to skip redundant resize passes.
- int lastWidth = -1; ///< The width last processed, to skip redundant resize passes.
};
#endif // COLOR_IDENTITY_WIDGET_H
diff --git a/cockatrice/src/interface/widgets/cards/additional_info/mana_cost_widget.h b/cockatrice/src/interface/widgets/cards/additional_info/mana_cost_widget.h
index 8bd2e9bc0..b2f6b62c0 100644
--- a/cockatrice/src/interface/widgets/cards/additional_info/mana_cost_widget.h
+++ b/cockatrice/src/interface/widgets/cards/additional_info/mana_cost_widget.h
@@ -17,7 +17,7 @@ class ManaCostWidget : public QWidget
public:
explicit ManaCostWidget(QWidget *parent, CardInfoPtr card);
- static QStringList parseManaCost(const QString &manaString);
+ QStringList parseManaCost(const QString &manaString);
public slots:
void resizeEvent(QResizeEvent *event) override;
diff --git a/cockatrice/src/interface/widgets/cards/additional_info/mana_symbol_widget.cpp b/cockatrice/src/interface/widgets/cards/additional_info/mana_symbol_widget.cpp
index 18011909f..51247da7a 100644
--- a/cockatrice/src/interface/widgets/cards/additional_info/mana_symbol_widget.cpp
+++ b/cockatrice/src/interface/widgets/cards/additional_info/mana_symbol_widget.cpp
@@ -1,15 +1,15 @@
#include "mana_symbol_widget.h"
#include "../../../../client/settings/cache_settings.h"
-#include "../../../pixel_map_generator.h"
#include
#include
ManaSymbolWidget::ManaSymbolWidget(QWidget *parent, QString _symbol, bool _isActive, bool _mayBeToggled)
- : QLabel(parent), symbol(std::move(_symbol)), isActive(_isActive), mayBeToggled(_mayBeToggled)
+ : QLabel(parent), symbol(_symbol), isActive(_isActive), mayBeToggled(_mayBeToggled)
{
- setPixmap(ManaSymbolPixmapGenerator::generatePixmap(symbol, QSize(50, 50)));
+ loadManaIcon();
+ setPixmap(manaIcon.scaled(50, 50, Qt::KeepAspectRatio, Qt::SmoothTransformation));
setMaximumWidth(50);
// Initialize opacity effect
@@ -64,13 +64,16 @@ void ManaSymbolWidget::mousePressEvent(QMouseEvent *event)
void ManaSymbolWidget::resizeEvent(QResizeEvent *event)
{
QLabel::resizeEvent(event);
- const QSize newSize = event->size();
+ setPixmap(manaIcon.scaled(event->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
+}
- // Skip the rescale when the size didn't actually change: layout passes resize these
- // widgets repeatedly with identical sizes.
- if (newSize.isEmpty() || pixmap().size() == newSize) {
- return;
+void ManaSymbolWidget::loadManaIcon()
+{
+ QString filename = "theme:icons/mana/";
+
+ if (symbol == "W" || symbol == "U" || symbol == "B" || symbol == "R" || symbol == "G") {
+ filename += symbol;
}
- setPixmap(ManaSymbolPixmapGenerator::generatePixmap(symbol, newSize));
+ manaIcon = QPixmap(filename);
}
diff --git a/cockatrice/src/interface/widgets/cards/additional_info/mana_symbol_widget.h b/cockatrice/src/interface/widgets/cards/additional_info/mana_symbol_widget.h
index 705873dff..0f2d7acd1 100644
--- a/cockatrice/src/interface/widgets/cards/additional_info/mana_symbol_widget.h
+++ b/cockatrice/src/interface/widgets/cards/additional_info/mana_symbol_widget.h
@@ -33,6 +33,8 @@ public:
return symbol[0];
}
+ void loadManaIcon();
+
public slots:
void resizeEvent(QResizeEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
@@ -42,6 +44,7 @@ signals:
private:
QString symbol;
+ QPixmap manaIcon;
bool isActive;
bool mayBeToggled;
QGraphicsOpacityEffect *opacityEffect;
diff --git a/cockatrice/src/interface/widgets/cards/art_crop_attribution.cpp b/cockatrice/src/interface/widgets/cards/art_crop_attribution.cpp
deleted file mode 100644
index 8ab18ddd9..000000000
--- a/cockatrice/src/interface/widgets/cards/art_crop_attribution.cpp
+++ /dev/null
@@ -1,61 +0,0 @@
-#include "art_crop_attribution.h"
-
-#include
-#include
-#include
-#include
-
-QString buildArtAttribution(const ExactCard &card)
-{
- const QString artist = card.getPrinting().getArtist();
- if (artist.isEmpty()) {
- return QString();
- }
- return QObject::tr("Art: %1").arg(artist);
-}
-
-QRectF paintArtAttribution(QPainter &painter,
- const QRectF &rect,
- const QString &attribution,
- Qt::Alignment anchor,
- qreal scale)
-{
- if (attribution.isEmpty()) {
- return QRectF();
- }
-
- painter.save();
-
- QFont font = painter.font();
- font.setPointSizeF(qMax(6.0, font.pointSizeF() * scale));
- painter.setFont(font);
-
- const QFontMetrics fm(font);
- const qreal maxTextWidth = rect.width() * 0.45;
- const QString elided = fm.elidedText(attribution, Qt::ElideRight, qMax(qreal(80.0) * scale, maxTextWidth));
-
- const qreal pad = 6.0 * scale;
- QRectF captionRect(QPointF(0, 0), QSizeF(fm.horizontalAdvance(elided) + pad * 2.0, fm.height() + pad * 2.0));
- const qreal margin = 4.0 * scale;
- if (anchor.testFlag(Qt::AlignLeft)) {
- captionRect.moveLeft(rect.left() + margin);
- } else {
- captionRect.moveRight(rect.right() - margin);
- }
- if (anchor.testFlag(Qt::AlignTop)) {
- captionRect.moveTop(rect.top() + margin);
- } else {
- captionRect.moveBottom(rect.bottom() - margin);
- }
-
- painter.setPen(Qt::NoPen);
- painter.setBrush(QColor(0, 0, 0, 120));
- painter.drawRoundedRect(captionRect, 4, 4);
-
- painter.setPen(QColor(255, 255, 255, 220));
- painter.drawText(captionRect, Qt::AlignCenter, elided);
-
- painter.restore();
-
- return captionRect;
-}
diff --git a/cockatrice/src/interface/widgets/cards/art_crop_attribution.h b/cockatrice/src/interface/widgets/cards/art_crop_attribution.h
deleted file mode 100644
index 0b3fb3d56..000000000
--- a/cockatrice/src/interface/widgets/cards/art_crop_attribution.h
+++ /dev/null
@@ -1,41 +0,0 @@
-#ifndef COCKATRICE_ART_CROP_ATTRIBUTION_H
-#define COCKATRICE_ART_CROP_ATTRIBUTION_H
-
-#include
-
-class ExactCard;
-class QPainter;
-class QRectF;
-class QString;
-
-/**
- * @brief Builds an attribution caption for a cropped card art display.
- *
- * When a card image's art region is shown cropped (an "art crop"), the artist
- * should be credited in the same interface. Returns an empty string when the
- * database has no artist data for the card.
- *
- * @param card The card whose art is being displayed.
- * @return Caption such as "Art: John Avon", or empty.
- */
-QString buildArtAttribution(const ExactCard &card);
-
-/**
- * @brief Paints an attribution caption in a corner of a rect.
- *
- * Draws a subtle semi-transparent pill containing the caption, elided to fit.
- *
- * @param painter Painter to draw with.
- * @param rect The area (e.g. the cropped art region) the caption belongs to.
- * @param attribution Caption text (see buildArtAttribution()).
- * @param anchor Corner of @p rect to pin the pill to (default bottom-right).
- * @param scale Size multiplier for the pill (e.g. 0.8 for a smaller pill).
- * @return The rect the pill was drawn in, or an empty rect if @p attribution is empty.
- */
-QRectF paintArtAttribution(QPainter &painter,
- const QRectF &rect,
- const QString &attribution,
- Qt::Alignment anchor = Qt::AlignRight | Qt::AlignBottom,
- qreal scale = 1.0);
-
-#endif // COCKATRICE_ART_CROP_ATTRIBUTION_H
diff --git a/cockatrice/src/interface/widgets/cards/card_info_picture_enlarged_widget.cpp b/cockatrice/src/interface/widgets/cards/card_info_picture_enlarged_widget.cpp
index 40bef3df3..f442fe425 100644
--- a/cockatrice/src/interface/widgets/cards/card_info_picture_enlarged_widget.cpp
+++ b/cockatrice/src/interface/widgets/cards/card_info_picture_enlarged_widget.cpp
@@ -52,29 +52,12 @@ void CardInfoPictureEnlargedWidget::loadPixmap(const QSize &size)
* @param size The desired size for the pixmap.
*
* Sets the widget's pixmap to the card image and resizes the widget to match the specified size. Triggers a repaint.
- *
- * When the image is not yet cached, the pixmap is cleared (instead of showing a stale previous card) and the widget
- * refreshes automatically once the card image finishes loading.
*/
void CardInfoPictureEnlargedWidget::setCardPixmap(const ExactCard &_card, const QSize size)
{
- if (card.getCardPtr()) {
- disconnect(card.getCardPtr().data(), nullptr, this, nullptr);
- }
-
card = _card;
-
- // Clear any previous card's art so we never paint a stale pixmap while the new image loads
- enlargedPixmap = QPixmap();
loadPixmap(size);
- if (card.getCardPtr()) {
- connect(card.getCardPtr().data(), &CardInfo::pixmapUpdated, this, [this]() {
- loadPixmap(this->size());
- update();
- });
- }
-
setFixedSize(size); // Set the widget size to the enlarged size
update(); // Trigger a repaint
diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.cpp b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.cpp
index 14defc8e9..fc53b296f 100644
--- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.cpp
+++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.cpp
@@ -2,7 +2,6 @@
#include "../../../client/settings/cache_settings.h"
#include "../../../client/settings/shortcuts_settings.h"
-#include "../playmat/playmat_settings_dialog.h"
#include "../settings_page/user_interface_settings_page.h"
#include "../tabs/api/commander_spellbook/commander_bracket_widget.h"
#include "deck_list_style_proxy.h"
@@ -12,12 +11,10 @@
#include
#include
#include
-#include
#include
#include
#include
#include
-#include
#include
#include
@@ -231,18 +228,10 @@ void DeckEditorDeckDockWidget::createDeckDock()
upperLayout->addWidget(bannerCardLabel, 4, 0);
upperLayout->addWidget(bannerCardComboBox, 4, 1);
- playmatLabel = new QLabel();
- playmatLabel->setObjectName("playmatLabel");
- playmatLabel->setText(tr("Playmat"));
- playmatSettingsButton = new QPushButton(tr("Edit Playmat..."));
- connect(playmatSettingsButton, &QPushButton::clicked, this, &DeckEditorDeckDockWidget::openPlaymatSettings);
- upperLayout->addWidget(playmatLabel, 5, 0);
- upperLayout->addWidget(playmatSettingsButton, 5, 1);
+ upperLayout->addWidget(deckTagsDisplayWidget, 5, 1);
- upperLayout->addWidget(deckTagsDisplayWidget, 6, 1);
-
- upperLayout->addWidget(activeGroupCriteriaLabel, 7, 0);
- upperLayout->addWidget(activeGroupCriteriaComboBox, 7, 1);
+ upperLayout->addWidget(activeGroupCriteriaLabel, 6, 0);
+ upperLayout->addWidget(activeGroupCriteriaComboBox, 6, 1);
hashLabel1 = new QLabel();
hashLabel1->setObjectName("hashLabel1");
@@ -451,35 +440,6 @@ void DeckEditorDeckDockWidget::writeBannerCard(int index)
deckStateManager->setBannerCard(bannerCard);
}
-void DeckEditorDeckDockWidget::openPlaymatSettings()
-{
- PlaymatInfo current = deckStateManager->getMetadata().playmat;
-
- PlaymatSettingsDialog dialog(current.card, current.params, this);
- if (dialog.exec() == QDialog::Accepted) {
- CardRef newCard = dialog.card();
- PlaymatParams newParams = dialog.params();
-
- if (newCard.isEmpty()) {
- deckStateManager->setPlaymat(PlaymatInfo{});
- } else {
- deckStateManager->setPlaymat({newCard, newParams});
- }
-
- updatePlaymatLabel();
- }
-}
-
-void DeckEditorDeckDockWidget::updatePlaymatLabel()
-{
- CardRef playmat = deckStateManager->getMetadata().playmat.card;
- if (playmat.isEmpty()) {
- playmatSettingsButton->setText(tr("Edit Playmat..."));
- } else {
- playmatSettingsButton->setText(tr("Edit Playmat (%1)").arg(playmat.name));
- }
-}
-
void DeckEditorDeckDockWidget::applyActiveGroupCriteria()
{
getModel()->setActiveGroupCriteria(
@@ -537,7 +497,6 @@ void DeckEditorDeckDockWidget::syncDisplayWidgetsToModel()
syncBannerCardComboBoxSelectionWithDeck();
updateBannerCardComboBox();
bannerCardComboBox->blockSignals(false);
- updatePlaymatLabel();
updateHash();
formatComboBox->blockSignals(true);
diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h
index 9db01e2e5..540199f0d 100644
--- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h
+++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h
@@ -15,15 +15,11 @@
#include "deck_list_history_manager_widget.h"
#include "deck_list_style_proxy.h"
-#include
-#include
#include
#include
-#include
#include
#include
#include
-#include
class CommanderBracketWidget;
class DeckListModel;
@@ -37,8 +33,6 @@ public:
DeckListStyleProxy *proxy;
QTreeView *deckView;
QComboBox *bannerCardComboBox;
- QLabel *playmatLabel;
- QPushButton *playmatSettingsButton;
void createDeckDock();
ExactCard getCurrentCard();
void retranslateUi();
@@ -108,8 +102,6 @@ private slots:
void writeName();
void writeComments();
void writeBannerCard(int);
- void openPlaymatSettings();
- void updatePlaymatLabel();
void applyActiveGroupCriteria();
void setSelectedIndex(const QModelIndex &newCardIndex, bool preserveWidgetFocus);
void updateHash();
diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.cpp b/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.cpp
index eda741728..f8fb450ce 100644
--- a/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.cpp
+++ b/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.cpp
@@ -142,19 +142,6 @@ void DeckStateManager::setBannerCard(const CardRef &bannerCard)
doMetadataModified();
}
-void DeckStateManager::setPlaymat(const PlaymatInfo &playmat)
-{
- PlaymatInfo previous = deckList->getPlaymat();
- if (previous == playmat) {
- return;
- }
-
- requestHistorySave(tr("Set playmat to %1").arg(playmat.card.name));
- deckList->setPlaymat(playmat);
-
- doMetadataModified();
-}
-
void DeckStateManager::setTags(const QStringList &tags)
{
QStringList previous = deckList->getTags();
diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.h b/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.h
index b9c99903e..6fce6be57 100644
--- a/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.h
+++ b/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.h
@@ -171,7 +171,6 @@ public:
void setName(const QString &name);
void setComments(const QString &comments);
void setBannerCard(const CardRef &bannerCard);
- void setPlaymat(const PlaymatInfo &playmat);
void setTags(const QStringList &tags);
void setFormat(const QString &format);
///@}
diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_create_game.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_create_game.cpp
index 59f3e033d..2b1e9e8c2 100644
--- a/cockatrice/src/interface/widgets/dialogs/dlg_create_game.cpp
+++ b/cockatrice/src/interface/widgets/dialogs/dlg_create_game.cpp
@@ -214,7 +214,6 @@ DlgCreateGame::DlgCreateGame(const ServerInfo_Game &gameInfo, const QMapsetChecked(gameInfo.spectators_need_password());
spectatorsCanTalkCheckBox->setChecked(gameInfo.spectators_can_chat());
spectatorsSeeEverythingCheckBox->setChecked(gameInfo.spectators_omniscient());
- shareDecklistsOnLoadCheckBox->setChecked(gameInfo.share_decklists_on_load());
QSet types;
for (int i = 0; i < gameInfo.game_types_size(); ++i) {
diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_filter_games.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_filter_games.cpp
index 7e174a228..8f498cc2c 100644
--- a/cockatrice/src/interface/widgets/dialogs/dlg_filter_games.cpp
+++ b/cockatrice/src/interface/widgets/dialogs/dlg_filter_games.cpp
@@ -30,7 +30,7 @@ DlgFilterGames::DlgFilterGames(const QMap &_allGameTypes,
hideFullGames = new QCheckBox(tr("Hide full games"));
hideFullGames->setChecked(filters.hideFullGames);
- hideGamesThatStarted = new QCheckBox(tr("Hide started games"));
+ hideGamesThatStarted = new QCheckBox(tr("Hide games that have started"));
hideGamesThatStarted->setChecked(filters.hideGamesThatStarted);
hidePasswordProtectedGames = new QCheckBox(tr("Hide password protected games"));
@@ -57,16 +57,16 @@ DlgFilterGames::DlgFilterGames(const QMap &_allGameTypes,
gameNameFilterEdit->setText(filters.gameNameFilter);
auto *gameNameFilterLabel = new QLabel(tr("Game &description:"));
gameNameFilterLabel->setBuddy(gameNameFilterEdit);
- hostNameFilterEdit = new QLineEdit;
- hostNameFilterEdit->setText(filters.hostNameFilters.join(", "));
- auto *hostNameFilterLabel = new QLabel(tr("&Host name:"));
- hostNameFilterLabel->setBuddy(hostNameFilterEdit);
+ creatorNameFilterEdit = new QLineEdit;
+ creatorNameFilterEdit->setText(filters.creatorNameFilters.join(", "));
+ auto *creatorNameFilterLabel = new QLabel(tr("&Creator name:"));
+ creatorNameFilterLabel->setBuddy(creatorNameFilterEdit);
auto *generalGrid = new QGridLayout;
generalGrid->addWidget(gameNameFilterLabel, 0, 0);
generalGrid->addWidget(gameNameFilterEdit, 0, 1);
- generalGrid->addWidget(hostNameFilterLabel, 1, 0);
- generalGrid->addWidget(hostNameFilterEdit, 1, 1);
+ generalGrid->addWidget(creatorNameFilterLabel, 1, 0);
+ generalGrid->addWidget(creatorNameFilterEdit, 1, 1);
generalGrid->addWidget(maxGameAgeLabel, 2, 0);
generalGrid->addWidget(maxGameAgeComboBox, 2, 1);
generalGroupBox = new QGroupBox(tr("General"));
@@ -193,7 +193,7 @@ GameFilterConfigs DlgFilterGames::getFilters() const
hideNotBuddyCreatedGames->isChecked(),
hideOpenDecklistGames->isChecked(),
gameNameFilterEdit->text(),
- getHostNameFilters(),
+ getCreatorNameFilters(),
getGameTypeFilter(),
maxPlayersFilterMinSpinBox->value(),
maxPlayersFilterMaxSpinBox->value(),
@@ -216,9 +216,9 @@ void DlgFilterGames::toggleSpectatorCheckboxEnabledness(bool spectatorsEnabled)
showOnlyIfSpectatorsCanSeeHands->setDisabled(!spectatorsEnabled);
}
-QStringList DlgFilterGames::getHostNameFilters() const
+QStringList DlgFilterGames::getCreatorNameFilters() const
{
- return hostNameFilterEdit->text().split(",", Qt::SkipEmptyParts);
+ return creatorNameFilterEdit->text().split(",", Qt::SkipEmptyParts);
}
QSet DlgFilterGames::getGameTypeFilter() const
diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_filter_games.h b/cockatrice/src/interface/widgets/dialogs/dlg_filter_games.h
index 1cf822b89..447f9b16c 100644
--- a/cockatrice/src/interface/widgets/dialogs/dlg_filter_games.h
+++ b/cockatrice/src/interface/widgets/dialogs/dlg_filter_games.h
@@ -35,7 +35,7 @@ private:
QCheckBox *hideNotBuddyCreatedGames;
QCheckBox *hideOpenDecklistGames;
QLineEdit *gameNameFilterEdit;
- QLineEdit *hostNameFilterEdit;
+ QLineEdit *creatorNameFilterEdit;
QMap gameTypeFilterCheckBoxes;
QSpinBox *maxPlayersFilterMinSpinBox;
QSpinBox *maxPlayersFilterMaxSpinBox;
@@ -50,7 +50,7 @@ private:
const GamesProxyModel *gamesProxyModel;
const QMap gameAgeMap;
- [[nodiscard]] QStringList getHostNameFilters() const;
+ [[nodiscard]] QStringList getCreatorNameFilters() const;
[[nodiscard]] QSet getGameTypeFilter() const;
[[nodiscard]] QTime getMaxGameAge() const;
[[nodiscard]] bool getShowSpectatorPasswordProtected() const;
diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_invite_to_game.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_invite_to_game.cpp
deleted file mode 100644
index b5451bb2c..000000000
--- a/cockatrice/src/interface/widgets/dialogs/dlg_invite_to_game.cpp
+++ /dev/null
@@ -1,112 +0,0 @@
-#include "dlg_invite_to_game.h"
-
-#include "../server/user/user_list_manager.h"
-#include "../server/user/user_list_widget.h"
-#include "../tabs/tab_supervisor.h"
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-DlgInviteToGame::DlgInviteToGame(TabSupervisor *_tabSupervisor,
- const QString &_inviteUrl,
- bool _onlyBuddies,
- const QStringList &_excludeUserNames,
- QWidget *parent)
- : QDialog(parent), tabSupervisor(_tabSupervisor), inviteUrl(_inviteUrl), onlyBuddies(_onlyBuddies),
- excludeUserNames(_excludeUserNames)
-{
- setModal(true);
-
- searchEdit = new QLineEdit(this);
- searchEdit->setClearButtonEnabled(true);
- connect(searchEdit, &QLineEdit::textChanged, this, &DlgInviteToGame::searchTextChanged);
-
- // The embedded list is the real room user list without the hover popup:
- // same manager, same delegate/painter, same sections, live via manager
- // signals while the modal loop runs.
- UserListManager *manager = tabSupervisor->getUserListManager();
- userList = new UserListWidget(tabSupervisor, tabSupervisor->getClient(), UserListWidget::RoomList, this,
- /*hasUserInfoPopup=*/false);
- userList->setUserFilter([this, manager](const QString &name, bool online) {
- return !excludeUserNames.contains(name) && online && !manager->isUserIgnored(name);
- });
- if (onlyBuddies) {
- userList->setSectioned({UserListWidget::Section::Buddy});
- } else {
- userList->setSectioned({UserListWidget::Section::Buddy, UserListWidget::Section::Online});
- }
- userList->bind(manager);
- userList->rebuild();
-
- connect(userList, &UserListWidget::userActivated, this, &DlgInviteToGame::inviteCurrentUser);
- connect(userList, &UserListWidget::currentUserChanged, this, [this](const QString &userName) {
- currentUserName = userName;
- inviteButton->setEnabled(!userName.isEmpty());
- });
-
- inviteButton = new QPushButton(this);
- inviteButton->setEnabled(false);
- inviteButton->setDefault(true);
- connect(inviteButton, &QPushButton::clicked, this, [this] { inviteCurrentUser(currentUserName); });
-
- cancelButton = new QPushButton(this);
- connect(cancelButton, &QPushButton::clicked, this, &QDialog::reject);
-
- auto *buttonRow = new QHBoxLayout;
- buttonRow->addStretch();
- buttonRow->addWidget(inviteButton);
- buttonRow->addWidget(cancelButton);
-
- auto *layout = new QVBoxLayout(this);
- layout->addWidget(searchEdit);
- layout->addWidget(userList, 1);
- layout->addLayout(buttonRow);
-
- retranslateUi();
-
- // Default to a comfortably tall dialog so the list has room to breathe,
- // capped by the available screen. No minimum is enforced: small screens
- // and manual resizing can go shorter than this.
- const QRect availableScreen = QGuiApplication::primaryScreen()->availableGeometry();
- resize(sizeHint().width(), qMin(sizeHint().height() * 3, availableScreen.height() * 4 / 5));
-}
-
-void DlgInviteToGame::searchTextChanged(const QString &text)
-{
- userList->setFilterText(text);
-}
-
-void DlgInviteToGame::inviteCurrentUser(const QString &userName)
-{
- if (userName.isEmpty()) {
- return;
- }
- // The invite link carries the game's id and, when the game has one, its
- // description (makeGameJoinLink embeds both). Read them back so the prefix
- // names the game by description first, then its id — identical to the
- // context-menu invite so recipients see one consistent message style.
- const QUrl inviteUrlObj(inviteUrl);
- const QUrlQuery inviteQuery(inviteUrlObj);
- const int gameId = inviteQuery.queryItemValue("gameid").toInt();
- const QString gameDescription = inviteQuery.queryItemValue("game");
- const QString prefix = gameDescription.isEmpty()
- ? tr("Join my game (#%1):").arg(gameId)
- : tr("Join my game \"%1\" (#%2):").arg(gameDescription).arg(gameId);
- tabSupervisor->sendInviteToUser(userName, prefix + " " + inviteUrl);
- accept();
-}
-
-void DlgInviteToGame::retranslateUi()
-{
- setWindowTitle(tr("Invite to Game"));
- searchEdit->setPlaceholderText(tr("Search users..."));
- inviteButton->setText(tr("Invite"));
- cancelButton->setText(tr("Cancel"));
-}
diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_invite_to_game.h b/cockatrice/src/interface/widgets/dialogs/dlg_invite_to_game.h
deleted file mode 100644
index bbb589bdb..000000000
--- a/cockatrice/src/interface/widgets/dialogs/dlg_invite_to_game.h
+++ /dev/null
@@ -1,46 +0,0 @@
-/**
- * @file dlg_invite_to_game.h
- * @ingroup RoomDialogs
- */
-//! \todo Document this file.
-
-#ifndef DLG_INVITE_TO_GAME_H
-#define DLG_INVITE_TO_GAME_H
-
-#include
-#include
-
-class QLineEdit;
-class QPushButton;
-class TabSupervisor;
-class UserListWidget;
-
-class DlgInviteToGame : public QDialog
-{
- Q_OBJECT
-public:
- DlgInviteToGame(TabSupervisor *_tabSupervisor,
- const QString &_inviteUrl,
- bool _onlyBuddies,
- const QStringList &_excludeUserNames,
- QWidget *parent = nullptr);
-
-private slots:
- void searchTextChanged(const QString &text);
- void inviteCurrentUser(const QString &userName);
-
-private:
- TabSupervisor *tabSupervisor;
- QString inviteUrl;
- bool onlyBuddies;
- QStringList excludeUserNames;
- QString currentUserName;
- QLineEdit *searchEdit;
- UserListWidget *userList;
- QPushButton *inviteButton;
- QPushButton *cancelButton;
-
- void retranslateUi();
-};
-
-#endif
diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_my_reports.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_my_reports.cpp
deleted file mode 100644
index 1af83bfb5..000000000
--- a/cockatrice/src/interface/widgets/dialogs/dlg_my_reports.cpp
+++ /dev/null
@@ -1,297 +0,0 @@
-#include "dlg_my_reports.h"
-
-#include "../utility/report_utils.h"
-#include "abstract_client.h"
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include