mirror of
https://github.com/PCSX2/pcsx2.git
synced 2026-01-31 01:15:24 +01:00
Compare commits
149 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
35624a12d9 | ||
|
|
9b147cc57c | ||
|
|
10e13cfece | ||
|
|
7b2eb7bc47 | ||
|
|
ab1cb802d8 | ||
|
|
366cdd8df0 | ||
|
|
bc3cfb1373 | ||
|
|
db6792af2e | ||
|
|
a1485fb7cd | ||
|
|
c72c309218 | ||
|
|
58899a9ed3 | ||
|
|
0823c70460 | ||
|
|
e5c29a3975 | ||
|
|
1174ae99c9 | ||
|
|
e2d3680038 | ||
|
|
8630893cb1 | ||
|
|
53598b970d | ||
|
|
89de00ac36 | ||
|
|
d5ddf07958 | ||
|
|
30dcf4a14a | ||
|
|
a87710e4bc | ||
|
|
a12f87fec2 | ||
|
|
8ba9bba094 | ||
|
|
1363571c14 | ||
|
|
80de666fcc | ||
|
|
ff0a2f84fa | ||
|
|
0676f145bc | ||
|
|
e19ae2bf60 | ||
|
|
7782d930d5 | ||
|
|
d1a53fe29b | ||
|
|
c484cf286c | ||
|
|
f8882c4da6 | ||
|
|
52c17e67a5 | ||
|
|
615cd00147 | ||
|
|
cb0bf953d3 | ||
|
|
26a68ef76a | ||
|
|
e4c1dc2359 | ||
|
|
40425e3bee | ||
|
|
e51e4a35fe | ||
|
|
bd1b9ea718 | ||
|
|
faaa376232 | ||
|
|
e9ca1a6ead | ||
|
|
4d3149eacb | ||
|
|
78822c96fb | ||
|
|
a78617b987 | ||
|
|
3059ab2b12 | ||
|
|
1d0f6cc5b7 | ||
|
|
38a35043a8 | ||
|
|
7385cbe40a | ||
|
|
9955e07470 | ||
|
|
74db386144 | ||
|
|
3f72efeb7a | ||
|
|
d0f8905439 | ||
|
|
5a60259ef5 | ||
|
|
c8dffccaa7 | ||
|
|
87a82b16ff | ||
|
|
5666902638 | ||
|
|
f1dc232f91 | ||
|
|
5476c5a17f | ||
|
|
9aabb197e6 | ||
|
|
c2488c9269 | ||
|
|
7e40ab8e7e | ||
|
|
902b3c5033 | ||
|
|
4d1afb9fdd | ||
|
|
4209900351 | ||
|
|
780c599b49 | ||
|
|
908d35bf77 | ||
|
|
cfea84b934 | ||
|
|
e5d94e255b | ||
|
|
080858b97c | ||
|
|
d883076573 | ||
|
|
b80101fbd6 | ||
|
|
aca775f8b8 | ||
|
|
4f4a26769c | ||
|
|
d19eaa1b8e | ||
|
|
be1af0cd0f | ||
|
|
6ab02e76f1 | ||
|
|
f87bc7d72b | ||
|
|
086f4f11e1 | ||
|
|
6f54da6234 | ||
|
|
44f47f11b8 | ||
|
|
b5a2d04b2e | ||
|
|
8508ebb7d3 | ||
|
|
3234e45f33 | ||
|
|
53d1320d83 | ||
|
|
9b545809be | ||
|
|
79400acf2a | ||
|
|
3107c4103a | ||
|
|
68c88f692e | ||
|
|
df19b37d6d | ||
|
|
1b5c352566 | ||
|
|
bed6a9e4d4 | ||
|
|
d602ad1d3e | ||
|
|
51c31347df | ||
|
|
00876e7076 | ||
|
|
47eb499893 | ||
|
|
b8680c3139 | ||
|
|
9b4e3b8f74 | ||
|
|
3e858167bc | ||
|
|
44d66555cc | ||
|
|
c5438ceca3 | ||
|
|
3e1927ae44 | ||
|
|
b688117002 | ||
|
|
e62e6fb6c3 | ||
|
|
262e94e5d7 | ||
|
|
11a4b4e7ff | ||
|
|
a98cfcf28c | ||
|
|
d02f30ee62 | ||
|
|
c0bf01a646 | ||
|
|
babb985e9e | ||
|
|
e379c8317d | ||
|
|
5098277474 | ||
|
|
4a94cb6cbd | ||
|
|
e245454b91 | ||
|
|
b003eadd2d | ||
|
|
a5984d8213 | ||
|
|
4dbd95b0bb | ||
|
|
68803229da | ||
|
|
b661a2a149 | ||
|
|
63cd355d7a | ||
|
|
0f5ff68679 | ||
|
|
bd74921926 | ||
|
|
78f83514f4 | ||
|
|
2c36259b88 | ||
|
|
32a0bed6af | ||
|
|
d415f8364c | ||
|
|
7c768b6833 | ||
|
|
773f6968a4 | ||
|
|
1021199512 | ||
|
|
08ef9e2bd9 | ||
|
|
6ba3f96f27 | ||
|
|
7d5b7bc3ce | ||
|
|
0d43d30346 | ||
|
|
da824b4e9e | ||
|
|
ed08b5f34e | ||
|
|
0ce312c1c3 | ||
|
|
07bc2fa452 | ||
|
|
bfd2775074 | ||
|
|
94ccafd745 | ||
|
|
5c6049c4ae | ||
|
|
090464c42d | ||
|
|
89a00db3d6 | ||
|
|
fc415dff93 | ||
|
|
f648a9a438 | ||
|
|
cac6669423 | ||
|
|
7db487a49b | ||
|
|
c96607fe37 | ||
|
|
ba0dae5f57 | ||
|
|
5fe5148e86 |
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
@@ -1,3 +1,4 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [PCSX2]
|
||||
liberapay: PCSX2
|
||||
|
||||
7
.github/labeler.yml
vendored
7
.github/labeler.yml
vendored
@@ -40,6 +40,13 @@
|
||||
- 'pcsx2-qt/**/*'
|
||||
- '3rdparty/Qt/*'
|
||||
- '3rdparty/Qt/**/*'
|
||||
'OSD / ImGui':
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'pcsx2/ImGui/*'
|
||||
- 'pcsx2/ImGui/**/*'
|
||||
- '3rdparty/imgui/*'
|
||||
- '3rdparty/imgui/**/*'
|
||||
'GameDB':
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
|
||||
2
.github/workflows/lint_gamedb.yml
vendored
2
.github/workflows/lint_gamedb.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
- name: Install Packages
|
||||
run: |
|
||||
npm install -g ajv-cli prettier
|
||||
sudo apt-get -y install yamllint
|
||||
pip install yamllint
|
||||
|
||||
- name: Validate YAML
|
||||
run: |
|
||||
|
||||
4
.github/workflows/linux_build_flatpak.yml
vendored
4
.github/workflows/linux_build_flatpak.yml
vendored
@@ -45,7 +45,7 @@ jobs:
|
||||
name: ${{ inputs.jobName }}
|
||||
runs-on: ${{ inputs.os }}
|
||||
container:
|
||||
image: ghcr.io/flathub-infra/flatpak-github-actions:kde-6.7
|
||||
image: ghcr.io/flathub-infra/flatpak-github-actions:kde-6.9
|
||||
options: --privileged
|
||||
timeout-minutes: 60
|
||||
|
||||
@@ -153,7 +153,7 @@ jobs:
|
||||
mv "./${{ steps.artifact-metadata.outputs.artifact-name }}.flatpak" "$GITHUB_WORKSPACE"/ci-artifacts/
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: ${{ steps.artifact-metadata.outputs.artifact-name }}
|
||||
path: ci-artifacts
|
||||
|
||||
2
.github/workflows/linux_build_qt.yml
vendored
2
.github/workflows/linux_build_qt.yml
vendored
@@ -174,7 +174,7 @@ jobs:
|
||||
|
||||
- name: Upload artifact
|
||||
if: inputs.buildAppImage == true
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: ${{ steps.artifact-metadata.outputs.artifact-name }}
|
||||
path: ci-artifacts
|
||||
|
||||
2
.github/workflows/macos_build.yml
vendored
2
.github/workflows/macos_build.yml
vendored
@@ -197,7 +197,7 @@ jobs:
|
||||
cp "${{ steps.artifact-metadata.outputs.artifact-name }}.tar.xz" ci-artifacts/macOS.tar.xz
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: ${{ steps.artifact-metadata.outputs.artifact-name }}
|
||||
path: "*.tar.xz"
|
||||
|
||||
6
.github/workflows/release_cut_new.yml
vendored
6
.github/workflows/release_cut_new.yml
vendored
@@ -68,7 +68,7 @@ jobs:
|
||||
mv ./release-notes.md ${GITHUB_WORKSPACE}/release-notes.md
|
||||
|
||||
- name: Create a GitHub Release (Manual)
|
||||
uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090
|
||||
uses: softprops/action-gh-release@5be0e66d93ac7ed76da52eca8bb058f665c3a5fe
|
||||
if: steps.tag_version.outputs.new_tag && github.event_name == 'workflow_dispatch'
|
||||
with:
|
||||
body_path: ./release-notes.md
|
||||
@@ -77,7 +77,7 @@ jobs:
|
||||
tag_name: ${{ steps.tag_version.outputs.new_tag }}
|
||||
|
||||
- name: Create a GitHub Release (Push)
|
||||
uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090
|
||||
uses: softprops/action-gh-release@5be0e66d93ac7ed76da52eca8bb058f665c3a5fe
|
||||
if: steps.tag_version.outputs.new_tag && github.event_name != 'workflow_dispatch'
|
||||
with:
|
||||
body_path: ./release-notes.md
|
||||
@@ -168,7 +168,7 @@ jobs:
|
||||
- name: Prepare Artifact Folder
|
||||
run: mkdir ./ci-artifacts/
|
||||
|
||||
- uses: actions/download-artifact@v5
|
||||
- uses: actions/download-artifact@v6
|
||||
name: Download all Artifacts
|
||||
with:
|
||||
path: ./ci-artifacts/
|
||||
|
||||
@@ -20,12 +20,12 @@ LIBBACKTRACE=ad106d5fdd5d960bd33fae1c48a351af567fd075
|
||||
LIBJPEGTURBO=3.1.2
|
||||
LIBPNG=1.6.50
|
||||
LIBWEBP=1.6.0
|
||||
SDL=SDL3-3.2.24
|
||||
QT=6.10.0
|
||||
SDL=SDL3-3.2.26
|
||||
QT=6.10.1
|
||||
QTAPNG=1.3.0
|
||||
LZ4=1.10.0
|
||||
ZSTD=1.5.7
|
||||
KDDOCKWIDGETS=2.3.0
|
||||
KDDOCKWIDGETS=2.4.0
|
||||
PLUTOVG=1.3.1
|
||||
PLUTOSVG=0.0.7
|
||||
|
||||
@@ -44,22 +44,22 @@ fd6f417fe9e3a071cf1424a5152d926a34c4a3c5070745470be6cf12a404ed79 $LIBBACKTRACE.
|
||||
8f0012234b464ce50890c490f18194f913a7b1f4e6a03d6644179fa0f867d0cf libjpeg-turbo-$LIBJPEGTURBO.tar.gz
|
||||
4df396518620a7aa3651443e87d1b2862e4e88cad135a8b93423e01706232307 libpng-$LIBPNG.tar.xz
|
||||
e4ab7009bf0629fd11982d4c2aa83964cf244cffba7347ecd39019a9e38c4564 libwebp-$LIBWEBP.tar.gz
|
||||
81cc0fc17e5bf2c1754eeca9af9c47a76789ac5efdd165b3b91cbbe4b90bfb76 $SDL.tar.gz
|
||||
dad488474a51a0b01d547cd2834893d6299328d2e30f479a3564088b5476bae2 $SDL.tar.gz
|
||||
687ddc0c7cb128a3ea58e159b5129252537c27ede0c32a93f11f03127f0c0165 libpng-$LIBPNG-apng.patch.gz
|
||||
537512904744b35e232912055ccf8ec66d768639ff3abe5788d90d792ec5f48b lz4-$LZ4.tar.gz
|
||||
eb33e51f49a15e023950cd7825ca74a4a2b43db8354825ac24fc1b7ee09e6fa3 zstd-$ZSTD.tar.gz
|
||||
ead4623bcb54a32257c5b3e3a5aec6d16ec96f4cda58d2e003f5a0c16f72046d qtbase-everywhere-src-$QT.tar.xz
|
||||
64450a52507c540de53616ed5e516df0e0905a99d3035ddfaa690f2b3f7c0cea qtimageformats-everywhere-src-$QT.tar.xz
|
||||
5ed2c0e04d5e73ff75c2a2ed92db5dc1788ba70f704fc2b71bc21644beda2533 qtsvg-everywhere-src-$QT.tar.xz
|
||||
d86d5098cf3e3e599f37e18df477e65908fc8f036e10ea731b3469ec4fdbd02a qttools-everywhere-src-$QT.tar.xz
|
||||
326e8253cfd0cb5745238117f297da80e30ce8f4c1db81990497bd388b026cde qttranslations-everywhere-src-$QT.tar.xz
|
||||
603f2b0a259b24bd0fb14f880d7761b1d248118a42a6870cdbe8fdda4173761f qtwayland-everywhere-src-$QT.tar.xz
|
||||
5a6226f7e23db51fdc3223121eba53f3f5447cf0cc4d6cb82a3a2df7a65d265d qtbase-everywhere-src-$QT.tar.xz
|
||||
498eabdf2381db96f808942b3e3c765f6360fe6c0e9961f0a45ff7a4c68d7a72 qtimageformats-everywhere-src-$QT.tar.xz
|
||||
c02f355a58f3bbcf404a628bf488b6aeb2d84a94c269afdb86f6e529343ab01f qtsvg-everywhere-src-$QT.tar.xz
|
||||
8148408380ffea03101a26305c812b612ea30dbc07121e58707601522404d49b qttools-everywhere-src-$QT.tar.xz
|
||||
8e49a2df88a12c376a479ae7bd272a91cf57ebb4e7c0cf7341b3565df99d2314 qttranslations-everywhere-src-$QT.tar.xz
|
||||
49bf6db800227a6b2c971f4c5d03dd1e81297e7ffb296ce4a96437304f27cb13 qtwayland-everywhere-src-$QT.tar.xz
|
||||
f1d3be3489f758efe1a8f12118a212febbe611aa670af32e0159fa3c1feab2a6 QtApng-$QTAPNG.tar.gz
|
||||
a8e4a25e5c2686fd36981e527ed05e451fcfc226bddf350f4e76181371190937 shaderc-$SHADERC.tar.gz
|
||||
9427deccbdf4bde6a269938df38c6bd75247493786a310d8d733a2c82065ef47 shaderc-glslang-$SHADERC_GLSLANG.tar.gz
|
||||
c2225a49c3d7efa5c4f4ce4a6b42081e6ea3daca376f3353d9d7c2722d77a28a shaderc-spirv-headers-$SHADERC_SPIRVHEADERS.tar.gz
|
||||
44d1005880c583fc00a0fb41c839214c68214b000ea8dcb54d352732fee600ff shaderc-spirv-tools-$SHADERC_SPIRVTOOLS.tar.gz
|
||||
843baf9e1812c1ab82fd81d85b57cbc0d29bb43245efeb2539039780004b1056 KDDockWidgets-$KDDOCKWIDGETS.tar.gz
|
||||
51dbf24fe72e43dd7cb9a289d3cab47112010f1a2ed69b6fc8ac0dff31991ed2 KDDockWidgets-$KDDOCKWIDGETS.tar.gz
|
||||
bea672eb96ee36c2cbeb911b9bac66dfe989b3ad9a9943101e00aeb2df2aefdb plutovg-$PLUTOVG.tar.gz
|
||||
78561b571ac224030cdc450ca2986b4de915c2ba7616004a6d71a379bffd15f3 plutosvg-$PLUTOSVG.tar.gz
|
||||
EOF
|
||||
@@ -116,7 +116,9 @@ echo "Building libjpegturbo..."
|
||||
rm -fr "libjpeg-turbo-$LIBJPEGTURBO"
|
||||
tar xf "libjpeg-turbo-$LIBJPEGTURBO.tar.gz"
|
||||
cd "libjpeg-turbo-$LIBJPEGTURBO"
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="$INSTALLDIR" -DCMAKE_INSTALL_PREFIX="$INSTALLDIR" -DENABLE_STATIC=OFF -DENABLE_SHARED=ON -B build -G Ninja
|
||||
# On non debian or debian based Linux systems, libjpeg-turbo will set CMAKE_INSTALL_DEFAULT_LIBDIR "lib64" (or libx32)
|
||||
# That will prevent CMake from finding the deps libjpeg later on. if we set CMAKE_INSTALL_DEFAULT_LIBDIR, libjpeg-turbo will leave it as is, so set it to "lib"
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="$INSTALLDIR" -DCMAKE_INSTALL_PREFIX="$INSTALLDIR" -DENABLE_STATIC=OFF -DENABLE_SHARED=ON -DCMAKE_INSTALL_DEFAULT_LIBDIR="lib" -B build -G Ninja
|
||||
cmake --build build --parallel
|
||||
ninja -C build install
|
||||
cd ..
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
"sources": [
|
||||
{
|
||||
"type": "archive",
|
||||
"url": "https://libsdl.org/release/SDL3-3.2.24.tar.gz",
|
||||
"sha256": "81cc0fc17e5bf2c1754eeca9af9c47a76789ac5efdd165b3b91cbbe4b90bfb76"
|
||||
"url": "https://libsdl.org/release/SDL3-3.2.26.tar.gz",
|
||||
"sha256": "dad488474a51a0b01d547cd2834893d6299328d2e30f479a3564088b5476bae2"
|
||||
}
|
||||
],
|
||||
"cleanup": [
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
{
|
||||
"app-id": "net.pcsx2.PCSX2",
|
||||
"runtime": "org.kde.Platform",
|
||||
"runtime-version": "6.9",
|
||||
"runtime-version": "6.10",
|
||||
"sdk": "org.kde.Sdk",
|
||||
"sdk-extensions": [
|
||||
"org.freedesktop.Sdk.Extension.llvm18"
|
||||
"org.freedesktop.Sdk.Extension.llvm20"
|
||||
],
|
||||
"add-extensions": {
|
||||
"org.freedesktop.Platform.ffmpeg-full": {
|
||||
"directory": "lib/ffmpeg",
|
||||
"version": "24.08",
|
||||
"version": "25.08",
|
||||
"add-ld-path": ".",
|
||||
"autodownload": true
|
||||
}
|
||||
@@ -50,8 +50,8 @@
|
||||
"-DCMAKE_PREFIX_PATH=\"${FLATPAK_DEST}\"",
|
||||
"-DCMAKE_BUILD_TYPE=Release",
|
||||
"-DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON",
|
||||
"-DCMAKE_C_COMPILER=/usr/lib/sdk/llvm18/bin/clang",
|
||||
"-DCMAKE_CXX_COMPILER=/usr/lib/sdk/llvm18/bin/clang++",
|
||||
"-DCMAKE_C_COMPILER=/usr/lib/sdk/llvm20/bin/clang",
|
||||
"-DCMAKE_CXX_COMPILER=/usr/lib/sdk/llvm20/bin/clang++",
|
||||
"-DCMAKE_EXE_LINKER_FLAGS_INIT=-fuse-ld=lld",
|
||||
"-DCMAKE_MODULE_LINKER_FLAGS_INIT=-fuse-ld=lld",
|
||||
"-DCMAKE_SHARED_LINKER_FLAGS_INIT=-fuse-ld=lld",
|
||||
|
||||
@@ -40,7 +40,7 @@ fi
|
||||
|
||||
FREETYPE=2.14.1
|
||||
HARFBUZZ=12.0.0
|
||||
SDL=SDL3-3.2.24
|
||||
SDL=SDL3-3.2.26
|
||||
ZSTD=1.5.7
|
||||
LZ4=1.10.0
|
||||
LIBPNG=1.6.50
|
||||
@@ -48,9 +48,9 @@ LIBJPEGTURBO=3.1.2
|
||||
LIBWEBP=1.6.0
|
||||
FFMPEG=8.0
|
||||
MOLTENVK=1.2.9
|
||||
QT=6.10.0
|
||||
QT=6.10.1
|
||||
QTAPNG=1.3.0
|
||||
KDDOCKWIDGETS=2.3.0
|
||||
KDDOCKWIDGETS=2.4.0
|
||||
PLUTOVG=1.3.1
|
||||
PLUTOSVG=0.0.7
|
||||
|
||||
@@ -80,7 +80,7 @@ CMAKE_ARCH_UNIVERSAL=-DCMAKE_OSX_ARCHITECTURES="x86_64;arm64"
|
||||
cat > SHASUMS <<EOF
|
||||
32427e8c471ac095853212a37aef816c60b42052d4d9e48230bab3bdf2936ccc freetype-$FREETYPE.tar.xz
|
||||
c4a398539c3e0fdc9a82dfe7824d0438cae78c1e2124e7c6ada3dfa600cdb6c8 harfbuzz-$HARFBUZZ.tar.gz
|
||||
81cc0fc17e5bf2c1754eeca9af9c47a76789ac5efdd165b3b91cbbe4b90bfb76 $SDL.tar.gz
|
||||
dad488474a51a0b01d547cd2834893d6299328d2e30f479a3564088b5476bae2 $SDL.tar.gz
|
||||
eb33e51f49a15e023950cd7825ca74a4a2b43db8354825ac24fc1b7ee09e6fa3 zstd-$ZSTD.tar.gz
|
||||
537512904744b35e232912055ccf8ec66d768639ff3abe5788d90d792ec5f48b lz4-$LZ4.tar.gz
|
||||
4df396518620a7aa3651443e87d1b2862e4e88cad135a8b93423e01706232307 libpng-$LIBPNG.tar.xz
|
||||
@@ -89,17 +89,17 @@ e4ab7009bf0629fd11982d4c2aa83964cf244cffba7347ecd39019a9e38c4564 libwebp-$LIBWE
|
||||
8f0012234b464ce50890c490f18194f913a7b1f4e6a03d6644179fa0f867d0cf libjpeg-turbo-$LIBJPEGTURBO.tar.gz
|
||||
b2751fccb6cc4c77708113cd78b561059b6fa904b24162fa0be2d60273d27b8e ffmpeg-$FFMPEG.tar.xz
|
||||
f415a09385030c6510a936155ce211f617c31506db5fbc563e804345f1ecf56e v$MOLTENVK.tar.gz
|
||||
ead4623bcb54a32257c5b3e3a5aec6d16ec96f4cda58d2e003f5a0c16f72046d qtbase-everywhere-src-$QT.tar.xz
|
||||
64450a52507c540de53616ed5e516df0e0905a99d3035ddfaa690f2b3f7c0cea qtimageformats-everywhere-src-$QT.tar.xz
|
||||
5ed2c0e04d5e73ff75c2a2ed92db5dc1788ba70f704fc2b71bc21644beda2533 qtsvg-everywhere-src-$QT.tar.xz
|
||||
d86d5098cf3e3e599f37e18df477e65908fc8f036e10ea731b3469ec4fdbd02a qttools-everywhere-src-$QT.tar.xz
|
||||
326e8253cfd0cb5745238117f297da80e30ce8f4c1db81990497bd388b026cde qttranslations-everywhere-src-$QT.tar.xz
|
||||
5a6226f7e23db51fdc3223121eba53f3f5447cf0cc4d6cb82a3a2df7a65d265d qtbase-everywhere-src-$QT.tar.xz
|
||||
498eabdf2381db96f808942b3e3c765f6360fe6c0e9961f0a45ff7a4c68d7a72 qtimageformats-everywhere-src-$QT.tar.xz
|
||||
c02f355a58f3bbcf404a628bf488b6aeb2d84a94c269afdb86f6e529343ab01f qtsvg-everywhere-src-$QT.tar.xz
|
||||
8148408380ffea03101a26305c812b612ea30dbc07121e58707601522404d49b qttools-everywhere-src-$QT.tar.xz
|
||||
8e49a2df88a12c376a479ae7bd272a91cf57ebb4e7c0cf7341b3565df99d2314 qttranslations-everywhere-src-$QT.tar.xz
|
||||
f1d3be3489f758efe1a8f12118a212febbe611aa670af32e0159fa3c1feab2a6 QtApng-$QTAPNG.tar.gz
|
||||
a8e4a25e5c2686fd36981e527ed05e451fcfc226bddf350f4e76181371190937 shaderc-$SHADERC.tar.gz
|
||||
9427deccbdf4bde6a269938df38c6bd75247493786a310d8d733a2c82065ef47 shaderc-glslang-$SHADERC_GLSLANG.tar.gz
|
||||
c2225a49c3d7efa5c4f4ce4a6b42081e6ea3daca376f3353d9d7c2722d77a28a shaderc-spirv-headers-$SHADERC_SPIRVHEADERS.tar.gz
|
||||
44d1005880c583fc00a0fb41c839214c68214b000ea8dcb54d352732fee600ff shaderc-spirv-tools-$SHADERC_SPIRVTOOLS.tar.gz
|
||||
843baf9e1812c1ab82fd81d85b57cbc0d29bb43245efeb2539039780004b1056 KDDockWidgets-$KDDOCKWIDGETS.tar.gz
|
||||
51dbf24fe72e43dd7cb9a289d3cab47112010f1a2ed69b6fc8ac0dff31991ed2 KDDockWidgets-$KDDOCKWIDGETS.tar.gz
|
||||
bea672eb96ee36c2cbeb911b9bac66dfe989b3ad9a9943101e00aeb2df2aefdb plutovg-$PLUTOVG.tar.gz
|
||||
78561b571ac224030cdc450ca2986b4de915c2ba7616004a6d71a379bffd15f3 plutosvg-$PLUTOSVG.tar.gz
|
||||
EOF
|
||||
|
||||
@@ -22,7 +22,7 @@ fi
|
||||
|
||||
FREETYPE=2.14.1
|
||||
HARFBUZZ=12.0.0
|
||||
SDL=SDL3-3.2.24
|
||||
SDL=SDL3-3.2.26
|
||||
ZSTD=1.5.7
|
||||
LZ4=1.10.0
|
||||
LIBPNG=1.6.50
|
||||
@@ -30,9 +30,9 @@ LIBJPEGTURBO=3.1.2
|
||||
LIBWEBP=1.6.0
|
||||
FFMPEG=8.0
|
||||
MOLTENVK=1.2.9
|
||||
QT=6.10.0
|
||||
QT=6.10.1
|
||||
QTAPNG=1.3.0
|
||||
KDDOCKWIDGETS=2.3.0
|
||||
KDDOCKWIDGETS=2.4.0
|
||||
PLUTOVG=1.3.1
|
||||
PLUTOSVG=0.0.7
|
||||
|
||||
@@ -61,7 +61,7 @@ CMAKE_COMMON=(
|
||||
cat > SHASUMS <<EOF
|
||||
32427e8c471ac095853212a37aef816c60b42052d4d9e48230bab3bdf2936ccc freetype-$FREETYPE.tar.xz
|
||||
c4a398539c3e0fdc9a82dfe7824d0438cae78c1e2124e7c6ada3dfa600cdb6c8 harfbuzz-$HARFBUZZ.tar.gz
|
||||
81cc0fc17e5bf2c1754eeca9af9c47a76789ac5efdd165b3b91cbbe4b90bfb76 $SDL.tar.gz
|
||||
dad488474a51a0b01d547cd2834893d6299328d2e30f479a3564088b5476bae2 $SDL.tar.gz
|
||||
eb33e51f49a15e023950cd7825ca74a4a2b43db8354825ac24fc1b7ee09e6fa3 zstd-$ZSTD.tar.gz
|
||||
537512904744b35e232912055ccf8ec66d768639ff3abe5788d90d792ec5f48b lz4-$LZ4.tar.gz
|
||||
4df396518620a7aa3651443e87d1b2862e4e88cad135a8b93423e01706232307 libpng-$LIBPNG.tar.xz
|
||||
@@ -70,17 +70,17 @@ e4ab7009bf0629fd11982d4c2aa83964cf244cffba7347ecd39019a9e38c4564 libwebp-$LIBWE
|
||||
8f0012234b464ce50890c490f18194f913a7b1f4e6a03d6644179fa0f867d0cf libjpeg-turbo-$LIBJPEGTURBO.tar.gz
|
||||
b2751fccb6cc4c77708113cd78b561059b6fa904b24162fa0be2d60273d27b8e ffmpeg-$FFMPEG.tar.xz
|
||||
f415a09385030c6510a936155ce211f617c31506db5fbc563e804345f1ecf56e v$MOLTENVK.tar.gz
|
||||
ead4623bcb54a32257c5b3e3a5aec6d16ec96f4cda58d2e003f5a0c16f72046d qtbase-everywhere-src-$QT.tar.xz
|
||||
64450a52507c540de53616ed5e516df0e0905a99d3035ddfaa690f2b3f7c0cea qtimageformats-everywhere-src-$QT.tar.xz
|
||||
5ed2c0e04d5e73ff75c2a2ed92db5dc1788ba70f704fc2b71bc21644beda2533 qtsvg-everywhere-src-$QT.tar.xz
|
||||
d86d5098cf3e3e599f37e18df477e65908fc8f036e10ea731b3469ec4fdbd02a qttools-everywhere-src-$QT.tar.xz
|
||||
326e8253cfd0cb5745238117f297da80e30ce8f4c1db81990497bd388b026cde qttranslations-everywhere-src-$QT.tar.xz
|
||||
5a6226f7e23db51fdc3223121eba53f3f5447cf0cc4d6cb82a3a2df7a65d265d qtbase-everywhere-src-$QT.tar.xz
|
||||
498eabdf2381db96f808942b3e3c765f6360fe6c0e9961f0a45ff7a4c68d7a72 qtimageformats-everywhere-src-$QT.tar.xz
|
||||
c02f355a58f3bbcf404a628bf488b6aeb2d84a94c269afdb86f6e529343ab01f qtsvg-everywhere-src-$QT.tar.xz
|
||||
8148408380ffea03101a26305c812b612ea30dbc07121e58707601522404d49b qttools-everywhere-src-$QT.tar.xz
|
||||
8e49a2df88a12c376a479ae7bd272a91cf57ebb4e7c0cf7341b3565df99d2314 qttranslations-everywhere-src-$QT.tar.xz
|
||||
f1d3be3489f758efe1a8f12118a212febbe611aa670af32e0159fa3c1feab2a6 QtApng-$QTAPNG.tar.gz
|
||||
a8e4a25e5c2686fd36981e527ed05e451fcfc226bddf350f4e76181371190937 shaderc-$SHADERC.tar.gz
|
||||
9427deccbdf4bde6a269938df38c6bd75247493786a310d8d733a2c82065ef47 shaderc-glslang-$SHADERC_GLSLANG.tar.gz
|
||||
c2225a49c3d7efa5c4f4ce4a6b42081e6ea3daca376f3353d9d7c2722d77a28a shaderc-spirv-headers-$SHADERC_SPIRVHEADERS.tar.gz
|
||||
44d1005880c583fc00a0fb41c839214c68214b000ea8dcb54d352732fee600ff shaderc-spirv-tools-$SHADERC_SPIRVTOOLS.tar.gz
|
||||
843baf9e1812c1ab82fd81d85b57cbc0d29bb43245efeb2539039780004b1056 KDDockWidgets-$KDDOCKWIDGETS.tar.gz
|
||||
51dbf24fe72e43dd7cb9a289d3cab47112010f1a2ed69b6fc8ac0dff31991ed2 KDDockWidgets-$KDDOCKWIDGETS.tar.gz
|
||||
bea672eb96ee36c2cbeb911b9bac66dfe989b3ad9a9943101e00aeb2df2aefdb plutovg-$PLUTOVG.tar.gz
|
||||
78561b571ac224030cdc450ca2986b4de915c2ba7616004a6d71a379bffd15f3 plutosvg-$PLUTOSVG.tar.gz
|
||||
EOF
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
diff --git a/.cmake.conf b/.cmake.conf
|
||||
index 9a21ff42a74..d6707ba7dff 100644
|
||||
--- a/.cmake.conf
|
||||
+++ b/.cmake.conf
|
||||
@@ -51,7 +51,7 @@ set(QT_MAX_NEW_POLICY_CMAKE_VERSION_QT_APPLE "3.21")
|
||||
@@ -12,7 +11,6 @@ index 9a21ff42a74..d6707ba7dff 100644
|
||||
|
||||
set(QT_SUPPORTED_MIN_IOS_SDK_VERSION "17")
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index d3a14fc67eb..1553b956fe3 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -12,6 +12,10 @@ cmake_minimum_required(VERSION 3.16)
|
||||
@@ -27,7 +25,6 @@ index d3a14fc67eb..1553b956fe3 100644
|
||||
|
||||
qt_internal_check_if_path_has_symlinks("${CMAKE_BINARY_DIR}")
|
||||
diff --git a/src/corelib/global/qsysinfo.cpp b/src/corelib/global/qsysinfo.cpp
|
||||
index ae762c0cc6d..9171a5736b4 100644
|
||||
--- a/src/corelib/global/qsysinfo.cpp
|
||||
+++ b/src/corelib/global/qsysinfo.cpp
|
||||
@@ -1027,7 +1027,7 @@ QByteArray QSysInfo::machineUniqueId()
|
||||
@@ -40,10 +37,9 @@ index ae762c0cc6d..9171a5736b4 100644
|
||||
CFStringGetCString(stringRef, uuid, sizeof(uuid), kCFStringEncodingMacRoman);
|
||||
return QByteArray(uuid);
|
||||
diff --git a/src/corelib/kernel/qcore_mac.mm b/src/corelib/kernel/qcore_mac.mm
|
||||
index 9f27dbe694e..c023a48cad3 100644
|
||||
--- a/src/corelib/kernel/qcore_mac.mm
|
||||
+++ b/src/corelib/kernel/qcore_mac.mm
|
||||
@@ -372,7 +372,7 @@ bool qt_apple_runningWithLiquidGlass()
|
||||
@@ -367,7 +367,7 @@ bool qt_apple_runningWithLiquidGlass()
|
||||
return config;
|
||||
#endif
|
||||
|
||||
@@ -52,8 +48,50 @@ index 9f27dbe694e..c023a48cad3 100644
|
||||
if (!nvram) {
|
||||
qWarning("Failed to locate NVRAM entry in IO registry");
|
||||
return {};
|
||||
diff --git a/src/gui/platform/darwin/qappleiconengine.mm b/src/gui/platform/darwin/qappleiconengine.mm
|
||||
--- a/src/gui/platform/darwin/qappleiconengine.mm
|
||||
+++ b/src/gui/platform/darwin/qappleiconengine.mm
|
||||
@@ -366,12 +366,16 @@
|
||||
weight:NSFontWeightRegular
|
||||
scale:NSImageSymbolScaleLarge];
|
||||
|
||||
+ auto *primaryColor = [NSColor colorWithSRGBRed:color.redF()
|
||||
+ green:color.greenF()
|
||||
+ blue:color.blueF()
|
||||
+ alpha:color.alphaF()];
|
||||
+
|
||||
+ if (@available(macOS 13, *)) {
|
||||
+
|
||||
// Apply tint color first, which switches the configuration to palette mode
|
||||
config = [config configurationByApplyingConfiguration:
|
||||
- [NSImageSymbolConfiguration configurationWithPaletteColors:@[
|
||||
- [NSColor colorWithSRGBRed:color.redF() green:color.greenF()
|
||||
- blue:color.blueF() alpha:color.alphaF()]
|
||||
- ]]];
|
||||
+ [NSImageSymbolConfiguration configurationWithPaletteColors:@[primaryColor]]];
|
||||
|
||||
// Then switch back to monochrome, as palette mode gives a different look
|
||||
// than monochrome, even with a single color.
|
||||
@@ -379,6 +383,18 @@
|
||||
[NSImageSymbolConfiguration configurationPreferringMonochrome]];
|
||||
|
||||
return [image imageWithSymbolConfiguration:config];
|
||||
+
|
||||
+ } else {
|
||||
+ NSImage *configuredImage = [image imageWithSymbolConfiguration:config];
|
||||
+ return [NSImage imageWithSize:configuredImage.size flipped:NO
|
||||
+ drawingHandler:^BOOL(NSRect) {
|
||||
+ [primaryColor set];
|
||||
+ NSRect imageRect = {NSZeroPoint, configuredImage.size};
|
||||
+ [configuredImage drawInRect:imageRect];
|
||||
+ NSRectFillUsingOperation(imageRect, NSCompositingOperationSourceIn);
|
||||
+ return YES;
|
||||
+ }];
|
||||
+ }
|
||||
}
|
||||
#elif defined(QT_PLATFORM_UIKIT)
|
||||
auto *configuredImage(const UIImage *image, const QColor &color)
|
||||
diff --git a/src/plugins/platforms/cocoa/qcocoawindow.mm b/src/plugins/platforms/cocoa/qcocoawindow.mm
|
||||
index 6b33d94d58c..867389e4c93 100644
|
||||
--- a/src/plugins/platforms/cocoa/qcocoawindow.mm
|
||||
+++ b/src/plugins/platforms/cocoa/qcocoawindow.mm
|
||||
@@ -323,6 +323,8 @@ a normal (not maximized or full screen) top-level window.
|
||||
|
||||
@@ -47,8 +47,8 @@ set HARFBUZZ=12.0.0
|
||||
set LIBJPEGTURBO=3.1.2
|
||||
set LIBPNG=1650
|
||||
set LIBPNGLONG=1.6.50
|
||||
set SDL=SDL3-3.2.24
|
||||
set QT=6.10.0
|
||||
set SDL=SDL3-3.2.26
|
||||
set QT=6.10.1
|
||||
set QTMINOR=6.10
|
||||
set QTAPNG=1.3.0
|
||||
set LZ4=1.10.0
|
||||
@@ -56,7 +56,7 @@ set WEBP=1.6.0
|
||||
set ZLIB=1.3.1
|
||||
set ZLIBSHORT=131
|
||||
set ZSTD=1.5.7
|
||||
set KDDOCKWIDGETS=2.3.0
|
||||
set KDDOCKWIDGETS=2.4.0
|
||||
set PLUTOVG=1.3.1
|
||||
set PLUTOSVG=0.0.7
|
||||
|
||||
@@ -71,18 +71,17 @@ call :downloadfile "lpng%LIBPNG%.zip" https://download.sourceforge.net/libpng/lp
|
||||
call :downloadfile "lpng%LIBPNG%-apng.patch.gz" https://download.sourceforge.net/libpng-apng/libpng-%LIBPNGLONG%-apng.patch.gz 687ddc0c7cb128a3ea58e159b5129252537c27ede0c32a93f11f03127f0c0165 || goto error
|
||||
call :downloadfile "libjpeg-turbo-%LIBJPEGTURBO%.tar.gz" "https://github.com/libjpeg-turbo/libjpeg-turbo/releases/download/%LIBJPEGTURBO%/libjpeg-turbo-%LIBJPEGTURBO%.tar.gz" 8f0012234b464ce50890c490f18194f913a7b1f4e6a03d6644179fa0f867d0cf || goto error
|
||||
call :downloadfile "libwebp-%WEBP%.tar.gz" "https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-%WEBP%.tar.gz" e4ab7009bf0629fd11982d4c2aa83964cf244cffba7347ecd39019a9e38c4564 || goto error
|
||||
call :downloadfile "%SDL%.zip" "https://libsdl.org/release/%SDL%.zip" ca7fe2ca54a97e047f5eff236e62ae87546e862f509f0a62fc6e564ded3c6a95 || goto error
|
||||
call :downloadfile "SDL-7914bdb7ea14ee5109d50df857c8dfc69a28a62d.patch" https://github.com/libsdl-org/SDL/commit/7914bdb7ea14ee5109d50df857c8dfc69a28a62d.patch 5c09a29a9cac87a85ec3b8f5a68ff0e5c6c47229f3e2282241b9c32166f26977 || goto error
|
||||
call :downloadfile "qtbase-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtbase-everywhere-src-%QT%.zip" d3b5379edcace266273d789249b6d68ae9495ec1b0b562ba6d039034cd315d8e || goto error
|
||||
call :downloadfile "qtimageformats-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtimageformats-everywhere-src-%QT%.zip" ac2fe34a9f1c1451b6785474e9b1b64eb59edef6553be3d630240f16a730456d || goto error
|
||||
call :downloadfile "qtsvg-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtsvg-everywhere-src-%QT%.zip" c12f8bfb617e4a03da104be36f6966ba7f64bee331f0095da1a649a1149796d2 || goto error
|
||||
call :downloadfile "qttools-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qttools-everywhere-src-%QT%.zip" c6d0f0a512304ad87b20f5ff604442dd8d55769d659576ecfe5462fcd7bb9b7d || goto error
|
||||
call :downloadfile "qttranslations-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qttranslations-everywhere-src-%QT%.zip" e6cc1ebf62cf37d81f3b86990086108518037bb383e75da327f297cc4fc1ae36 || goto error
|
||||
call :downloadfile "%SDL%.zip" "https://libsdl.org/release/%SDL%.zip" 739356eef1192fff9d641c320a8f5ef4a10506b8927def4b9ceb764c7e947369 || goto error
|
||||
call :downloadfile "qtbase-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtbase-everywhere-src-%QT%.zip" c43f471a808b07fc541528410e94ce89c6745bdc1d744492e19911d35fbf7d33 || goto error
|
||||
call :downloadfile "qtimageformats-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtimageformats-everywhere-src-%QT%.zip" 2d828d8c999fdd18167937c071781c22321c643b04a106c714411c2356cdb26d || goto error
|
||||
call :downloadfile "qtsvg-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtsvg-everywhere-src-%QT%.zip" ddd74a417d2397eb085d047a9b6ba52b76e748055817f728fe691f8456035d23 || goto error
|
||||
call :downloadfile "qttools-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qttools-everywhere-src-%QT%.zip" db8e49ed50912c3c064a4f9ada7791c09eccec5a8d53463a19608eaab17679f0 || goto error
|
||||
call :downloadfile "qttranslations-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qttranslations-everywhere-src-%QT%.zip" 868eb651e395d48ade5932ef2c386e606e054eb5888ebe5284fbd8cb63ed935a || goto error
|
||||
call :downloadfile "QtApng-%QTAPNG%.zip" "https://github.com/jurplel/QtApng/archive/refs/tags/%QTAPNG%.zip" 5176082cdd468047a7eb1ec1f106b032f57df207aa318d559b29606b00d159ac || goto error
|
||||
call :downloadfile "lz4-%LZ4%.zip" "https://github.com/lz4/lz4/archive/refs/tags/v%LZ4%.zip" 3224b4c80f351f194984526ef396f6079bd6332dd9825c72ac0d7a37b3cdc565 || goto error
|
||||
call :downloadfile "zlib%ZLIBSHORT%.zip" "https://zlib.net/zlib%ZLIBSHORT%.zip" 72af66d44fcc14c22013b46b814d5d2514673dda3d115e64b690c1ad636e7b17 || goto error
|
||||
call :downloadfile "zstd-%ZSTD%.zip" "https://github.com/facebook/zstd/archive/refs/tags/v%ZSTD%.zip" 7897bc5d620580d9b7cd3539c44b59d78f3657d33663fe97a145e07b4ebd69a4 || goto error
|
||||
call :downloadfile "KDDockWidgets-%KDDOCKWIDGETS%.zip" "https://github.com/KDAB/KDDockWidgets/archive/v%KDDOCKWIDGETS%.zip" d2b9592ebe5d053ac97a0213ea35139866d8d5e0a1d84b7d3fb581db7f0b01c6 || goto error
|
||||
call :downloadfile "KDDockWidgets-%KDDOCKWIDGETS%.zip" "https://github.com/KDAB/KDDockWidgets/archive/v%KDDOCKWIDGETS%.zip" 47ddb48197872055f0adf8e90a7235f8a3b795ca1ee3a28ac2c504c673ae3806 || goto error
|
||||
call :downloadfile "plutovg-%PLUTOVG%.zip" "https://github.com/sammycage/plutovg/archive/v%PLUTOVG%.zip" 615184f756d91ce416f2cf883bb67fd4262651417c2e40c4d681c8641a48263e || goto error
|
||||
call :downloadfile "plutosvg-%PLUTOSVG%.zip" "https://github.com/sammycage/plutosvg/archive/v%PLUTOSVG%.zip" 82dee2c57ad712bdd6d6d81d3e76249d89caa4b5a4214353660fd5adff12201a || goto error
|
||||
|
||||
@@ -189,7 +188,6 @@ echo Building SDL...
|
||||
rmdir /S /Q "%SDL%"
|
||||
%SEVENZIP% x "%SDL%.zip" || goto error
|
||||
cd "%SDL%" || goto error
|
||||
%PATCH% -p1 < "..\SDL-7914bdb7ea14ee5109d50df857c8dfc69a28a62d.patch" || goto error
|
||||
cmake -B build %ARM64TOOLCHAIN% -DCMAKE_BUILD_TYPE=Release %FORCEPDB% -DCMAKE_INSTALL_PREFIX="%INSTALLDIR%" -DBUILD_SHARED_LIBS=ON -DSDL_SHARED=ON -DSDL_STATIC=OFF -G Ninja || goto error
|
||||
cmake --build build --parallel || goto error
|
||||
ninja -C build install || goto error
|
||||
|
||||
@@ -44,9 +44,9 @@ set FREETYPE=2.14.1
|
||||
set HARFBUZZ=12.0.0
|
||||
set LIBJPEGTURBO=3.1.2
|
||||
set LIBPNG=1650
|
||||
set SDL=SDL3-3.2.24
|
||||
set SDL=SDL3-3.2.26
|
||||
set LIBPNGLONG=1.6.50
|
||||
set QT=6.10.0
|
||||
set QT=6.10.1
|
||||
set QTMINOR=6.10
|
||||
set QTAPNG=1.3.0
|
||||
set LZ4=1.10.0
|
||||
@@ -54,7 +54,7 @@ set WEBP=1.6.0
|
||||
set ZLIB=1.3.1
|
||||
set ZLIBSHORT=131
|
||||
set ZSTD=1.5.7
|
||||
set KDDOCKWIDGETS=2.3.0
|
||||
set KDDOCKWIDGETS=2.4.0
|
||||
set PLUTOVG=1.3.1
|
||||
set PLUTOSVG=0.0.7
|
||||
|
||||
@@ -69,18 +69,17 @@ call :downloadfile "lpng%LIBPNG%.zip" https://download.sourceforge.net/libpng/lp
|
||||
call :downloadfile "lpng%LIBPNG%-apng.patch.gz" https://download.sourceforge.net/libpng-apng/libpng-%LIBPNGLONG%-apng.patch.gz 687ddc0c7cb128a3ea58e159b5129252537c27ede0c32a93f11f03127f0c0165 || goto error
|
||||
call :downloadfile "libjpeg-turbo-%LIBJPEGTURBO%.tar.gz" "https://github.com/libjpeg-turbo/libjpeg-turbo/releases/download/%LIBJPEGTURBO%/libjpeg-turbo-%LIBJPEGTURBO%.tar.gz" 8f0012234b464ce50890c490f18194f913a7b1f4e6a03d6644179fa0f867d0cf || goto error
|
||||
call :downloadfile "libwebp-%WEBP%.tar.gz" "https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-%WEBP%.tar.gz" e4ab7009bf0629fd11982d4c2aa83964cf244cffba7347ecd39019a9e38c4564 || goto error
|
||||
call :downloadfile "%SDL%.zip" "https://libsdl.org/release/%SDL%.zip" ca7fe2ca54a97e047f5eff236e62ae87546e862f509f0a62fc6e564ded3c6a95 || goto error
|
||||
call :downloadfile "SDL-7914bdb7ea14ee5109d50df857c8dfc69a28a62d.patch" https://github.com/libsdl-org/SDL/commit/7914bdb7ea14ee5109d50df857c8dfc69a28a62d.patch 5c09a29a9cac87a85ec3b8f5a68ff0e5c6c47229f3e2282241b9c32166f26977 || goto error
|
||||
call :downloadfile "qtbase-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtbase-everywhere-src-%QT%.zip" d3b5379edcace266273d789249b6d68ae9495ec1b0b562ba6d039034cd315d8e || goto error
|
||||
call :downloadfile "qtimageformats-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtimageformats-everywhere-src-%QT%.zip" ac2fe34a9f1c1451b6785474e9b1b64eb59edef6553be3d630240f16a730456d || goto error
|
||||
call :downloadfile "qtsvg-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtsvg-everywhere-src-%QT%.zip" c12f8bfb617e4a03da104be36f6966ba7f64bee331f0095da1a649a1149796d2 || goto error
|
||||
call :downloadfile "qttools-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qttools-everywhere-src-%QT%.zip" c6d0f0a512304ad87b20f5ff604442dd8d55769d659576ecfe5462fcd7bb9b7d || goto error
|
||||
call :downloadfile "qttranslations-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qttranslations-everywhere-src-%QT%.zip" e6cc1ebf62cf37d81f3b86990086108518037bb383e75da327f297cc4fc1ae36 || goto error
|
||||
call :downloadfile "%SDL%.zip" "https://libsdl.org/release/%SDL%.zip" 739356eef1192fff9d641c320a8f5ef4a10506b8927def4b9ceb764c7e947369 || goto error
|
||||
call :downloadfile "qtbase-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtbase-everywhere-src-%QT%.zip" c43f471a808b07fc541528410e94ce89c6745bdc1d744492e19911d35fbf7d33 || goto error
|
||||
call :downloadfile "qtimageformats-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtimageformats-everywhere-src-%QT%.zip" 2d828d8c999fdd18167937c071781c22321c643b04a106c714411c2356cdb26d || goto error
|
||||
call :downloadfile "qtsvg-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtsvg-everywhere-src-%QT%.zip" ddd74a417d2397eb085d047a9b6ba52b76e748055817f728fe691f8456035d23 || goto error
|
||||
call :downloadfile "qttools-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qttools-everywhere-src-%QT%.zip" db8e49ed50912c3c064a4f9ada7791c09eccec5a8d53463a19608eaab17679f0 || goto error
|
||||
call :downloadfile "qttranslations-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qttranslations-everywhere-src-%QT%.zip" 868eb651e395d48ade5932ef2c386e606e054eb5888ebe5284fbd8cb63ed935a || goto error
|
||||
call :downloadfile "QtApng-%QTAPNG%.zip" "https://github.com/jurplel/QtApng/archive/refs/tags/%QTAPNG%.zip" 5176082cdd468047a7eb1ec1f106b032f57df207aa318d559b29606b00d159ac || goto error
|
||||
call :downloadfile "lz4-%LZ4%.zip" "https://github.com/lz4/lz4/archive/refs/tags/v%LZ4%.zip" 3224b4c80f351f194984526ef396f6079bd6332dd9825c72ac0d7a37b3cdc565 || goto error
|
||||
call :downloadfile "zlib%ZLIBSHORT%.zip" "https://zlib.net/zlib%ZLIBSHORT%.zip" 72af66d44fcc14c22013b46b814d5d2514673dda3d115e64b690c1ad636e7b17 || goto error
|
||||
call :downloadfile "zstd-%ZSTD%.zip" "https://github.com/facebook/zstd/archive/refs/tags/v%ZSTD%.zip" 7897bc5d620580d9b7cd3539c44b59d78f3657d33663fe97a145e07b4ebd69a4 || goto error
|
||||
call :downloadfile "KDDockWidgets-%KDDOCKWIDGETS%.zip" "https://github.com/KDAB/KDDockWidgets/archive/v%KDDOCKWIDGETS%.zip" d2b9592ebe5d053ac97a0213ea35139866d8d5e0a1d84b7d3fb581db7f0b01c6 || goto error
|
||||
call :downloadfile "KDDockWidgets-%KDDOCKWIDGETS%.zip" "https://github.com/KDAB/KDDockWidgets/archive/v%KDDOCKWIDGETS%.zip" 47ddb48197872055f0adf8e90a7235f8a3b795ca1ee3a28ac2c504c673ae3806 || goto error
|
||||
call :downloadfile "plutovg-%PLUTOVG%.zip" "https://github.com/sammycage/plutovg/archive/v%PLUTOVG%.zip" 615184f756d91ce416f2cf883bb67fd4262651417c2e40c4d681c8641a48263e || goto error
|
||||
call :downloadfile "plutosvg-%PLUTOSVG%.zip" "https://github.com/sammycage/plutosvg/archive/v%PLUTOSVG%.zip" 82dee2c57ad712bdd6d6d81d3e76249d89caa4b5a4214353660fd5adff12201a || goto error
|
||||
|
||||
@@ -186,7 +185,6 @@ echo Building SDL...
|
||||
rmdir /S /Q "%SDL%"
|
||||
%SEVENZIP% x "%SDL%.zip" || goto error
|
||||
cd "%SDL%" || goto error
|
||||
%PATCH% -p1 < "..\SDL-7914bdb7ea14ee5109d50df857c8dfc69a28a62d.patch" || goto error
|
||||
cmake -B build -DCMAKE_BUILD_TYPE=Release %FORCEPDB% -DCMAKE_INSTALL_PREFIX="%INSTALLDIR%" -DBUILD_SHARED_LIBS=ON -DSDL_SHARED=ON -DSDL_STATIC=OFF -G Ninja || goto error
|
||||
cmake --build build --parallel || goto error
|
||||
ninja -C build install || goto error
|
||||
|
||||
4
.github/workflows/windows_build_qt.yml
vendored
4
.github/workflows/windows_build_qt.yml
vendored
@@ -154,7 +154,7 @@ jobs:
|
||||
cmake --build build --config Release --target unittests
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: ${{ steps.artifact-metadata.outputs.artifact-name }}
|
||||
path: |
|
||||
@@ -186,7 +186,7 @@ jobs:
|
||||
}
|
||||
|
||||
- name: Upload artifact - with symbols
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: ${{ steps.artifact-metadata.outputs.artifact-name }}-symbols
|
||||
path: |
|
||||
|
||||
2
3rdparty/soundtouch/CMakeLists.txt
vendored
2
3rdparty/soundtouch/CMakeLists.txt
vendored
@@ -38,7 +38,7 @@ if(NOT "${CMAKE_BUILD_TYPE}" MATCHES "Debug")
|
||||
if(MSVC)
|
||||
target_compile_options(pcsx2-soundtouch PRIVATE /O2 /fp:fast)
|
||||
else()
|
||||
target_compile_options(pcsx2-soundtouch PRIVATE -Ofast)
|
||||
target_compile_options(pcsx2-soundtouch PRIVATE -O3 -ffast-math)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
||||
Binary file not shown.
@@ -1,58 +0,0 @@
|
||||
|
||||
†---------------------†
|
||||
|
||||
Disassembly view:
|
||||
|
||||
†---------------------†
|
||||
|
||||
Ctrl + G Goto
|
||||
Ctrl + E Edit Breakpoint
|
||||
Ctrl + D Enable/disable breakpoint
|
||||
Ctrl + B Add breakpoint
|
||||
Left Go back one branch level/goto pc
|
||||
Right Follow branch/position memory view to accessed address
|
||||
Up Move cursor up one line
|
||||
Down Move cursor down one line
|
||||
Page Up Move visible area up one page
|
||||
Page Down Move visible area down one page
|
||||
F10 Step over
|
||||
F11 Step into
|
||||
Tab Toggle display symbols
|
||||
Left Click Select line/toggle breakpoint if line is already highlighted
|
||||
Right Click Open context menu
|
||||
|
||||
†---------------------†
|
||||
|
||||
Memory View:
|
||||
|
||||
†---------------------†
|
||||
|
||||
Ctrl + G Goto
|
||||
Ctrl + B Add breakpoint
|
||||
Left Move cursor back one byte/nibble
|
||||
Right Move cursor ahead one byte/nibble
|
||||
Up Move cursor up one line
|
||||
Down Move cursor down one line
|
||||
Page Up Move cursor up one page
|
||||
Page Down Move cursor down one page
|
||||
0-9,A-F Overwrite hex nibble
|
||||
Any Overwrite ansi byte
|
||||
Left Click Select byte/nibble
|
||||
Right Click Open context menu
|
||||
Ctrl+Wheel Zoom memory view
|
||||
Esc Return to previous goto address
|
||||
Ctrl+V Paste a hex string into memory
|
||||
|
||||
†---------------------†
|
||||
|
||||
Breakpoint List:
|
||||
|
||||
†---------------------†
|
||||
|
||||
Up Select previous item
|
||||
Down Select next item
|
||||
Delete Remove selected breakpoint
|
||||
Return Edit selected breakpoint
|
||||
Space Toggle enable state of selected breakpoint
|
||||
|
||||
†---------------------†
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
# Game Controller DB for SDL in 2.0.16 format
|
||||
# Source: https://github.com/gabomdq/SDL_GameControllerDB
|
||||
# Source: https://github.com/mdqinc/SDL_GameControllerDB
|
||||
|
||||
# Windows
|
||||
03000000300f00000a01000000000000,3 In 1 Conversion Box,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b8,x:b3,y:b0,platform:Windows,
|
||||
@@ -175,7 +175,6 @@
|
||||
030000001a1c00000001000000000000,Datel Arcade Joystick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000451300000830000000000000,Defender Game Racer X7,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,
|
||||
03000000791d00000103000000000000,Dual Box Wii,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
|
||||
03000000c0160000e105000000000000,Dual Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b0,y:b3,platform:Windows,
|
||||
030000004f040000070f000000000000,Dual Power,a:b8,b:b9,back:b4,dpdown:b1,dpleft:b2,dpright:b3,dpup:b0,leftshoulder:b13,leftstick:b6,lefttrigger:b14,leftx:a0,lefty:a1,rightshoulder:b12,rightstick:b7,righttrigger:b15,start:b5,x:b10,y:b11,platform:Windows,
|
||||
030000004f04000012b3000000000000,Dual Power 3,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Windows,
|
||||
030000004f04000020b3000000000000,Dual Trigger,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Windows,
|
||||
@@ -253,7 +252,6 @@
|
||||
03000000300f00000b01000000000000,GGE909 Recoil,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Windows,
|
||||
03000000f0250000c283000000000000,Gioteck PlayStation Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
|
||||
03000000f025000021c1000000000000,Gioteck PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
|
||||
03000000f025000021c1000010010000,Gioteck PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,
|
||||
03000000f025000031c1000000000000,Gioteck PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
|
||||
03000000f0250000c383000000000000,Gioteck VX2 PlayStation Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
|
||||
03000000f0250000c483000000000000,Gioteck VX2 PlayStation Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
|
||||
@@ -840,6 +838,7 @@
|
||||
03000000172700004431000000000000,Xiaomi Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b20,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a7,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Windows,
|
||||
03000000172700003350000000000000,Xiaomi XMGP01YM,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
|
||||
03000000bc2000005060000000000000,Xiaomi XMGP01YM,+lefty:+a2,+righty:+a5,-lefty:-a1,-righty:-a4,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,start:b11,x:b3,y:b4,platform:Windows,
|
||||
03000000c0160000e105000000000000,XinMo Dual Arcade,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,
|
||||
xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,
|
||||
030000007d0400000340000000000000,Xterminator Digital Gamepad,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:-a4,lefttrigger:+a4,leftx:a0,lefty:a1,paddle1:b7,paddle2:b6,rightshoulder:b5,rightstick:b9,righttrigger:b2,rightx:a3,righty:a5,start:b8,x:b3,y:b4,platform:Windows,
|
||||
030000002c3600000100000000000000,Yawman Arrow,+rightx:h0.2,+righty:h0.4,-rightx:h0.8,-righty:h0.1,a:b4,b:b5,back:b6,dpdown:b15,dpleft:b14,dpright:b16,dpup:b13,leftshoulder:b10,leftstick:b0,lefttrigger:-a4,leftx:a0,lefty:a1,paddle1:b11,paddle2:b12,rightshoulder:b8,rightstick:b9,righttrigger:+a4,start:b3,x:b1,y:b2,platform:Windows,
|
||||
@@ -1120,6 +1119,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
|
||||
030000005f140000c501000000020000,Trust Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Mac OS X,
|
||||
03000000100800000100000000000000,Twin USB Joystick,a:b4,b:b2,back:b16,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b12,leftstick:b20,lefttrigger:b8,leftx:a0,lefty:a2,rightshoulder:b14,rightstick:b22,righttrigger:b10,rightx:a6,righty:a4,start:b18,x:b6,y:b0,platform:Mac OS X,
|
||||
03000000632500002605000000010000,Uberwith Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,
|
||||
03000000c0160000e105000000040000,Ultimate Atari Fight Stick,a:b2,b:b4,back:b18,dpdown:+a2,dpleft:-a0,dpright:+a0,dpup:-a2,rightshoulder:b8,righttrigger:b10,start:b16,x:b0,y:b6,platform:Mac OS X,
|
||||
03000000151900005678000010010000,Uniplay U6,a:b3,b:b6,back:b25,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b17,leftstick:b31,lefttrigger:b21,leftx:a1,lefty:a3,rightshoulder:b19,rightstick:b33,righttrigger:b23,rightx:a4,righty:a5,start:b27,x:b11,y:b13,platform:Mac OS X,
|
||||
030000006f0e00000302000025040000,Victrix PS4 Pro Fightstick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X,
|
||||
030000006f0e00000702000003060000,Victrix PS4 Pro Fightstick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X,
|
||||
@@ -1197,6 +1197,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
|
||||
05000000c82d00000660000000010000,8BitDo Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,
|
||||
03000000c82d00000020000000000000,8BitDo Pro 2 for Xbox,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
06000000c82d00000020000006010000,8BitDo Pro 2 for Xbox,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
03000000c82d00000960000011010000,8BitDo Pro 3,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,paddle1:b17,paddle2:b16,paddle3:b2,paddle4:b5,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,
|
||||
03000000c82d00000131000011010000,8BitDo Receiver,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,
|
||||
03000000c82d00000231000011010000,8BitDo Receiver,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,
|
||||
03000000c82d00000331000011010000,8BitDo Receiver,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,
|
||||
@@ -1331,6 +1332,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
|
||||
030000006f0e00000104000000010000,Gamestop Logic3 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
030000008f0e00000800000010010000,Gasia PlayStation Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,
|
||||
03000000451300000010000010010000,Genius Maxfire Grandias 12,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,
|
||||
03000000f025000021c1000010010000,Gioteck PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,
|
||||
03000000f0250000c283000010010000,Gioteck VX2 PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
|
||||
190000004b4800000010000000010000,GO-Advance Controller,a:b1,b:b0,back:b10,dpdown:b7,dpleft:b8,dpright:b9,dpup:b6,leftshoulder:b4,lefttrigger:b12,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b13,start:b15,x:b2,y:b3,platform:Linux,
|
||||
190000004b4800000010000001010000,GO-Advance Controller,a:b1,b:b0,back:b12,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,leftshoulder:b4,leftstick:b13,lefttrigger:b14,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b16,righttrigger:b15,start:b17,x:b2,y:b3,platform:Linux,
|
||||
@@ -1773,6 +1775,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
|
||||
030000005f140000c501000010010000,Trust Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,
|
||||
06000000f51000000870000003010000,Turtle Beach Recon,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
03000000100800000100000010010000,Twin PS2 Adapter,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux,
|
||||
03000000c0160000e105000010010000,Ultimate Atari Fight Stick,a:b1,b:b2,back:b9,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,rightshoulder:b4,righttrigger:b5,start:b8,x:b0,y:b3,platform:Linux,
|
||||
03000000151900005678000010010000,Uniplay U6,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
|
||||
03000000100800000300000010010000,USB Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux,
|
||||
03000000790000000600000007010000,USB gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b3,y:b0,platform:Linux,
|
||||
|
||||
BIN
bin/resources/icons/AppIconLarge.ico
Normal file
BIN
bin/resources/icons/AppIconLarge.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 295 KiB |
@@ -80,6 +80,7 @@ PS_OUTPUT ps_downsample_copy(PS_INPUT input)
|
||||
int DownsampleFactor = DOFFSET;
|
||||
int2 ClampMin = int2(EMODA, EMODC);
|
||||
float Weight = BGColor.x;
|
||||
float step_multiplier = BGColor.y;
|
||||
|
||||
int2 coord = max(int2(input.p.xy) * DownsampleFactor, ClampMin);
|
||||
|
||||
@@ -88,7 +89,7 @@ PS_OUTPUT ps_downsample_copy(PS_INPUT input)
|
||||
for (int yoff = 0; yoff < DownsampleFactor; yoff++)
|
||||
{
|
||||
for (int xoff = 0; xoff < DownsampleFactor; xoff++)
|
||||
output.c += Texture.Load(int3(coord + int2(xoff, yoff), 0));
|
||||
output.c += Texture.Load(int3(coord + int2(xoff * step_multiplier, yoff * step_multiplier), 0));
|
||||
}
|
||||
output.c /= Weight;
|
||||
return output;
|
||||
|
||||
@@ -175,8 +175,32 @@ float4 ps_main4(PS_INPUT input) : SV_Target0
|
||||
// high motion -> interpolate pixels above and below
|
||||
return (hn + ln) / 2.0f;
|
||||
else
|
||||
// low motion -> weave
|
||||
return cn;
|
||||
{
|
||||
// Check if it's completely static first, we don't need to mess with any of that.
|
||||
if((mh_max != -motion_thr.x) || (ml_max != -motion_thr.x) || (mc_max != -motion_thr.x))
|
||||
{
|
||||
// Check the diff with the above and below lines, if the difference is smaller between the new high and low lines
|
||||
// compared to the new centre line and the high line (with some threshold of about 25 color steps), then reconstruct.
|
||||
float3 mhln = hn.rgb - ln.rgb;
|
||||
float3 mchn = hn.rgb - cn.rgb;
|
||||
|
||||
mhln = max(mhln, -mhln) - motion_thr;
|
||||
mchn = max(mchn, -mchn) - motion_thr;
|
||||
|
||||
float mhln_max = max(max(mhln.x, mhln.y), mhln.z);
|
||||
float mchn_max = max(max(mchn.x, mchn.y), mchn.z);
|
||||
|
||||
// The new centre line is a fair chunk different from those surrounding it, so quite likely incorrect.
|
||||
if (mhln_max < 0.0f && mchn_max >= (mhln_max * 0.90f))
|
||||
return (hn + ln) / 2.0f;
|
||||
else
|
||||
// low motion -> weave
|
||||
return cn;
|
||||
}
|
||||
else
|
||||
// low motion -> weave
|
||||
return cn;
|
||||
}
|
||||
}
|
||||
|
||||
return float4(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
|
||||
@@ -70,6 +70,7 @@ void ps_depth_copy()
|
||||
uniform ivec2 ClampMin;
|
||||
uniform int DownsampleFactor;
|
||||
uniform float Weight;
|
||||
uniform float StepMultiplier;
|
||||
|
||||
void ps_downsample_copy()
|
||||
{
|
||||
@@ -78,7 +79,7 @@ void ps_downsample_copy()
|
||||
for (int yoff = 0; yoff < DownsampleFactor; yoff++)
|
||||
{
|
||||
for (int xoff = 0; xoff < DownsampleFactor; xoff++)
|
||||
result += texelFetch(TextureSampler, coord + ivec2(xoff, yoff), 0);
|
||||
result += texelFetch(TextureSampler, coord + ivec2(xoff * StepMultiplier, yoff * StepMultiplier), 0);
|
||||
}
|
||||
SV_Target0 = result / Weight;
|
||||
}
|
||||
|
||||
@@ -173,8 +173,32 @@ void ps_main4()
|
||||
// high motion -> interpolate pixels above and below
|
||||
SV_Target0 = (hn + ln) / 2.0f;
|
||||
else
|
||||
// low motion -> weave
|
||||
SV_Target0 = cn;
|
||||
{
|
||||
// Check if it's completely static first, we don't need to mess with any of that.
|
||||
if((mh_max != -motion_thr.x) || (ml_max != -motion_thr.x) || (mc_max != -motion_thr.x))
|
||||
{
|
||||
// Check the diff with the above and below lines, if the difference is smaller between the new high and low lines
|
||||
// compared to the new centre line and the high line (with some threshold of about 25 color steps), then reconstruct.
|
||||
vec3 mhln = hn.rgb - ln.rgb;
|
||||
vec3 mchn = hn.rgb - cn.rgb;
|
||||
|
||||
mhln = max(mhln, -mhln) - motion_thr;
|
||||
mchn = max(mchn, -mchn) - motion_thr;
|
||||
|
||||
float mhln_max = max(max(mhln.x, mhln.y), mhln.z);
|
||||
float mchn_max = max(max(mchn.x, mchn.y), mchn.z);
|
||||
|
||||
// The new centre line is a fair chunk different from those surrounding it, so quite likely incorrect.
|
||||
if (mhln_max < 0.0f && mchn_max >= (mhln_max * 0.90f))
|
||||
SV_Target0 = (hn + ln) / 2.0f;
|
||||
else
|
||||
// low motion -> weave
|
||||
SV_Target0 = cn;
|
||||
}
|
||||
else
|
||||
// low motion -> weave
|
||||
SV_Target0 = cn;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -66,7 +66,8 @@ layout(push_constant) uniform cb10
|
||||
int DownsampleFactor;
|
||||
int pad0;
|
||||
float Weight;
|
||||
vec3 pad1;
|
||||
float step_multiplier;
|
||||
vec2 pad1;
|
||||
};
|
||||
void ps_downsample_copy()
|
||||
{
|
||||
@@ -75,7 +76,9 @@ void ps_downsample_copy()
|
||||
for (int yoff = 0; yoff < DownsampleFactor; yoff++)
|
||||
{
|
||||
for (int xoff = 0; xoff < DownsampleFactor; xoff++)
|
||||
result += texelFetch(samp0, coord + ivec2(xoff, yoff), 0);
|
||||
{
|
||||
result += texelFetch(samp0, coord + ivec2(xoff * step_multiplier, yoff * step_multiplier), 0);
|
||||
}
|
||||
}
|
||||
o_col0 = result / Weight;
|
||||
}
|
||||
|
||||
@@ -194,8 +194,32 @@ void ps_main4()
|
||||
// high motion -> interpolate pixels above and below
|
||||
o_col0 = (hn + ln) / 2.0f;
|
||||
else
|
||||
// low motion -> weave
|
||||
o_col0 = cn;
|
||||
{
|
||||
// Check if it's completely static first, we don't need to mess with any of that.
|
||||
if((mh_max != -motion_thr.x) || (ml_max != -motion_thr.x) || (mc_max != -motion_thr.x))
|
||||
{
|
||||
// Check the diff with the above and below lines, if the difference is smaller between the new high and low lines
|
||||
// compared to the new centre line and the high line (with some threshold of about 25 color steps), then reconstruct.
|
||||
vec3 mhln = hn.rgb - ln.rgb;
|
||||
vec3 mchn = hn.rgb - cn.rgb;
|
||||
|
||||
mhln = max(mhln, -mhln) - motion_thr;
|
||||
mchn = max(mchn, -mchn) - motion_thr;
|
||||
|
||||
float mhln_max = max(max(mhln.x, mhln.y), mhln.z);
|
||||
float mchn_max = max(max(mchn.x, mchn.y), mchn.z);
|
||||
|
||||
// The new centre line is a fair chunk different from those surrounding it, so quite likely incorrect.
|
||||
if (mhln_max < 0.0f && mchn_max >= (mhln_max * 0.90f))
|
||||
o_col0 = (hn + ln) / 2.0f;
|
||||
else
|
||||
// low motion -> weave
|
||||
o_col0 = cn;
|
||||
}
|
||||
else
|
||||
// low motion -> weave
|
||||
o_col0 = cn;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -108,7 +108,7 @@ disable_compiler_warnings_for_target(speex)
|
||||
|
||||
# Find the Qt components that we need.
|
||||
if(ENABLE_QT_UI)
|
||||
find_package(Qt6 6.7.3 COMPONENTS CoreTools Core GuiTools Gui WidgetsTools Widgets LinguistTools REQUIRED)
|
||||
find_package(Qt6 6.10.0 COMPONENTS CoreTools Core GuiTools Gui WidgetsTools Widgets LinguistTools REQUIRED)
|
||||
endif()
|
||||
|
||||
if (Qt6_VERSION VERSION_GREATER_EQUAL 6.10.0)
|
||||
|
||||
@@ -7,7 +7,6 @@ endif(NOT TOP_CMAKE_WAS_SOURCED)
|
||||
|
||||
add_library(common)
|
||||
|
||||
# x86emitter sources
|
||||
target_sources(common PRIVATE
|
||||
AlignedMalloc.cpp
|
||||
Assertions.cpp
|
||||
@@ -34,9 +33,9 @@ target_sources(common PRIVATE
|
||||
Timer.cpp
|
||||
WAVWriter.cpp
|
||||
WindowInfo.cpp
|
||||
YAML.cpp
|
||||
)
|
||||
|
||||
# x86emitter headers
|
||||
target_sources(common PRIVATE
|
||||
AlignedMalloc.h
|
||||
Assertions.h
|
||||
@@ -81,6 +80,7 @@ target_sources(common PRIVATE
|
||||
WAVWriter.h
|
||||
WindowInfo.h
|
||||
WrappedMemCopy.h
|
||||
YAML.h
|
||||
)
|
||||
|
||||
if(_M_X86)
|
||||
@@ -208,6 +208,7 @@ target_link_libraries(common PRIVATE
|
||||
target_link_libraries(common PUBLIC
|
||||
fmt::fmt
|
||||
fast_float
|
||||
rapidyaml::rapidyaml
|
||||
)
|
||||
|
||||
fixup_file_properties(common)
|
||||
|
||||
@@ -99,7 +99,7 @@ __ri void Log::WriteToConsole(LOGLEVEL level, ConsoleColors color, std::string_v
|
||||
static constexpr size_t BUFFER_SIZE = 512;
|
||||
|
||||
SmallStackString<BUFFER_SIZE> buffer;
|
||||
buffer.reserve(32 + message.length());
|
||||
buffer.reserve(static_cast<u32>(32 + message.length()));
|
||||
buffer.append(s_ansi_color_codes[color]);
|
||||
|
||||
if (s_log_timestamps)
|
||||
|
||||
@@ -85,7 +85,7 @@ static inline bool FileSystemCharacterIsSane(char32_t c, bool strip_slashes)
|
||||
if (c == '*')
|
||||
return false;
|
||||
|
||||
// macos doesn't allow colons, apparently
|
||||
// macos doesn't allow colons, apparently
|
||||
#ifdef __APPLE__
|
||||
if (c == U':')
|
||||
return false;
|
||||
@@ -2490,6 +2490,21 @@ bool FileSystem::DeleteDirectory(const char* path)
|
||||
return (rmdir(path) == 0);
|
||||
}
|
||||
|
||||
std::string FileSystem::GetPackagePath()
|
||||
{
|
||||
// NOTE: The reason this function is separated from FileSystem::GetProgramPath() is because
|
||||
// This path check breaks other usages of FileSystem::GetProgramPath for the AppImage.
|
||||
// Notably the CI-generated AppImage fails to start because PCSX2 can't find its resources
|
||||
// since it tries to look for them relative to the .AppImage file instead of relative to the actual executable.
|
||||
|
||||
// Check if we are running inside appimage. If so, return the path to the appimage instead.
|
||||
if (const char* appimage_path = getenv("APPIMAGE"))
|
||||
return std::string(appimage_path);
|
||||
|
||||
// Otherwise, find the executable file directly
|
||||
return GetProgramPath();
|
||||
}
|
||||
|
||||
std::string FileSystem::GetProgramPath()
|
||||
{
|
||||
#if defined(__linux__)
|
||||
|
||||
@@ -166,6 +166,9 @@ namespace FileSystem
|
||||
/// Copies one file to another, optionally replacing it if it already exists.
|
||||
bool CopyFilePath(const char* source, const char* destination, bool replace);
|
||||
|
||||
/// Returns the path to the current package (AppImage).
|
||||
std::string GetPackagePath();
|
||||
|
||||
/// Returns the path to the current executable.
|
||||
std::string GetProgramPath();
|
||||
|
||||
|
||||
635
common/Image.cpp
635
common/Image.cpp
@@ -14,9 +14,6 @@
|
||||
#include <webp/decode.h>
|
||||
#include <webp/encode.h>
|
||||
|
||||
// Compute the address of a base type given a field offset.
|
||||
#define BASE_FROM_RECORD_FIELD(ptr, base_type, field) ((base_type*)(((char*)ptr) - offsetof(base_type, field)))
|
||||
|
||||
static bool PNGBufferLoader(RGBA8Image* image, const void* buffer, size_t buffer_size);
|
||||
static bool PNGBufferSaver(const RGBA8Image& image, std::vector<u8>* buffer, u8 quality);
|
||||
static bool PNGFileLoader(RGBA8Image* image, const char* filename, std::FILE* fp);
|
||||
@@ -32,6 +29,11 @@ static bool WebPBufferSaver(const RGBA8Image& image, std::vector<u8>* buffer, u8
|
||||
static bool WebPFileLoader(RGBA8Image* image, const char* filename, std::FILE* fp);
|
||||
static bool WebPFileSaver(const RGBA8Image& image, const char* filename, std::FILE* fp, u8 quality);
|
||||
|
||||
static bool BMPBufferLoader(RGBA8Image* image, const void* buffer, size_t buffer_size);
|
||||
static bool BMPBufferSaver(const RGBA8Image& image, std::vector<u8>* buffer, u8 quality);
|
||||
static bool BMPFileLoader(RGBA8Image* image, const char* filename, std::FILE* fp);
|
||||
static bool BMPFileSaver(const RGBA8Image& image, const char* filename, std::FILE* fp, u8 quality);
|
||||
|
||||
struct FormatHandler
|
||||
{
|
||||
const char* extension;
|
||||
@@ -46,6 +48,7 @@ static constexpr FormatHandler s_format_handlers[] = {
|
||||
{"jpg", JPEGBufferLoader, JPEGBufferSaver, JPEGFileLoader, JPEGFileSaver},
|
||||
{"jpeg", JPEGBufferLoader, JPEGBufferSaver, JPEGFileLoader, JPEGFileSaver},
|
||||
{"webp", WebPBufferLoader, WebPBufferSaver, WebPFileLoader, WebPFileSaver},
|
||||
{"bmp", BMPBufferLoader, BMPBufferSaver, BMPFileLoader, BMPFileSaver},
|
||||
};
|
||||
|
||||
static const FormatHandler* GetFormatHandler(const std::string_view extension)
|
||||
@@ -485,6 +488,8 @@ bool JPEGFileLoader(RGBA8Image* image, const char* filename, std::FILE* fp)
|
||||
|
||||
struct FileCallback
|
||||
{
|
||||
// Must be the first member (&this == &mgr)
|
||||
// We pass a pointer of mgr to libjpeg, and we need to be able to cast it back to FileCallback.
|
||||
jpeg_source_mgr mgr;
|
||||
|
||||
std::FILE* fp;
|
||||
@@ -496,7 +501,7 @@ bool JPEGFileLoader(RGBA8Image* image, const char* filename, std::FILE* fp)
|
||||
.mgr = {
|
||||
.init_source = [](j_decompress_ptr cinfo) {},
|
||||
.fill_input_buffer = [](j_decompress_ptr cinfo) -> boolean {
|
||||
FileCallback* cb = BASE_FROM_RECORD_FIELD(cinfo->src, FileCallback, mgr);
|
||||
FileCallback* cb = reinterpret_cast<FileCallback*>(cinfo->src);
|
||||
cb->mgr.next_input_byte = cb->buffer.get();
|
||||
if (cb->end_of_file)
|
||||
{
|
||||
@@ -513,7 +518,7 @@ bool JPEGFileLoader(RGBA8Image* image, const char* filename, std::FILE* fp)
|
||||
},
|
||||
.skip_input_data =
|
||||
[](j_decompress_ptr cinfo, long num_bytes) {
|
||||
FileCallback* cb = BASE_FROM_RECORD_FIELD(cinfo->src, FileCallback, mgr);
|
||||
FileCallback* cb = reinterpret_cast<FileCallback*>(cinfo->src);
|
||||
const size_t skip_in_buffer = std::min<size_t>(cb->mgr.bytes_in_buffer, static_cast<size_t>(num_bytes));
|
||||
cb->mgr.next_input_byte += skip_in_buffer;
|
||||
cb->mgr.bytes_in_buffer -= skip_in_buffer;
|
||||
@@ -650,12 +655,12 @@ bool JPEGFileSaver(const RGBA8Image& image, const char* filename, std::FILE* fp,
|
||||
.mgr = {
|
||||
.init_destination =
|
||||
[](j_compress_ptr cinfo) {
|
||||
FileCallback* cb = BASE_FROM_RECORD_FIELD(cinfo->dest, FileCallback, mgr);
|
||||
FileCallback* cb = reinterpret_cast<FileCallback*>(cinfo->dest);
|
||||
cb->mgr.next_output_byte = cb->buffer.get();
|
||||
cb->mgr.free_in_buffer = BUFFER_SIZE;
|
||||
},
|
||||
.empty_output_buffer = [](j_compress_ptr cinfo) -> boolean {
|
||||
FileCallback* cb = BASE_FROM_RECORD_FIELD(cinfo->dest, FileCallback, mgr);
|
||||
FileCallback* cb = reinterpret_cast<FileCallback*>(cinfo->dest);
|
||||
if (!cb->write_error)
|
||||
cb->write_error |= (std::fwrite(cb->buffer.get(), 1, BUFFER_SIZE, cb->fp) != BUFFER_SIZE);
|
||||
|
||||
@@ -665,7 +670,7 @@ bool JPEGFileSaver(const RGBA8Image& image, const char* filename, std::FILE* fp,
|
||||
},
|
||||
.term_destination =
|
||||
[](j_compress_ptr cinfo) {
|
||||
FileCallback* cb = BASE_FROM_RECORD_FIELD(cinfo->dest, FileCallback, mgr);
|
||||
FileCallback* cb = reinterpret_cast<FileCallback*>(cinfo->dest);
|
||||
const size_t left = BUFFER_SIZE - cb->mgr.free_in_buffer;
|
||||
if (left > 0 && !cb->write_error)
|
||||
cb->write_error |= (std::fwrite(cb->buffer.get(), 1, left, cb->fp) != left);
|
||||
@@ -734,3 +739,617 @@ bool WebPFileSaver(const RGBA8Image& image, const char* filename, std::FILE* fp,
|
||||
|
||||
return (std::fwrite(buffer.data(), buffer.size(), 1, fp) == 1);
|
||||
}
|
||||
|
||||
// Some of this code is adapted from Qt's BMP handler (https://github.com/qt/qtbase/blob/dev/src/gui/image/qbmphandler.cpp)
|
||||
#pragma pack(push, 1)
|
||||
struct BMPFileHeader
|
||||
{
|
||||
u16 type;
|
||||
u32 size;
|
||||
u16 reserved1;
|
||||
u16 reserved2;
|
||||
u32 offset;
|
||||
};
|
||||
|
||||
struct BMPInfoHeader
|
||||
{
|
||||
u32 size;
|
||||
s32 width;
|
||||
s32 height;
|
||||
u16 planes;
|
||||
u16 bit_count;
|
||||
u32 compression;
|
||||
u32 size_image;
|
||||
s32 x_pels_per_meter;
|
||||
s32 y_pels_per_meter;
|
||||
u32 clr_used;
|
||||
u32 clr_important;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
bool IsSupportedBMPFormat(u32 compression, u16 bit_count)
|
||||
{
|
||||
if (compression == 0)
|
||||
return (bit_count == 1 || bit_count == 4 || bit_count == 8 || bit_count == 16 || bit_count == 24 || bit_count == 32);
|
||||
|
||||
if (compression == 1)
|
||||
return (bit_count == 8);
|
||||
|
||||
if (compression == 2)
|
||||
return (bit_count == 4);
|
||||
|
||||
if (compression == 3 || compression == 4) // BMP_BITFIELDS or BMP_ALPHABITFIELDS
|
||||
return (bit_count == 16 || bit_count == 32);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LoadBMPPalette(std::vector<u32>& palette, const u8* data, u32 palette_offset, const BMPInfoHeader& info_header)
|
||||
{
|
||||
// 1 bit format doesn't use a palette in the traditional sense
|
||||
if (info_header.bit_count == 1)
|
||||
{
|
||||
palette = {0xFFFFFFFF, 0xFF000000};
|
||||
return true;
|
||||
}
|
||||
|
||||
const u32 num_colors = (info_header.clr_used > 0) ? info_header.clr_used : (1u << info_header.bit_count);
|
||||
|
||||
// Make sure that we don't have an unreasonably large palette
|
||||
if (num_colors > 256)
|
||||
{
|
||||
Console.Error("Invalid palette size: %u", num_colors);
|
||||
return false;
|
||||
}
|
||||
|
||||
palette.clear();
|
||||
palette.reserve(num_colors);
|
||||
|
||||
const u8* palette_data = data + sizeof(BMPFileHeader) + info_header.size;
|
||||
|
||||
for (u32 i = 0; i < num_colors; i++)
|
||||
{
|
||||
const u8* color = palette_data + (i * 4);
|
||||
const u8 b = color[0];
|
||||
const u8 g = color[1];
|
||||
const u8 r = color[2];
|
||||
palette.push_back(r | (g << 8) | (b << 16) | 0xFF000000u);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LoadUncompressedBMP(u32* pixels, const u8* src, const u8* data, u32 width, u32 height, const BMPInfoHeader& info_header, const std::vector<u32>& palette, bool flip_vertical, u32 red_mask = 0, u32 green_mask = 0, u32 blue_mask = 0, u32 alpha_mask = 0, bool use_alpha = false)
|
||||
{
|
||||
const u32 row_size = ((width * info_header.bit_count + 31) / 32) * 4;
|
||||
|
||||
for (u32 y = 0; y < height; y++)
|
||||
{
|
||||
u32 dst_y = flip_vertical ? (height - 1 - y) : y;
|
||||
const u8* row_src = src + (y * row_size);
|
||||
u32* row_dst = pixels + (dst_y * width);
|
||||
|
||||
u32 bit_offset = 0;
|
||||
|
||||
for (u32 x = 0; x < width; x++)
|
||||
{
|
||||
u32 pixel_value = 0;
|
||||
|
||||
switch (info_header.bit_count)
|
||||
{
|
||||
case 1:
|
||||
{
|
||||
const u32 byte_index = bit_offset / 8;
|
||||
const u32 bit_index = 7 - (bit_offset % 8);
|
||||
pixel_value = (row_src[byte_index] >> bit_index) & 1;
|
||||
bit_offset += 1;
|
||||
break;
|
||||
}
|
||||
case 4:
|
||||
{
|
||||
const u32 byte_index = bit_offset / 8;
|
||||
const u32 nibble_index = (bit_offset % 8) / 4;
|
||||
pixel_value = (row_src[byte_index] >> (nibble_index * 4)) & 0xF;
|
||||
bit_offset += 4;
|
||||
break;
|
||||
}
|
||||
case 8:
|
||||
{
|
||||
pixel_value = row_src[bit_offset / 8];
|
||||
bit_offset += 8;
|
||||
break;
|
||||
}
|
||||
case 16:
|
||||
{
|
||||
const u32 byte_index = bit_offset / 8;
|
||||
pixel_value = row_src[byte_index] | (row_src[byte_index + 1] << 8);
|
||||
bit_offset += 16;
|
||||
|
||||
if (info_header.compression == 3)
|
||||
{
|
||||
const u8* bitfields = data + sizeof(BMPFileHeader) + info_header.size;
|
||||
const u32 r_mask = *reinterpret_cast<const u32*>(bitfields);
|
||||
const u32 g_mask = *reinterpret_cast<const u32*>(bitfields + 4);
|
||||
const u32 b_mask = *reinterpret_cast<const u32*>(bitfields + 8);
|
||||
|
||||
u32 r_shift = 0, g_shift = 0, b_shift = 0;
|
||||
u32 temp = r_mask;
|
||||
while (temp >>= 1)
|
||||
r_shift++;
|
||||
temp = g_mask;
|
||||
while (temp >>= 1)
|
||||
g_shift++;
|
||||
temp = b_mask;
|
||||
while (temp >>= 1)
|
||||
b_shift++;
|
||||
|
||||
const u8 r = static_cast<u8>((pixel_value & r_mask) >> r_shift);
|
||||
const u8 g = static_cast<u8>((pixel_value & g_mask) >> g_shift);
|
||||
const u8 b = static_cast<u8>((pixel_value & b_mask) >> b_shift);
|
||||
|
||||
const u8 r_max = static_cast<u8>(r_mask >> r_shift);
|
||||
const u8 g_max = static_cast<u8>(g_mask >> g_shift);
|
||||
const u8 b_max = static_cast<u8>(b_mask >> b_shift);
|
||||
|
||||
const u8 r_scaled = (r_max > 0) ? static_cast<u8>((r * 255) / r_max) : 0;
|
||||
const u8 g_scaled = (g_max > 0) ? static_cast<u8>((g * 255) / g_max) : 0;
|
||||
const u8 b_scaled = (b_max > 0) ? static_cast<u8>((b * 255) / b_max) : 0;
|
||||
|
||||
row_dst[x] = r_scaled | (g_scaled << 8) | (b_scaled << 16) | 0xFF000000u;
|
||||
}
|
||||
else
|
||||
{
|
||||
const u8 r = (pixel_value >> 10) & 0x1F;
|
||||
const u8 g = (pixel_value >> 5) & 0x1F;
|
||||
const u8 b = pixel_value & 0x1F;
|
||||
row_dst[x] = (r << 3) | (g << 11) | (b << 19) | 0xFF000000u;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
case 24:
|
||||
{
|
||||
const u32 byte_index = bit_offset / 8;
|
||||
const u8 b = row_src[byte_index + 0];
|
||||
const u8 g = row_src[byte_index + 1];
|
||||
const u8 r = row_src[byte_index + 2];
|
||||
row_dst[x] = r | (g << 8) | (b << 16) | 0xFF000000u;
|
||||
bit_offset += 24;
|
||||
continue;
|
||||
}
|
||||
case 32:
|
||||
{
|
||||
const u32 byte_index = bit_offset / 8;
|
||||
u32 pixel_value = row_src[byte_index] | (row_src[byte_index + 1] << 8) | (row_src[byte_index + 2] << 16) | (row_src[byte_index + 3] << 24);
|
||||
bit_offset += 32;
|
||||
|
||||
if (info_header.compression == 3 || info_header.compression == 4) // BITFIELDS or ALPHABITFIELDS
|
||||
{
|
||||
// Calculate shifts
|
||||
auto calc_shift = [](u32 mask) -> u32 {
|
||||
u32 result = 0;
|
||||
while ((mask >= 0x100) || (!(mask & 1) && mask))
|
||||
{
|
||||
result++;
|
||||
mask >>= 1;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
// Calculate scales
|
||||
auto calc_scale = [](u32 low_mask) -> u32 {
|
||||
u32 result = 8;
|
||||
while (low_mask && result)
|
||||
{
|
||||
result--;
|
||||
low_mask >>= 1;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
// Apply scale
|
||||
auto apply_scale = [](u32 value, u32 scale) -> u8 {
|
||||
if (!(scale & 0x07)) // scale == 8 or 0
|
||||
return static_cast<u8>(value);
|
||||
u32 filled = 8 - scale;
|
||||
u32 result = value << scale;
|
||||
do
|
||||
{
|
||||
result |= result >> filled;
|
||||
filled <<= 1;
|
||||
} while (filled < 8);
|
||||
return static_cast<u8>(result);
|
||||
};
|
||||
|
||||
const u32 r_shift = calc_shift(red_mask);
|
||||
const u32 g_shift = calc_shift(green_mask);
|
||||
const u32 b_shift = calc_shift(blue_mask);
|
||||
const u32 a_shift = (alpha_mask != 0) ? calc_shift(alpha_mask) : 0;
|
||||
|
||||
const u32 r_scale = calc_scale(red_mask >> r_shift);
|
||||
const u32 g_scale = calc_scale(green_mask >> g_shift);
|
||||
const u32 b_scale = calc_scale(blue_mask >> b_shift);
|
||||
const u32 a_scale = (alpha_mask != 0) ? calc_scale(alpha_mask >> a_shift) : 0;
|
||||
|
||||
const u8 r = apply_scale((pixel_value & red_mask) >> r_shift, r_scale);
|
||||
const u8 g = apply_scale((pixel_value & green_mask) >> g_shift, g_scale);
|
||||
const u8 b = apply_scale((pixel_value & blue_mask) >> b_shift, b_scale);
|
||||
const u8 a = (use_alpha && alpha_mask != 0) ? apply_scale((pixel_value & alpha_mask) >> a_shift, a_scale) : 0xFF;
|
||||
|
||||
row_dst[x] = r | (g << 8) | (b << 16) | (a << 24);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Uncompressed 32-bit BGRA order
|
||||
const u8 b = row_src[byte_index + 0];
|
||||
const u8 g = row_src[byte_index + 1];
|
||||
const u8 r = row_src[byte_index + 2];
|
||||
const u8 a = row_src[byte_index + 3];
|
||||
row_dst[x] = r | (g << 8) | (b << 16) | (a << 24);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (info_header.bit_count <= 8)
|
||||
{
|
||||
if (pixel_value < palette.size())
|
||||
row_dst[x] = palette[pixel_value];
|
||||
else
|
||||
{
|
||||
Console.Error("Invalid palette index: %u (palette size: %zu)", pixel_value, palette.size());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LoadCompressedBMP(u32* pixels, const u8* src, u32 src_size, u32 width, u32 height, const BMPInfoHeader& info_header, const std::vector<u32>& palette, bool flip_vertical)
|
||||
{
|
||||
u32 src_pos = 0;
|
||||
const u32 pixel_size = (info_header.bit_count == 8) ? 1 : 2;
|
||||
|
||||
for (u32 y = 0; y < height; y++)
|
||||
{
|
||||
u32 dst_y = flip_vertical ? (height - 1 - y) : y;
|
||||
u32* row_dst = pixels + (dst_y * width);
|
||||
u32 x = 0;
|
||||
|
||||
while (x < width)
|
||||
{
|
||||
// Check bounds before reading
|
||||
if (src_pos + 2 > src_size)
|
||||
return false;
|
||||
|
||||
const u8 count = src[src_pos++];
|
||||
const u8 value = src[src_pos++];
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
if (value == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else if (value == 1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (value == 2)
|
||||
{
|
||||
// Delta (jump) need 2 more bytes
|
||||
if (src_pos + 2 > src_size)
|
||||
return false;
|
||||
const u8 dx = src[src_pos++];
|
||||
const u8 dy = src[src_pos++];
|
||||
x += dx;
|
||||
y += dy;
|
||||
if (y >= height || x >= width)
|
||||
return false;
|
||||
const u32 new_dst_y = flip_vertical ? (height - 1 - y) : y;
|
||||
row_dst = pixels + (new_dst_y * width);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Absolute mode need "value" bytes of pixel data
|
||||
const u32 run_length = value;
|
||||
const u32 bytes_needed = run_length * pixel_size;
|
||||
if (src_pos + bytes_needed > src_size)
|
||||
return false;
|
||||
|
||||
for (u32 i = 0; i < run_length; i++)
|
||||
{
|
||||
if (x >= width)
|
||||
break;
|
||||
|
||||
u8 pixel_value = 0;
|
||||
if (info_header.bit_count == 8)
|
||||
{
|
||||
pixel_value = src[src_pos++];
|
||||
}
|
||||
else
|
||||
{
|
||||
const u8 byte_val = src[src_pos++];
|
||||
pixel_value = (i % 2 == 0) ? (byte_val >> 4) : (byte_val & 0x0F);
|
||||
}
|
||||
|
||||
row_dst[x++] = (pixel_value < palette.size()) ? palette[pixel_value] : 0;
|
||||
}
|
||||
|
||||
if ((run_length * pixel_size) % 2 == 1)
|
||||
src_pos++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
u8 pixel_value = value;
|
||||
|
||||
for (u32 i = 0; i < count; i++)
|
||||
{
|
||||
if (x >= width)
|
||||
break;
|
||||
row_dst[x++] = (pixel_value < palette.size()) ? palette[pixel_value] : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BMPBufferLoader(RGBA8Image* image, const void* buffer, size_t buffer_size)
|
||||
{
|
||||
if (buffer_size < sizeof(BMPFileHeader) + sizeof(BMPInfoHeader))
|
||||
{
|
||||
Console.Error("BMP file too small");
|
||||
return false;
|
||||
}
|
||||
|
||||
const u8* data = static_cast<const u8*>(buffer);
|
||||
BMPFileHeader file_header;
|
||||
BMPInfoHeader info_header;
|
||||
|
||||
std::memcpy(&file_header, data, sizeof(BMPFileHeader));
|
||||
std::memcpy(&info_header, data + sizeof(BMPFileHeader), sizeof(BMPInfoHeader));
|
||||
|
||||
if (file_header.type != 0x4D42)
|
||||
{
|
||||
Console.Error("Invalid BMP signature");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for extended header versions (V4=108 bytes, V5=124 bytes)
|
||||
// We read as BITMAPINFOHEADER (40 bytes) regardless, since extended headers just add fields at the end
|
||||
if (info_header.size == 108)
|
||||
{
|
||||
Console.Warning("BITMAPV4HEADER detected, reading as BITMAPINFOHEADER");
|
||||
}
|
||||
else if (info_header.size == 124)
|
||||
{
|
||||
Console.Warning("BITMAPV5HEADER detected, reading as BITMAPINFOHEADER");
|
||||
}
|
||||
else if (info_header.size != 40)
|
||||
{
|
||||
Console.Warning("Unknown BMP header size: %u, attempting to read as BITMAPINFOHEADER", info_header.size);
|
||||
}
|
||||
|
||||
if (!IsSupportedBMPFormat(info_header.compression, info_header.bit_count))
|
||||
{
|
||||
Console.Error("Unsupported BMP format: compression=%u, bit_count=%u", info_header.compression, info_header.bit_count);
|
||||
return false;
|
||||
}
|
||||
|
||||
const u32 width = static_cast<u32>(std::abs(info_header.width));
|
||||
const u32 height = static_cast<u32>(std::abs(info_header.height));
|
||||
const bool flip_vertical = (info_header.height > 0);
|
||||
|
||||
if (width == 0 || height == 0)
|
||||
{
|
||||
Console.Error("Invalid BMP dimensions: %ux%u", width, height);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (width > 65536 || height > 65536)
|
||||
{
|
||||
Console.Error("BMP dimensions too large: %ux%u", width, height);
|
||||
return false;
|
||||
}
|
||||
|
||||
Console.WriteLn("BMP: %ux%u, %u-bit, compression=%u", width, height, info_header.bit_count, info_header.compression);
|
||||
|
||||
// Read color masks from header or bitfields
|
||||
u32 red_mask = 0;
|
||||
u32 green_mask = 0;
|
||||
u32 blue_mask = 0;
|
||||
u32 alpha_mask = 0;
|
||||
const bool bitfields = (info_header.compression == 3 || info_header.compression == 4); // BMP_BITFIELDS or BMP_ALPHABITFIELDS
|
||||
const u8* header_start = data + sizeof(BMPFileHeader);
|
||||
const u32 header_base_offset = sizeof(BMPFileHeader) + 40; // Base header is 40 bytes
|
||||
|
||||
if (info_header.size >= 108) // BMP_WIN4 (108) or BMP_WIN5 (124)
|
||||
{
|
||||
// V4/V5 headers masks come right after the 40-byte base header
|
||||
// Masks are at offsets from header_start: red=40, green=44, blue=48, alpha=52
|
||||
if (buffer_size >= header_base_offset + 16) // Need space for 4 masks
|
||||
{
|
||||
red_mask = *reinterpret_cast<const u32*>(header_start + 40);
|
||||
green_mask = *reinterpret_cast<const u32*>(header_start + 44);
|
||||
blue_mask = *reinterpret_cast<const u32*>(header_start + 48);
|
||||
alpha_mask = *reinterpret_cast<const u32*>(header_start + 52);
|
||||
}
|
||||
}
|
||||
else if (bitfields && (info_header.bit_count == 16 || info_header.bit_count == 32))
|
||||
{
|
||||
const u32 bitfields_offset = sizeof(BMPFileHeader) + info_header.size;
|
||||
if (buffer_size >= bitfields_offset + 12) // Need space for at least r/g/b masks
|
||||
{
|
||||
red_mask = *reinterpret_cast<const u32*>(data + bitfields_offset);
|
||||
green_mask = *reinterpret_cast<const u32*>(data + bitfields_offset + 4);
|
||||
blue_mask = *reinterpret_cast<const u32*>(data + bitfields_offset + 8);
|
||||
if (info_header.compression == 4) // BMP_ALPHABITFIELDS
|
||||
{
|
||||
// Read alpha mask: r, g, b, a
|
||||
if (buffer_size >= bitfields_offset + 16)
|
||||
alpha_mask = *reinterpret_cast<const u32*>(data + bitfields_offset + 12);
|
||||
}
|
||||
// For BMP_BITFIELDS (3), alpha_mask stays 0
|
||||
}
|
||||
}
|
||||
|
||||
bool use_alpha = bitfields || (info_header.compression == 0 && info_header.bit_count == 32 && alpha_mask == 0xff000000);
|
||||
use_alpha = use_alpha && (alpha_mask != 0);
|
||||
|
||||
const u32 bytes_per_pixel = info_header.bit_count / 8;
|
||||
const u32 row_size = ((width * bytes_per_pixel + 3) / 4) * 4;
|
||||
|
||||
// For uncompressed BMPs, verify we have enough data
|
||||
// For RLE-compressed BMPs, size is variable so we check differently
|
||||
if (info_header.compression == 0)
|
||||
{
|
||||
if (file_header.offset + (row_size * height) > buffer_size)
|
||||
{
|
||||
Console.Error("BMP file data incomplete");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// For RLE-compressed BMPs, check that we have at least the offset and some data
|
||||
// Use biSizeImage if available, otherwise just verify offset is valid
|
||||
if (file_header.offset >= buffer_size)
|
||||
{
|
||||
Console.Error("BMP file data incomplete");
|
||||
return false;
|
||||
}
|
||||
if (info_header.size_image > 0)
|
||||
{
|
||||
if (file_header.offset + info_header.size_image > buffer_size)
|
||||
{
|
||||
Console.Error("BMP file data incomplete");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<u32> pixels;
|
||||
pixels.resize(width * height);
|
||||
|
||||
const u8* src = data + file_header.offset;
|
||||
const u32 src_size = buffer_size - file_header.offset;
|
||||
|
||||
std::vector<u32> palette;
|
||||
if (info_header.bit_count <= 8)
|
||||
{
|
||||
if (!LoadBMPPalette(palette, data, file_header.offset, info_header))
|
||||
{
|
||||
Console.Error("Failed to load BMP palette");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (info_header.compression == 0 || info_header.compression == 3 || info_header.compression == 4)
|
||||
{
|
||||
if (!LoadUncompressedBMP(pixels.data(), src, data, width, height, info_header, palette, flip_vertical, red_mask, green_mask, blue_mask, alpha_mask, use_alpha))
|
||||
{
|
||||
Console.Error("Failed to load uncompressed BMP data");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!LoadCompressedBMP(pixels.data(), src, src_size, width, height, info_header, palette, flip_vertical))
|
||||
{
|
||||
Console.Error("Failed to load compressed BMP data");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle alpha channel for 32-bit BMPs
|
||||
// Only use alpha if alpha_mask is explicitly set in header/bitfields
|
||||
if (info_header.bit_count == 32 && !use_alpha)
|
||||
{
|
||||
// Alpha mask not set or zero - set all pixels to fully opaque
|
||||
for (u32& pixel : pixels)
|
||||
pixel |= 0xFF000000u;
|
||||
}
|
||||
|
||||
image->SetPixels(width, height, std::move(pixels));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BMPFileLoader(RGBA8Image* image, const char* filename, std::FILE* fp)
|
||||
{
|
||||
std::optional<std::vector<u8>> data = FileSystem::ReadBinaryFile(fp);
|
||||
if (!data.has_value())
|
||||
return false;
|
||||
|
||||
return BMPBufferLoader(image, data->data(), data->size());
|
||||
}
|
||||
|
||||
bool BMPBufferSaver(const RGBA8Image& image, std::vector<u8>* buffer, u8 quality)
|
||||
{
|
||||
const u32 width = image.GetWidth();
|
||||
const u32 height = image.GetHeight();
|
||||
|
||||
// Check dimensions
|
||||
if (width == 0 || height == 0)
|
||||
{
|
||||
Console.Error("Invalid BMP dimensions: %ux%u", width, height);
|
||||
return false;
|
||||
}
|
||||
|
||||
const u32 row_size = ((width * 3 + 3) / 4) * 4;
|
||||
const u32 image_size = row_size * height;
|
||||
const u32 file_size = sizeof(BMPFileHeader) + sizeof(BMPInfoHeader) + image_size;
|
||||
|
||||
buffer->resize(file_size);
|
||||
u8* data = buffer->data();
|
||||
|
||||
BMPFileHeader file_header = {};
|
||||
file_header.type = 0x4D42;
|
||||
file_header.size = file_size;
|
||||
file_header.reserved1 = 0;
|
||||
file_header.reserved2 = 0;
|
||||
file_header.offset = sizeof(BMPFileHeader) + sizeof(BMPInfoHeader);
|
||||
std::memcpy(data, &file_header, sizeof(BMPFileHeader));
|
||||
|
||||
BMPInfoHeader info_header = {};
|
||||
info_header.size = sizeof(BMPInfoHeader);
|
||||
info_header.width = static_cast<s32>(width);
|
||||
info_header.height = static_cast<s32>(height);
|
||||
info_header.planes = 1;
|
||||
info_header.bit_count = 24;
|
||||
info_header.compression = 0;
|
||||
info_header.size_image = image_size;
|
||||
info_header.x_pels_per_meter = 0;
|
||||
info_header.y_pels_per_meter = 0;
|
||||
info_header.clr_used = 0;
|
||||
info_header.clr_important = 0;
|
||||
std::memcpy(data + sizeof(BMPFileHeader), &info_header, sizeof(BMPInfoHeader));
|
||||
|
||||
u8* pixel_data = data + file_header.offset;
|
||||
for (u32 y = 0; y < height; y++)
|
||||
{
|
||||
const u32 src_y = height - 1 - y;
|
||||
const u32* row_src = image.GetRowPixels(src_y);
|
||||
u8* row_dst = pixel_data + (y * row_size);
|
||||
|
||||
for (u32 x = 0; x < width; x++)
|
||||
{
|
||||
const u32 rgba = row_src[x];
|
||||
row_dst[x * 3 + 0] = static_cast<u8>((rgba >> 16) & 0xFF);
|
||||
row_dst[x * 3 + 1] = static_cast<u8>((rgba >> 8) & 0xFF);
|
||||
row_dst[x * 3 + 2] = static_cast<u8>(rgba & 0xFF);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BMPFileSaver(const RGBA8Image& image, const char* filename, std::FILE* fp, u8 quality)
|
||||
{
|
||||
std::vector<u8> buffer;
|
||||
if (!BMPBufferSaver(image, &buffer, quality))
|
||||
return false;
|
||||
|
||||
return (std::fwrite(buffer.data(), buffer.size(), 1, fp) == 1);
|
||||
}
|
||||
|
||||
@@ -106,6 +106,11 @@ namespace Common
|
||||
Reset();
|
||||
}
|
||||
|
||||
Timer::Timer(Value start_value)
|
||||
{
|
||||
m_tvStartValue = start_value;
|
||||
}
|
||||
|
||||
void Timer::Reset()
|
||||
{
|
||||
m_tvStartValue = GetCurrentValue();
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace Common
|
||||
using Value = std::uint64_t;
|
||||
|
||||
Timer();
|
||||
Timer (Value start_value);
|
||||
|
||||
static Value GetCurrentValue();
|
||||
static double ConvertValueToSeconds(Value value);
|
||||
|
||||
@@ -100,54 +100,16 @@ void Common::SetMousePosition(int x, int y)
|
||||
SetCursorPos(x, y);
|
||||
}
|
||||
|
||||
/*
|
||||
static HHOOK mouseHook = nullptr;
|
||||
static std::function<void(int, int)> fnMouseMoveCb;
|
||||
LRESULT CALLBACK Mousecb(int nCode, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
if (nCode >= 0 && wParam == WM_MOUSEMOVE)
|
||||
{
|
||||
MSLLHOOKSTRUCT* mouse = (MSLLHOOKSTRUCT*)lParam;
|
||||
fnMouseMoveCb(mouse->pt.x, mouse->pt.y);
|
||||
}
|
||||
return CallNextHookEx(mouseHook, nCode, wParam, lParam);
|
||||
}
|
||||
*/
|
||||
|
||||
// This (and the above) works, but is not recommended on Windows and is only here for consistency.
|
||||
// Defer to using raw input instead.
|
||||
bool Common::AttachMousePositionCb(std::function<void(int, int)> cb)
|
||||
{
|
||||
/*
|
||||
if (mouseHook)
|
||||
Common::DetachMousePositionCb();
|
||||
|
||||
fnMouseMoveCb = cb;
|
||||
mouseHook = SetWindowsHookEx(WH_MOUSE_LL, Mousecb, GetModuleHandle(NULL), 0);
|
||||
if (!mouseHook)
|
||||
{
|
||||
Console.Warning("Failed to set mouse hook: %d", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(PCSX2_DEBUG) || defined(PCSX2_DEVBUILD)
|
||||
static bool warned = false;
|
||||
if (!warned)
|
||||
{
|
||||
Console.Warning("Mouse hooks are enabled, and this isn't a release build! Using a debugger, or loading symbols, _will_ stall the hook and cause global mouse lag.");
|
||||
warned = true;
|
||||
}
|
||||
#endif
|
||||
*/
|
||||
// We use raw input messages which are handled by the windows message loop.
|
||||
// The alternative is to use a low-level mouse hook, but this passes Windows all mouse messages to PCSX2.
|
||||
// If PCSX2 hangs, or you attach a debugger, the mouse will stop working system-wide.
|
||||
return true;
|
||||
}
|
||||
|
||||
void Common::DetachMousePositionCb()
|
||||
{
|
||||
/*
|
||||
UnhookWindowsHookEx(mouseHook);
|
||||
mouseHook = nullptr;
|
||||
*/
|
||||
}
|
||||
|
||||
bool Common::PlaySoundAsync(const char* path)
|
||||
|
||||
43
common/YAML.cpp
Normal file
43
common/YAML.cpp
Normal file
@@ -0,0 +1,43 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "YAML.h"
|
||||
|
||||
#include <csetjmp>
|
||||
#include <cstdlib>
|
||||
|
||||
struct RapidYAMLContext
|
||||
{
|
||||
std::jmp_buf env;
|
||||
Error* error = nullptr;
|
||||
};
|
||||
|
||||
std::optional<ryml::Tree> ParseYAMLFromString(ryml::csubstr yaml, ryml::csubstr file_name, Error* error)
|
||||
{
|
||||
RapidYAMLContext context;
|
||||
context.error = error;
|
||||
|
||||
ryml::Callbacks callbacks;
|
||||
callbacks.m_user_data = static_cast<void*>(&context);
|
||||
callbacks.m_error = [](const char* msg, size_t msg_len, ryml::Location location, void* user_data) {
|
||||
RapidYAMLContext* context = static_cast<RapidYAMLContext*>(user_data);
|
||||
|
||||
Error::SetString(context->error, std::string(msg, msg_len));
|
||||
std::longjmp(context->env, 1);
|
||||
};
|
||||
|
||||
ryml::EventHandlerTree event_handler(callbacks);
|
||||
ryml::Parser parser(&event_handler);
|
||||
|
||||
ryml::Tree tree;
|
||||
|
||||
// The only options RapidYAML provides for recovering from errors are
|
||||
// throwing an exception or using setjmp/longjmp. Since we have exceptions
|
||||
// disabled we have to use the latter option.
|
||||
if (setjmp(context.env))
|
||||
return std::nullopt;
|
||||
|
||||
ryml::parse_in_arena(&parser, file_name, yaml, &tree);
|
||||
|
||||
return tree;
|
||||
}
|
||||
18
common/YAML.h
Normal file
18
common/YAML.h
Normal file
@@ -0,0 +1,18 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Error.h"
|
||||
|
||||
#include "ryml_std.hpp"
|
||||
#include "ryml.hpp"
|
||||
#include "ryml.hpp"
|
||||
|
||||
#include <optional>
|
||||
|
||||
/// Parse a YAML file with RapidYAML, and use setjmp/longjmp to recover from
|
||||
/// parsing errors (as is recommended by the documentation for cases where
|
||||
/// exceptions are disabled). The file_name parameter is only used for error
|
||||
/// messages, which are returned via the error parameter.
|
||||
std::optional<ryml::Tree> ParseYAMLFromString(ryml::csubstr yaml, ryml::csubstr file_name, Error* error);
|
||||
@@ -36,10 +36,12 @@
|
||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\fast_float\include</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\fmt\include</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\jpgd</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\rapidyaml\include</AdditionalIncludeDirectories>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<ForcedIncludeFiles>PrecompiledHeader.h</ForcedIncludeFiles>
|
||||
<PrecompiledHeaderFile>PrecompiledHeader.h</PrecompiledHeaderFile>
|
||||
<ObjectFileName>$(IntDir)%(RelativeDir)</ObjectFileName>
|
||||
<PreprocessorDefinitions>C4_NO_DEBUG_BREAK;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
</ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
@@ -71,6 +73,7 @@
|
||||
<ClCompile Include="Timer.cpp" />
|
||||
<ClCompile Include="WAVWriter.cpp" />
|
||||
<ClCompile Include="WindowInfo.cpp" />
|
||||
<ClCompile Include="YAML.cpp" />
|
||||
<ClCompile Include="Perf.cpp" />
|
||||
<ClCompile Include="PrecompiledHeader.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
@@ -157,6 +160,7 @@
|
||||
<ClInclude Include="Timer.h" />
|
||||
<ClInclude Include="WAVWriter.h" />
|
||||
<ClInclude Include="WindowInfo.h" />
|
||||
<ClInclude Include="YAML.h" />
|
||||
<ClInclude Include="Threading.h" />
|
||||
<ClInclude Include="emitter\implement\avx.h" />
|
||||
<ClInclude Include="emitter\implement\bmi.h" />
|
||||
|
||||
@@ -127,6 +127,9 @@
|
||||
<ClCompile Include="SmallString.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="YAML.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="AlignedMalloc.h">
|
||||
@@ -335,6 +338,9 @@
|
||||
</ClInclude>
|
||||
<ClInclude Include="SingleRegisterTypes.h" />
|
||||
<ClInclude Include="FPControl.h" />
|
||||
<ClInclude Include="YAML.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
@@ -349,4 +355,4 @@
|
||||
<Filter>Source Files</Filter>
|
||||
</MASM>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
@@ -59,6 +59,7 @@ namespace GSRunner
|
||||
{
|
||||
static void InitializeConsole();
|
||||
static bool InitializeConfig();
|
||||
static void SettingsOverride();
|
||||
static bool ParseCommandLineArgs(int argc, char* argv[], VMBootParameters& params);
|
||||
static void DumpStats();
|
||||
|
||||
@@ -119,42 +120,6 @@ bool GSRunner::InitializeConfig()
|
||||
|
||||
VMManager::SetDefaultSettings(si, true, true, true, true, true);
|
||||
|
||||
// complete as quickly as possible
|
||||
si.SetBoolValue("EmuCore/GS", "FrameLimitEnable", false);
|
||||
si.SetIntValue("EmuCore/GS", "VsyncEnable", false);
|
||||
|
||||
// Force screenshot quality settings to something more performant, overriding any defaults good for users.
|
||||
si.SetIntValue("EmuCore/GS", "ScreenshotFormat", static_cast<int>(GSScreenshotFormat::PNG));
|
||||
si.SetIntValue("EmuCore/GS", "ScreenshotQuality", 10);
|
||||
|
||||
// ensure all input sources are disabled, we're not using them
|
||||
si.SetBoolValue("InputSources", "SDL", false);
|
||||
si.SetBoolValue("InputSources", "XInput", false);
|
||||
|
||||
// we don't need any sound output
|
||||
si.SetStringValue("SPU2/Output", "OutputModule", "nullout");
|
||||
|
||||
// none of the bindings are going to resolve to anything
|
||||
Pad::ClearPortBindings(si, 0);
|
||||
si.ClearSection("Hotkeys");
|
||||
|
||||
// force logging
|
||||
si.SetBoolValue("Logging", "EnableSystemConsole", !s_no_console);
|
||||
si.SetBoolValue("Logging", "EnableTimestamps", true);
|
||||
si.SetBoolValue("Logging", "EnableVerbose", true);
|
||||
|
||||
// and show some stats :)
|
||||
si.SetBoolValue("EmuCore/GS", "OsdShowFPS", true);
|
||||
si.SetBoolValue("EmuCore/GS", "OsdShowResolution", true);
|
||||
si.SetBoolValue("EmuCore/GS", "OsdShowGSStats", true);
|
||||
|
||||
// remove memory cards, so we don't have sharing violations
|
||||
for (u32 i = 0; i < 2; i++)
|
||||
{
|
||||
si.SetBoolValue("MemoryCards", fmt::format("Slot{}_Enable", i + 1).c_str(), false);
|
||||
si.SetStringValue("MemoryCards", fmt::format("Slot{}_Filename", i + 1).c_str(), "");
|
||||
}
|
||||
|
||||
VMManager::Internal::LoadStartupSettings();
|
||||
return true;
|
||||
}
|
||||
@@ -481,8 +446,8 @@ static void PrintCommandLineHelp(const char* progname)
|
||||
std::fprintf(stderr, " -help: Displays this information and exits.\n");
|
||||
std::fprintf(stderr, " -version: Displays version information and exits.\n");
|
||||
std::fprintf(stderr, " -dumpdir <dir>: Frame dump directory (will be dumped as filename_frameN.png).\n");
|
||||
std::fprintf(stderr, " -dump [rt|tex|z|f|a|i|tr]: Enabling dumping of render target, texture, z buffer, frame, "
|
||||
"alphas, and info (context, vertices, transfers (list)), transfers (images), respectively, per draw. Generates lots of data.\n");
|
||||
std::fprintf(stderr, " -dump [rt|tex|z|f|a|i|tr|ds|fs]: Enabling dumping of render target, texture, z buffer, frame, "
|
||||
"alphas, and info (context, vertices, list of transfers), transfers images, draw stats, frame stats, respectively, per draw. Generates lots of data.\n");
|
||||
std::fprintf(stderr, " -dumprange N[,L,B]: Start dumping from draw N (base 0), stops after L draws, and only "
|
||||
"those draws that are multiples of B (intersection of -dumprange and -dumprangef used)."
|
||||
"Defaults to 0,-1,1 (all draws). Only used if -dump used.\n");
|
||||
@@ -568,6 +533,10 @@ bool GSRunner::ParseCommandLineArgs(int argc, char* argv[], VMBootParameters& pa
|
||||
s_settings_interface.SetBoolValue("EmuCore/GS", "SaveInfo", true);
|
||||
if (str.find("tr") != std::string::npos)
|
||||
s_settings_interface.SetBoolValue("EmuCore/GS", "SaveTransferImages", true);
|
||||
if (str.find("ds") != std::string::npos)
|
||||
s_settings_interface.SetBoolValue("EmuCore/GS", "SaveDrawStats", true);
|
||||
if (str.find("fs") != std::string::npos)
|
||||
s_settings_interface.SetBoolValue("EmuCore/GS", "SaveFrameStats", true);
|
||||
continue;
|
||||
}
|
||||
else if (CHECK_ARG_PARAM("-dumprange"))
|
||||
@@ -709,6 +678,28 @@ bool GSRunner::ParseCommandLineArgs(int argc, char* argv[], VMBootParameters& pa
|
||||
|
||||
continue;
|
||||
}
|
||||
else if (CHECK_ARG_PARAM("-ini"))
|
||||
{
|
||||
std::string path = std::string(StringUtil::StripWhitespace(argv[++i]));
|
||||
if (!FileSystem::FileExists(path.c_str()))
|
||||
{
|
||||
Console.ErrorFmt("INI file {} does not exit.", path);
|
||||
return false;
|
||||
}
|
||||
|
||||
INISettingsInterface si_ini(path);
|
||||
|
||||
if (!si_ini.Load())
|
||||
{
|
||||
Console.ErrorFmt("Unable to load INI settings from {}.", path);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto& [key, value] : si_ini.GetKeyValueList("EmuCore/GS"))
|
||||
s_settings_interface.SetStringValue("EmuCore/GS", key.c_str(), value.c_str());
|
||||
|
||||
continue;
|
||||
}
|
||||
else if (CHECK_ARG_PARAM("-upscale"))
|
||||
{
|
||||
const float upscale = StringUtil::FromChars<float>(argv[++i]).value_or(0.0f);
|
||||
@@ -739,7 +730,7 @@ bool GSRunner::ParseCommandLineArgs(int argc, char* argv[], VMBootParameters& pa
|
||||
else if (CHECK_ARG("-noshadercache"))
|
||||
{
|
||||
Console.WriteLn("Disabling shader cache");
|
||||
s_settings_interface.SetBoolValue("EmuCore/GS", "disable_shader_cache", true);
|
||||
s_settings_interface.SetBoolValue("EmuCore/GS", "DisableShaderCache", true);
|
||||
continue;
|
||||
}
|
||||
else if (CHECK_ARG("-window"))
|
||||
@@ -813,6 +804,45 @@ bool GSRunner::ParseCommandLineArgs(int argc, char* argv[], VMBootParameters& pa
|
||||
return true;
|
||||
}
|
||||
|
||||
void GSRunner::SettingsOverride()
|
||||
{
|
||||
// complete as quickly as possible
|
||||
s_settings_interface.SetBoolValue("EmuCore/GS", "FrameLimitEnable", false);
|
||||
s_settings_interface.SetIntValue("EmuCore/GS", "VsyncEnable", false);
|
||||
|
||||
// Force screenshot quality settings to something more performant, overriding any defaults good for users.
|
||||
s_settings_interface.SetIntValue("EmuCore/GS", "ScreenshotFormat", static_cast<int>(GSScreenshotFormat::PNG));
|
||||
s_settings_interface.SetIntValue("EmuCore/GS", "ScreenshotQuality", 10);
|
||||
|
||||
// ensure all input sources are disabled, we're not using them
|
||||
s_settings_interface.SetBoolValue("InputSources", "SDL", false);
|
||||
s_settings_interface.SetBoolValue("InputSources", "XInput", false);
|
||||
|
||||
// we don't need any sound output
|
||||
s_settings_interface.SetStringValue("SPU2/Output", "OutputModule", "nullout");
|
||||
|
||||
// none of the bindings are going to resolve to anything
|
||||
Pad::ClearPortBindings(s_settings_interface, 0);
|
||||
s_settings_interface.ClearSection("Hotkeys");
|
||||
|
||||
// force logging
|
||||
s_settings_interface.SetBoolValue("Logging", "EnableSystemConsole", !s_no_console);
|
||||
s_settings_interface.SetBoolValue("Logging", "EnableTimestamps", true);
|
||||
s_settings_interface.SetBoolValue("Logging", "EnableVerbose", true);
|
||||
|
||||
// and show some stats :)
|
||||
s_settings_interface.SetBoolValue("EmuCore/GS", "OsdShowFPS", true);
|
||||
s_settings_interface.SetBoolValue("EmuCore/GS", "OsdShowResolution", true);
|
||||
s_settings_interface.SetBoolValue("EmuCore/GS", "OsdShowGSStats", true);
|
||||
|
||||
// remove memory cards, so we don't have sharing violations
|
||||
for (u32 i = 0; i < 2; i++)
|
||||
{
|
||||
s_settings_interface.SetBoolValue("MemoryCards", fmt::format("Slot{}_Enable", i + 1).c_str(), false);
|
||||
s_settings_interface.SetStringValue("MemoryCards", fmt::format("Slot{}_Filename", i + 1).c_str(), "");
|
||||
}
|
||||
}
|
||||
|
||||
void GSRunner::DumpStats()
|
||||
{
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
@@ -831,16 +861,27 @@ void GSRunner::DumpStats()
|
||||
#define main real_main
|
||||
#endif
|
||||
|
||||
static void CPUThreadMain(VMBootParameters* params) {
|
||||
if (VMManager::Initialize(*params))
|
||||
static void CPUThreadMain(VMBootParameters* params, std::atomic<int>* ret)
|
||||
{
|
||||
ret->store(EXIT_FAILURE);
|
||||
|
||||
if (VMManager::Internal::CPUThreadInitialize())
|
||||
{
|
||||
// run until end
|
||||
GSDumpReplayer::SetLoopCount(s_loop_count);
|
||||
VMManager::SetState(VMState::Running);
|
||||
while (VMManager::GetState() == VMState::Running)
|
||||
VMManager::Execute();
|
||||
VMManager::Shutdown(false);
|
||||
GSRunner::DumpStats();
|
||||
// apply new settings (e.g. pick up renderer change)
|
||||
VMManager::ApplySettings();
|
||||
GSDumpReplayer::SetIsDumpRunner(true);
|
||||
|
||||
if (VMManager::Initialize(*params))
|
||||
{
|
||||
// run until end
|
||||
GSDumpReplayer::SetLoopCount(s_loop_count);
|
||||
VMManager::SetState(VMState::Running);
|
||||
while (VMManager::GetState() == VMState::Running)
|
||||
VMManager::Execute();
|
||||
VMManager::Shutdown(false);
|
||||
GSRunner::DumpStats();
|
||||
ret->store(EXIT_SUCCESS);
|
||||
}
|
||||
}
|
||||
|
||||
VMManager::Internal::CPUThreadShutdown();
|
||||
@@ -862,27 +903,23 @@ int main(int argc, char* argv[])
|
||||
if (!GSRunner::ParseCommandLineArgs(argc, argv, params))
|
||||
return EXIT_FAILURE;
|
||||
|
||||
if (!VMManager::Internal::CPUThreadInitialize())
|
||||
return EXIT_FAILURE;
|
||||
|
||||
if (s_use_window.value_or(true) && !GSRunner::CreatePlatformWindow())
|
||||
{
|
||||
Console.Error("Failed to create window.");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// apply new settings (e.g. pick up renderer change)
|
||||
VMManager::ApplySettings();
|
||||
GSDumpReplayer::SetIsDumpRunner(true);
|
||||
// Override settings that shouldn't be picked up from defaults or INIs.
|
||||
GSRunner::SettingsOverride();
|
||||
|
||||
std::thread cputhread(CPUThreadMain, ¶ms);
|
||||
std::atomic<int> thread_ret;
|
||||
std::thread cputhread(CPUThreadMain, ¶ms, &thread_ret);
|
||||
GSRunner::PumpPlatformMessages(/*forever=*/true);
|
||||
cputhread.join();
|
||||
|
||||
VMManager::Internal::CPUThreadShutdown();
|
||||
GSRunner::DestroyPlatformWindow();
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
return thread_ret.load();
|
||||
}
|
||||
|
||||
void Host::PumpMessagesOnCPUThread()
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "common/SmallString.h"
|
||||
|
||||
#include <QtCore/QFile>
|
||||
#include <QtCore/QFileInfo>
|
||||
#include <QtCore/QString>
|
||||
#include <QtGui/QDesktopServices>
|
||||
#include <QtWidgets/QDialog>
|
||||
@@ -134,12 +135,16 @@ void AboutDialog::showHTMLDialog(QWidget* parent, const QString& title, const QS
|
||||
tb->setOpenExternalLinks(true);
|
||||
|
||||
QFile file(path);
|
||||
file.open(QIODevice::ReadOnly);
|
||||
if (const QByteArray data = file.readAll(); !data.isEmpty())
|
||||
tb->setText(QString::fromUtf8(data));
|
||||
else
|
||||
QFileInfo fi(path);
|
||||
if (!fi.exists() || !fi.isReadable())
|
||||
{
|
||||
tb->setText(tr("File not found: %1").arg(path));
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
tb->setSource(QUrl::fromLocalFile(path));
|
||||
}
|
||||
|
||||
layout->addWidget(tb, 1);
|
||||
|
||||
QDialogButtonBox* bb = new QDialogButtonBox(QDialogButtonBox::Close, &dialog);
|
||||
|
||||
@@ -81,6 +81,9 @@
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::TextBrowserInteraction</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
@@ -115,6 +118,9 @@
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::TextBrowserInteraction</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
|
||||
@@ -264,6 +264,14 @@ target_sources(pcsx2-qt PRIVATE
|
||||
resources/resources.qrc
|
||||
)
|
||||
|
||||
if (NOT APPLE)
|
||||
target_sources(pcsx2-qt PRIVATE
|
||||
ShortcutCreationDialog.cpp
|
||||
ShortcutCreationDialog.h
|
||||
ShortcutCreationDialog.ui
|
||||
)
|
||||
endif()
|
||||
|
||||
file(GLOB TS_FILES ${CMAKE_CURRENT_SOURCE_DIR}/Translations/*.ts)
|
||||
|
||||
target_precompile_headers(pcsx2-qt PRIVATE PrecompiledHeader.h)
|
||||
|
||||
@@ -43,6 +43,9 @@
|
||||
<property name="buddy">
|
||||
<cstring>label</cstring>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::TextBrowserInteraction</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
@@ -58,6 +61,9 @@
|
||||
<property name="buddy">
|
||||
<cstring>urls</cstring>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::TextBrowserInteraction</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
@@ -74,6 +80,9 @@
|
||||
<property name="buddy">
|
||||
<cstring>useTitleFileNames</cstring>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::TextBrowserInteraction</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
|
||||
@@ -34,6 +34,9 @@
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::TextBrowserInteraction</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
|
||||
@@ -153,7 +153,7 @@ void DebuggerSettingsManager::saveGameSettings(QAbstractTableModel* abstractTabl
|
||||
if (path.empty())
|
||||
return;
|
||||
|
||||
const std::lock_guard<std::mutex> lock(writeLock);
|
||||
std::lock_guard<std::mutex> lock(writeLock);
|
||||
QJsonObject loadedSettings = loadGameSettingsJSON();
|
||||
QJsonArray rowsArray;
|
||||
QStringList keys;
|
||||
|
||||
@@ -238,6 +238,12 @@ int DebuggerWindow::fontSize()
|
||||
|
||||
void DebuggerWindow::updateTheme()
|
||||
{
|
||||
// Detect recursive StyleChange events caused by updating the stylesheet.
|
||||
if (m_is_updating_theme)
|
||||
return;
|
||||
|
||||
m_is_updating_theme = true;
|
||||
|
||||
// TODO: Migrate away from stylesheets to improve performance.
|
||||
setStyleSheet(QString("font-size: %1pt;").arg(m_font_size));
|
||||
|
||||
@@ -248,6 +254,8 @@ void DebuggerWindow::updateTheme()
|
||||
setStyleSheet(QString());
|
||||
|
||||
dockManager().updateTheme();
|
||||
|
||||
m_is_updating_theme = false;
|
||||
}
|
||||
|
||||
void DebuggerWindow::saveWindowGeometry()
|
||||
@@ -535,6 +543,12 @@ void DebuggerWindow::onStepOut()
|
||||
this->repaint();
|
||||
}
|
||||
|
||||
void DebuggerWindow::changeEvent(QEvent* event)
|
||||
{
|
||||
if (event->type() == QEvent::PaletteChange || event->type() == QEvent::StyleChange)
|
||||
updateTheme();
|
||||
}
|
||||
|
||||
void DebuggerWindow::closeEvent(QCloseEvent* event)
|
||||
{
|
||||
dockManager().saveCurrentLayout();
|
||||
|
||||
@@ -60,7 +60,8 @@ Q_SIGNALS:
|
||||
void onVMActuallyPaused();
|
||||
|
||||
protected:
|
||||
void closeEvent(QCloseEvent* event);
|
||||
void changeEvent(QEvent* event) override;
|
||||
void closeEvent(QCloseEvent* event) override;
|
||||
|
||||
private:
|
||||
DebugInterface* currentCPU();
|
||||
@@ -75,6 +76,8 @@ private:
|
||||
int m_font_size;
|
||||
static const constexpr int MINIMUM_FONT_SIZE = 5;
|
||||
static const constexpr int MAXIMUM_FONT_SIZE = 30;
|
||||
|
||||
bool m_is_updating_theme = false;
|
||||
};
|
||||
|
||||
extern DebuggerWindow* g_debugger_window;
|
||||
|
||||
@@ -118,11 +118,11 @@ void DisassemblyView::contextPasteInstructionText()
|
||||
// split text in clipboard by new lines
|
||||
QString clipboardText = QApplication::clipboard()->text();
|
||||
std::vector<std::string> newInstructions = StringUtil::splitOnNewLine(clipboardText.toLocal8Bit().constData());
|
||||
int newInstructionsSize = newInstructions.size();
|
||||
u32 newInstructionsSize = static_cast<u32>(newInstructions.size());
|
||||
|
||||
// validate new instructions before pasting them
|
||||
std::vector<u32> encodedInstructions;
|
||||
for (int instructionIdx = 0; instructionIdx < newInstructionsSize; instructionIdx++)
|
||||
for (u32 instructionIdx = 0; instructionIdx < newInstructionsSize; instructionIdx++)
|
||||
{
|
||||
u32 replaceAddress = m_selectedAddressStart + instructionIdx * 4;
|
||||
u32 encodedInstruction;
|
||||
@@ -137,7 +137,7 @@ void DisassemblyView::contextPasteInstructionText()
|
||||
}
|
||||
|
||||
// paste validated instructions
|
||||
for (int instructionIdx = 0; instructionIdx < newInstructionsSize; instructionIdx++)
|
||||
for (u32 instructionIdx = 0; instructionIdx < newInstructionsSize; instructionIdx++)
|
||||
{
|
||||
u32 replaceAddress = m_selectedAddressStart + instructionIdx * 4;
|
||||
setInstructions(replaceAddress, replaceAddress, encodedInstructions[instructionIdx]);
|
||||
@@ -424,7 +424,7 @@ void DisassemblyView::paintEvent(QPaintEvent* event)
|
||||
bool alternate = m_visibleStart % 8;
|
||||
|
||||
// Draw visible disassembly rows
|
||||
for (u32 i = 0; i <= m_visibleRows; i++)
|
||||
for (u32 i = 0; i < m_visibleRows + 1; i++)
|
||||
{
|
||||
// Address of instruction being displayed on row
|
||||
const u32 rowAddress = (i * 4) + m_visibleStart;
|
||||
@@ -977,18 +977,18 @@ QColor DisassemblyView::GetAddressFunctionColor(u32 address)
|
||||
QString DisassemblyView::FetchSelectionInfo(SelectionInfo selInfo)
|
||||
{
|
||||
QString infoBlock;
|
||||
for (u32 i = m_selectedAddressStart; i <= m_selectedAddressEnd; i += 4)
|
||||
for (u64 i = m_selectedAddressStart; i <= m_selectedAddressEnd; i += 4)
|
||||
{
|
||||
if (i != m_selectedAddressStart)
|
||||
infoBlock += '\n';
|
||||
if (selInfo == SelectionInfo::ADDRESS)
|
||||
{
|
||||
infoBlock += FilledQStringFromValue(i, 16);
|
||||
infoBlock += FilledQStringFromValue(static_cast<u32>(i), 16);
|
||||
}
|
||||
else if (selInfo == SelectionInfo::INSTRUCTIONTEXT)
|
||||
{
|
||||
DisassemblyLineInfo line;
|
||||
m_disassemblyManager.getLine(i, true, line);
|
||||
m_disassemblyManager.getLine(static_cast<u32>(i), true, line);
|
||||
infoBlock += QString("%1 %2").arg(line.name.c_str()).arg(line.params.c_str());
|
||||
}
|
||||
else // INSTRUCTIONHEX
|
||||
@@ -1075,9 +1075,9 @@ void DisassemblyView::setInstructions(u32 start, u32 end, u32 value)
|
||||
|
||||
bool DisassemblyView::AddressCanRestore(u32 start, u32 end)
|
||||
{
|
||||
for (u32 i = start; i <= end; i += 4)
|
||||
for (u64 i = start; i <= end; i += 4)
|
||||
{
|
||||
if (this->m_nopedInstructions.find(i) != this->m_nopedInstructions.end())
|
||||
if (this->m_nopedInstructions.find(static_cast<u32>(i)) != this->m_nopedInstructions.end())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -6,39 +6,36 @@
|
||||
#include <QtCore/QTimer>
|
||||
#include <QtGui/QPainter>
|
||||
#include <QtGui/QPaintEvent>
|
||||
#include <QtWidgets/QBoxLayout>
|
||||
#include <QtWidgets/QStyleFactory>
|
||||
#include <QtWidgets/QStyleOption>
|
||||
|
||||
static const int OUTER_MENU_MARGIN = 2;
|
||||
static const int INNER_MENU_MARGIN = 4;
|
||||
static constexpr int TAB_BAR_TOP_MARGIN = 2;
|
||||
static constexpr int RIGHT_MARGIN = 2;
|
||||
|
||||
DockMenuBar::DockMenuBar(QWidget* original_menu_bar, QWidget* parent)
|
||||
: QWidget(parent)
|
||||
, m_original_menu_bar(original_menu_bar)
|
||||
{
|
||||
QHBoxLayout* layout = new QHBoxLayout;
|
||||
layout->setContentsMargins(0, OUTER_MENU_MARGIN, OUTER_MENU_MARGIN, 0);
|
||||
setLayout(layout);
|
||||
QHBoxLayout* layout = new QHBoxLayout(this);
|
||||
layout->setContentsMargins(0, 0, RIGHT_MARGIN, 0);
|
||||
|
||||
QWidget* menu_wrapper = new QWidget;
|
||||
menu_wrapper->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
|
||||
layout->addWidget(menu_wrapper);
|
||||
QWidget* menu_bar_wrapper = new QWidget;
|
||||
layout->addWidget(menu_bar_wrapper);
|
||||
|
||||
QHBoxLayout* menu_layout = new QHBoxLayout;
|
||||
menu_layout->setContentsMargins(0, INNER_MENU_MARGIN, 0, INNER_MENU_MARGIN);
|
||||
menu_wrapper->setLayout(menu_layout);
|
||||
QVBoxLayout* menu_bar_layout = new QVBoxLayout(menu_bar_wrapper);
|
||||
menu_bar_layout->setContentsMargins(0, 0, 0, 0);
|
||||
menu_bar_layout->addWidget(original_menu_bar, 0, Qt::AlignVCenter);
|
||||
|
||||
menu_layout->addWidget(original_menu_bar);
|
||||
QWidget* layout_switcher_wrapper = new QWidget;
|
||||
layout->addWidget(layout_switcher_wrapper);
|
||||
|
||||
m_layout_switcher_layout = new QVBoxLayout(layout_switcher_wrapper);
|
||||
|
||||
m_layout_switcher = new QTabBar;
|
||||
m_layout_switcher->setContentsMargins(0, 0, 0, 0);
|
||||
m_layout_switcher->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
||||
m_layout_switcher->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
m_layout_switcher->setDrawBase(false);
|
||||
m_layout_switcher->setExpanding(false);
|
||||
m_layout_switcher->setMovable(true);
|
||||
layout->addWidget(m_layout_switcher);
|
||||
|
||||
connect(m_layout_switcher, &QTabBar::tabMoved, this, [this](int from, int to) {
|
||||
DockLayout::Index from_index = static_cast<DockLayout::Index>(from);
|
||||
@@ -63,6 +60,10 @@ DockMenuBar::DockMenuBar(QWidget* original_menu_bar, QWidget* parent)
|
||||
});
|
||||
layout->addWidget(m_layout_locked_toggle);
|
||||
|
||||
layout->setStretchFactor(menu_bar_wrapper, 0);
|
||||
layout->setStretchFactor(layout_switcher_wrapper, 1);
|
||||
layout->setStretchFactor(m_layout_locked_toggle, 0);
|
||||
|
||||
updateTheme();
|
||||
}
|
||||
|
||||
@@ -75,6 +76,19 @@ void DockMenuBar::updateTheme()
|
||||
|
||||
delete m_style;
|
||||
m_style = style;
|
||||
|
||||
// Vertically centre the layout switcher tabs for the Windows 11 style
|
||||
// because I think it looks better. Do the same for macOS too.
|
||||
if (style->baseStyle()->name() == "windows11" || style->baseStyle()->name() == "macOS")
|
||||
{
|
||||
m_layout_switcher_layout->setContentsMargins(0, 0, 0, 0);
|
||||
m_layout_switcher_layout->addWidget(m_layout_switcher, 0, Qt::AlignVCenter);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_layout_switcher_layout->setContentsMargins(0, TAB_BAR_TOP_MARGIN, 0, 0);
|
||||
m_layout_switcher_layout->addWidget(m_layout_switcher, 0, Qt::AlignBottom);
|
||||
}
|
||||
}
|
||||
|
||||
void DockMenuBar::updateLayoutSwitcher(DockLayout::Index current_index, const std::vector<DockLayout>& layouts)
|
||||
@@ -182,11 +196,6 @@ void DockMenuBar::stopBlink()
|
||||
}
|
||||
}
|
||||
|
||||
int DockMenuBar::innerHeight() const
|
||||
{
|
||||
return m_original_menu_bar->sizeHint().height() + INNER_MENU_MARGIN * 2;
|
||||
}
|
||||
|
||||
void DockMenuBar::paintEvent(QPaintEvent* event)
|
||||
{
|
||||
QPainter painter(this);
|
||||
@@ -236,39 +245,6 @@ void DockMenuBarStyle::drawControl(
|
||||
{
|
||||
switch (element)
|
||||
{
|
||||
case CE_MenuBarItem:
|
||||
{
|
||||
const QStyleOptionMenuItem* opt = qstyleoption_cast<const QStyleOptionMenuItem*>(option);
|
||||
if (!opt)
|
||||
break;
|
||||
|
||||
QWidget* menu_wrapper = widget->parentWidget();
|
||||
if (!menu_wrapper)
|
||||
break;
|
||||
|
||||
const DockMenuBar* menu_bar = qobject_cast<const DockMenuBar*>(menu_wrapper->parentWidget());
|
||||
if (!menu_bar)
|
||||
break;
|
||||
|
||||
if (baseStyle()->name() != "fusion")
|
||||
break;
|
||||
|
||||
// This mirrors a check in QFusionStyle::drawControl. If act is
|
||||
// false, QFusionStyle will try to draw a border along the bottom.
|
||||
bool act = opt->state & State_Selected && opt->state & State_Sunken;
|
||||
if (act)
|
||||
break;
|
||||
|
||||
// Extend the menu item to the bottom of the menu bar to fix the
|
||||
// position in which it draws its bottom border. We also need to
|
||||
// extend it up by the same amount so that the text isn't moved.
|
||||
QStyleOptionMenuItem menu_opt = *opt;
|
||||
int difference = (menu_bar->innerHeight() - option->rect.top()) - menu_opt.rect.height();
|
||||
menu_opt.rect.adjust(0, -difference, 0, difference);
|
||||
QProxyStyle::drawControl(element, &menu_opt, painter, widget);
|
||||
|
||||
return;
|
||||
}
|
||||
case CE_TabBarTab:
|
||||
{
|
||||
QProxyStyle::drawControl(element, option, painter, widget);
|
||||
@@ -286,6 +262,25 @@ void DockMenuBarStyle::drawControl(
|
||||
|
||||
return;
|
||||
}
|
||||
case CE_MenuBarItem:
|
||||
{
|
||||
const QStyleOptionMenuItem* opt = qstyleoption_cast<const QStyleOptionMenuItem*>(option);
|
||||
if (!opt)
|
||||
break;
|
||||
|
||||
if (baseStyle()->name() != "fusion")
|
||||
break;
|
||||
|
||||
// This mirrors a check in QFusionStyle::drawControl. If act is
|
||||
// false, QFusionStyle will try to draw a border along the bottom.
|
||||
bool act = opt->state & State_Selected && opt->state & State_Sunken;
|
||||
if (act)
|
||||
break;
|
||||
|
||||
QCommonStyle::drawControl(element, option, painter, widget);
|
||||
|
||||
return;
|
||||
}
|
||||
case CE_MenuBarEmptyArea:
|
||||
{
|
||||
// Prevent it from drawing a border in the wrong position.
|
||||
@@ -301,11 +296,13 @@ void DockMenuBarStyle::drawControl(
|
||||
}
|
||||
|
||||
QSize DockMenuBarStyle::sizeFromContents(
|
||||
QStyle::ContentsType type, const QStyleOption* option, const QSize& contents_size, const QWidget* widget) const
|
||||
QStyle::ContentsType type,
|
||||
const QStyleOption* option,
|
||||
const QSize& contents_size,
|
||||
const QWidget* widget) const
|
||||
{
|
||||
QSize size = QProxyStyle::sizeFromContents(type, option, contents_size, widget);
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
// Adjust the sizes of the layout switcher tabs depending on the theme.
|
||||
if (type == CT_TabBarTab)
|
||||
{
|
||||
@@ -313,30 +310,12 @@ QSize DockMenuBarStyle::sizeFromContents(
|
||||
if (!opt)
|
||||
return size;
|
||||
|
||||
const QTabBar* tab_bar = qobject_cast<const QTabBar*>(widget);
|
||||
if (!tab_bar)
|
||||
return size;
|
||||
|
||||
const DockMenuBar* menu_bar = qobject_cast<const DockMenuBar*>(tab_bar->parentWidget());
|
||||
if (!menu_bar)
|
||||
return size;
|
||||
|
||||
if (baseStyle()->name() == "fusion" || baseStyle()->name() == "windowsvista")
|
||||
if (baseStyle()->name() == "windows11")
|
||||
{
|
||||
// Make sure the tab extends to the bottom of the widget.
|
||||
size.setHeight(menu_bar->innerHeight() - opt->rect.top());
|
||||
}
|
||||
else if (baseStyle()->name() == "windows11")
|
||||
{
|
||||
// Adjust the size of the tab such that it is vertically centred.
|
||||
size.setHeight(menu_bar->innerHeight() - opt->rect.top() * 2 - OUTER_MENU_MARGIN);
|
||||
|
||||
// Make the plus button square.
|
||||
if (opt->tabIndex + 1 == tab_bar->count())
|
||||
size.setWidth(size.height());
|
||||
// Make the tabs a bit taller, otherwise there's an awkward margin.
|
||||
size.setHeight(size.height() + 4);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <QtWidgets/QProxyStyle>
|
||||
#include <QtWidgets/QPushButton>
|
||||
#include <QtWidgets/QTabBar>
|
||||
#include <QtWidgets/QBoxLayout>
|
||||
#include <QtWidgets/QWidget>
|
||||
|
||||
class DockMenuBarStyle;
|
||||
@@ -36,8 +37,6 @@ public:
|
||||
void updateBlink();
|
||||
void stopBlink();
|
||||
|
||||
int innerHeight() const;
|
||||
|
||||
Q_SIGNALS:
|
||||
void currentLayoutChanged(DockLayout::Index layout_index);
|
||||
void newButtonClicked();
|
||||
@@ -54,6 +53,7 @@ private:
|
||||
|
||||
QWidget* m_original_menu_bar;
|
||||
|
||||
QVBoxLayout* m_layout_switcher_layout;
|
||||
QTabBar* m_layout_switcher;
|
||||
QMetaObject::Connection m_tab_connection;
|
||||
int m_plus_tab_index = -1;
|
||||
@@ -70,8 +70,7 @@ private:
|
||||
DockMenuBarStyle* m_style = nullptr;
|
||||
};
|
||||
|
||||
// Fixes some theming issues relating to the menu bar, the layout switcher and
|
||||
// the layout locked/unlocked toggle button.
|
||||
// Fixes some theming issues relating to the menu bar and the layout switcher.
|
||||
class DockMenuBarStyle : public QProxyStyle
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -83,11 +82,11 @@ public:
|
||||
ControlElement element,
|
||||
const QStyleOption* option,
|
||||
QPainter* painter,
|
||||
const QWidget* widget = nullptr) const override;
|
||||
const QWidget* widget) const override;
|
||||
|
||||
QSize sizeFromContents(
|
||||
QStyle::ContentsType type,
|
||||
const QStyleOption* option,
|
||||
const QSize& contents_size,
|
||||
const QWidget* widget = nullptr) const override;
|
||||
const QWidget* widget) const override;
|
||||
};
|
||||
|
||||
@@ -145,15 +145,8 @@ DockTabBar::DockTabBar(KDDockWidgets::Core::TabBar* controller, QWidget* parent)
|
||||
setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(this, &DockTabBar::customContextMenuRequested, this, &DockTabBar::openContextMenu);
|
||||
|
||||
// The constructor of KDDockWidgets::QtWidgets::TabBar makes a QProxyStyle
|
||||
// that ends up taking ownerhsip of the style for the entire application!
|
||||
if (QProxyStyle* proxy_style = qobject_cast<QProxyStyle*>(style()))
|
||||
{
|
||||
if (proxy_style->baseStyle() == qApp->style())
|
||||
proxy_style->baseStyle()->setParent(qApp);
|
||||
|
||||
proxy_style->setBaseStyle(QStyleFactory::create(qApp->style()->name()));
|
||||
}
|
||||
}
|
||||
|
||||
void DockTabBar::openContextMenu(QPoint pos)
|
||||
|
||||
@@ -114,12 +114,30 @@ QWidget* SymbolTreeValueDelegate::createEditor(QWidget* parent, const QStyleOpti
|
||||
const ccc::ast::Enum& enumeration = type.as<ccc::ast::Enum>();
|
||||
|
||||
QComboBox* combo_box = new QComboBox(parent);
|
||||
for (s32 i = 0; i < (s32)enumeration.constants.size(); i++)
|
||||
bool named = false;
|
||||
|
||||
for (size_t i = 0; i < enumeration.constants.size(); i++)
|
||||
{
|
||||
combo_box->addItem(QString::fromStdString(enumeration.constants[i].second));
|
||||
QString text = QString::fromStdString(enumeration.constants[i].second);
|
||||
combo_box->addItem(text, enumeration.constants[i].first);
|
||||
if (enumeration.constants[i].first == value.toInt())
|
||||
combo_box->setCurrentIndex(i);
|
||||
{
|
||||
combo_box->setCurrentIndex(static_cast<int>(i));
|
||||
named = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!named)
|
||||
{
|
||||
// The value isn't equal to any of the named constants, so
|
||||
// add an extra item to the combo box representing the
|
||||
// current value so that the first named constant isn't
|
||||
// written back to VM memory accidentally.
|
||||
QString text = display_options.signedIntegerToString(value.toInt(), 32);
|
||||
combo_box->insertItem(0, text, value.toInt());
|
||||
combo_box->setCurrentIndex(0);
|
||||
}
|
||||
|
||||
connect(combo_box, &QComboBox::currentIndexChanged, this, &SymbolTreeValueDelegate::onComboBoxIndexChanged);
|
||||
result = combo_box;
|
||||
|
||||
@@ -245,15 +263,10 @@ void SymbolTreeValueDelegate::setModelData(QWidget* editor, QAbstractItemModel*
|
||||
}
|
||||
case ccc::ast::ENUM:
|
||||
{
|
||||
const ccc::ast::Enum& enumeration = type.as<ccc::ast::Enum>();
|
||||
QComboBox* combo_box = qobject_cast<QComboBox*>(editor);
|
||||
Q_ASSERT(combo_box);
|
||||
|
||||
s32 comboIndex = combo_box->currentIndex();
|
||||
if (comboIndex < 0 || comboIndex >= (s32)enumeration.constants.size())
|
||||
break;
|
||||
|
||||
value = enumeration.constants[comboIndex].first;
|
||||
value = combo_box->currentData().toInt();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -390,15 +390,13 @@ QString SymbolTreeNode::generateDisplayString(
|
||||
}
|
||||
case ccc::ast::ENUM:
|
||||
{
|
||||
s32 value = (s32)location.read32(cpu);
|
||||
s32 value = static_cast<s32>(location.read32(cpu));
|
||||
const auto& enum_type = physical_type.as<ccc::ast::Enum>();
|
||||
for (auto [test_value, name] : enum_type.constants)
|
||||
{
|
||||
for (const auto& [test_value, name] : enum_type.constants)
|
||||
if (test_value == value)
|
||||
return QString::fromStdString(name);
|
||||
}
|
||||
|
||||
break;
|
||||
return display_options.signedIntegerToString(value, 32);
|
||||
}
|
||||
case ccc::ast::POINTER_OR_REFERENCE:
|
||||
{
|
||||
|
||||
@@ -13,7 +13,7 @@ ThreadModel::ThreadModel(DebugInterface& cpu, QObject* parent)
|
||||
|
||||
int ThreadModel::rowCount(const QModelIndex&) const
|
||||
{
|
||||
return m_cpu.GetThreadList().size();
|
||||
return static_cast<int>(m_threads.size());
|
||||
}
|
||||
|
||||
int ThreadModel::columnCount(const QModelIndex&) const
|
||||
|
||||
@@ -29,20 +29,12 @@
|
||||
#include <qpa/qplatformnativeinterface.h>
|
||||
#endif
|
||||
|
||||
DisplayWidget::DisplayWidget(QWidget* parent)
|
||||
: QWidget(parent)
|
||||
DisplaySurface::DisplaySurface()
|
||||
: QWindow()
|
||||
{
|
||||
// We want a native window for both D3D and OpenGL.
|
||||
setAutoFillBackground(false);
|
||||
setAttribute(Qt::WA_NativeWindow, true);
|
||||
setAttribute(Qt::WA_NoSystemBackground, true);
|
||||
setAttribute(Qt::WA_PaintOnScreen, true);
|
||||
setAttribute(Qt::WA_KeyCompression, false);
|
||||
setFocusPolicy(Qt::StrongFocus);
|
||||
setMouseTracking(true);
|
||||
}
|
||||
|
||||
DisplayWidget::~DisplayWidget()
|
||||
DisplaySurface::~DisplaySurface()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (m_clip_mouse_enabled)
|
||||
@@ -50,19 +42,17 @@ DisplayWidget::~DisplayWidget()
|
||||
#endif
|
||||
}
|
||||
|
||||
int DisplayWidget::scaledWindowWidth() const
|
||||
QWidget* DisplaySurface::createWindowContainer(QWidget* parent)
|
||||
{
|
||||
return std::max(static_cast<int>(std::ceil(static_cast<qreal>(width()) * devicePixelRatioF())), 1);
|
||||
m_container = QWidget::createWindowContainer(this, parent);
|
||||
m_container->installEventFilter(this);
|
||||
m_container->setFocusPolicy(Qt::StrongFocus);
|
||||
return m_container;
|
||||
}
|
||||
|
||||
int DisplayWidget::scaledWindowHeight() const
|
||||
std::optional<WindowInfo> DisplaySurface::getWindowInfo()
|
||||
{
|
||||
return std::max(static_cast<int>(std::ceil(static_cast<qreal>(height()) * devicePixelRatioF())), 1);
|
||||
}
|
||||
|
||||
std::optional<WindowInfo> DisplayWidget::getWindowInfo()
|
||||
{
|
||||
std::optional<WindowInfo> ret(QtUtils::GetWindowInfoForWidget(this));
|
||||
std::optional<WindowInfo> ret(QtUtils::GetWindowInfoForWindow(this));
|
||||
if (ret.has_value())
|
||||
{
|
||||
m_last_window_width = ret->surface_width;
|
||||
@@ -72,7 +62,7 @@ std::optional<WindowInfo> DisplayWidget::getWindowInfo()
|
||||
return ret;
|
||||
}
|
||||
|
||||
void DisplayWidget::updateRelativeMode(bool enabled)
|
||||
void DisplaySurface::updateRelativeMode(bool enabled)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
// prefer ClipCursor() over warping movement when we're using raw input
|
||||
@@ -104,17 +94,17 @@ void DisplayWidget::updateRelativeMode(bool enabled)
|
||||
#endif
|
||||
m_relative_mouse_start_pos = QCursor::pos();
|
||||
updateCenterPos();
|
||||
grabMouse();
|
||||
setMouseGrabEnabled(true);
|
||||
}
|
||||
else if (m_relative_mouse_enabled)
|
||||
{
|
||||
m_relative_mouse_enabled = false;
|
||||
QCursor::setPos(m_relative_mouse_start_pos);
|
||||
releaseMouse();
|
||||
setMouseGrabEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayWidget::updateCursor(bool hidden)
|
||||
void DisplaySurface::updateCursor(bool hidden)
|
||||
{
|
||||
if (m_cursor_hidden == hidden)
|
||||
return;
|
||||
@@ -132,7 +122,7 @@ void DisplayWidget::updateCursor(bool hidden)
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayWidget::handleCloseEvent(QCloseEvent* event)
|
||||
void DisplaySurface::handleCloseEvent(QCloseEvent* event)
|
||||
{
|
||||
// Closing the separate widget will either cancel the close, or trigger shutdown.
|
||||
// In the latter case, it's going to destroy us, so don't let Qt do it first.
|
||||
@@ -152,28 +142,13 @@ void DisplayWidget::handleCloseEvent(QCloseEvent* event)
|
||||
event->ignore();
|
||||
}
|
||||
|
||||
void DisplayWidget::destroy()
|
||||
bool DisplaySurface::isActuallyFullscreen() const
|
||||
{
|
||||
m_destroying = true;
|
||||
|
||||
#ifdef __APPLE__
|
||||
// See Qt documentation, entire application is in full screen state, and the main
|
||||
// window will get reopened fullscreen instead of windowed if we don't close the
|
||||
// fullscreen window first.
|
||||
if (isActuallyFullscreen())
|
||||
close();
|
||||
#endif
|
||||
deleteLater();
|
||||
// DisplaySurface is always in a container, so we need to check parent window
|
||||
return parent()->windowState() & Qt::WindowFullScreen;
|
||||
}
|
||||
|
||||
bool DisplayWidget::isActuallyFullscreen() const
|
||||
{
|
||||
// I hate you QtWayland... have to check the parent, not ourselves.
|
||||
QWidget* container = qobject_cast<QWidget*>(parent());
|
||||
return container ? container->isFullScreen() : isFullScreen();
|
||||
}
|
||||
|
||||
void DisplayWidget::updateCenterPos()
|
||||
void DisplaySurface::updateCenterPos()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (m_clip_mouse_enabled)
|
||||
@@ -203,12 +178,14 @@ void DisplayWidget::updateCenterPos()
|
||||
#endif
|
||||
}
|
||||
|
||||
QPaintEngine* DisplayWidget::paintEngine() const
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool DisplayWidget::event(QEvent* event)
|
||||
// Keyboard focus and child windows are inconsistant across platforms;
|
||||
// Windows: Can programmatically focus the child window, NVidia overlay can defocus it.
|
||||
// X11: Can programmatically focus the child window.
|
||||
// Wayland: Child window cannot be focused at all on most(?) DE.
|
||||
// Mac: Can programmatically focus the child window.
|
||||
// Thus for KB inputs we need to sometimes use the event filter.
|
||||
// Mouse events are always delivered to the child window, so that seems consistant.
|
||||
void DisplaySurface::handleKeyInputEvent(QEvent* event)
|
||||
{
|
||||
switch (event->type())
|
||||
{
|
||||
@@ -229,7 +206,7 @@ bool DisplayWidget::event(QEvent* event)
|
||||
}
|
||||
|
||||
if (key_event->isAutoRepeat())
|
||||
return true;
|
||||
return;
|
||||
|
||||
// For some reason, Windows sends "fake" key events.
|
||||
// Scenario: Press shift, press F1, release shift, release F1.
|
||||
@@ -246,7 +223,7 @@ bool DisplayWidget::event(QEvent* event)
|
||||
if (it != m_keys_pressed_with_modifiers.end())
|
||||
{
|
||||
if (pressed)
|
||||
return true;
|
||||
return;
|
||||
else
|
||||
m_keys_pressed_with_modifiers.erase(it);
|
||||
}
|
||||
@@ -259,6 +236,23 @@ bool DisplayWidget::event(QEvent* event)
|
||||
InputManager::InvokeEvents(InputManager::MakeHostKeyboardKey(key), static_cast<float>(pressed));
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
default:
|
||||
pxAssert(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool DisplaySurface::event(QEvent* event)
|
||||
{
|
||||
switch (event->type())
|
||||
{
|
||||
case QEvent::KeyPress:
|
||||
case QEvent::KeyRelease:
|
||||
{
|
||||
handleKeyInputEvent(event);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -268,7 +262,7 @@ bool DisplayWidget::event(QEvent* event)
|
||||
|
||||
if (!m_relative_mouse_enabled)
|
||||
{
|
||||
const qreal dpr = devicePixelRatioF();
|
||||
const qreal dpr = devicePixelRatio();
|
||||
const QPoint mouse_pos = mouse_event->pos();
|
||||
|
||||
const float scaled_x = static_cast<float>(static_cast<qreal>(mouse_pos.x()) * dpr);
|
||||
@@ -352,11 +346,11 @@ bool DisplayWidget::event(QEvent* event)
|
||||
case QEvent::DevicePixelRatioChange:
|
||||
case QEvent::Resize:
|
||||
{
|
||||
QWidget::event(event);
|
||||
QWindow::event(event);
|
||||
|
||||
const float dpr = devicePixelRatioF();
|
||||
const u32 scaled_width = static_cast<u32>(std::max(static_cast<int>(std::ceil(static_cast<qreal>(width()) * dpr)), 1));
|
||||
const u32 scaled_height = static_cast<u32>(std::max(static_cast<int>(std::ceil(static_cast<qreal>(height()) * dpr)), 1));
|
||||
const float dpr = devicePixelRatio();
|
||||
const u32 scaled_width = static_cast<u32>(std::max(static_cast<int>(std::round(static_cast<qreal>(width()) * dpr)), 1));
|
||||
const u32 scaled_height = static_cast<u32>(std::max(static_cast<int>(std::round(static_cast<qreal>(height()) * dpr)), 1));
|
||||
|
||||
// avoid spamming resize events for paint events (sent on move on windows)
|
||||
if (m_last_window_width != scaled_width || m_last_window_height != scaled_height || m_last_window_scale != dpr)
|
||||
@@ -371,106 +365,60 @@ bool DisplayWidget::event(QEvent* event)
|
||||
return true;
|
||||
}
|
||||
|
||||
case QEvent::DragEnter:
|
||||
QWindow::event(event);
|
||||
emit dragEnterEvent(static_cast<QDragEnterEvent*>(event));
|
||||
return event->isAccepted();
|
||||
|
||||
case QEvent::Drop:
|
||||
QWindow::event(event);
|
||||
emit dropEvent(static_cast<QDropEvent*>(event));
|
||||
return event->isAccepted();
|
||||
|
||||
case QEvent::Move:
|
||||
{
|
||||
updateCenterPos();
|
||||
return true;
|
||||
}
|
||||
|
||||
case QEvent::Close:
|
||||
{
|
||||
if (m_destroying)
|
||||
return QWidget::event(event);
|
||||
|
||||
handleCloseEvent(static_cast<QCloseEvent*>(event));
|
||||
return true;
|
||||
}
|
||||
|
||||
case QEvent::WindowStateChange:
|
||||
{
|
||||
QWidget::event(event);
|
||||
|
||||
if (static_cast<QWindowStateChangeEvent*>(event)->oldState() & Qt::WindowMinimized)
|
||||
emit windowRestoredEvent();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
default:
|
||||
return QWidget::event(event);
|
||||
return QWindow::event(event);
|
||||
}
|
||||
}
|
||||
|
||||
DisplayContainer::DisplayContainer()
|
||||
: QStackedWidget(nullptr)
|
||||
bool DisplaySurface::eventFilter(QObject* object, QEvent* event)
|
||||
{
|
||||
}
|
||||
|
||||
DisplayContainer::~DisplayContainer() = default;
|
||||
|
||||
bool DisplayContainer::isNeeded(bool fullscreen, bool render_to_main)
|
||||
{
|
||||
#if defined(_WIN32) || defined(__APPLE__)
|
||||
return false;
|
||||
#else
|
||||
if (!isRunningOnWayland())
|
||||
return false;
|
||||
|
||||
// We only need this on Wayland because of client-side decorations...
|
||||
return (fullscreen || !render_to_main);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool DisplayContainer::isRunningOnWayland()
|
||||
{
|
||||
#if defined(_WIN32) || defined(__APPLE__)
|
||||
return false;
|
||||
#else
|
||||
const QString platform_name = QGuiApplication::platformName();
|
||||
return (platform_name == QStringLiteral("wayland"));
|
||||
#endif
|
||||
}
|
||||
|
||||
void DisplayContainer::setDisplayWidget(DisplayWidget* widget)
|
||||
{
|
||||
pxAssert(!m_display_widget);
|
||||
m_display_widget = widget;
|
||||
addWidget(widget);
|
||||
}
|
||||
|
||||
DisplayWidget* DisplayContainer::removeDisplayWidget()
|
||||
{
|
||||
DisplayWidget* widget = m_display_widget;
|
||||
pxAssert(widget);
|
||||
m_display_widget = nullptr;
|
||||
removeWidget(widget);
|
||||
return widget;
|
||||
}
|
||||
|
||||
bool DisplayContainer::event(QEvent* event)
|
||||
{
|
||||
if (event->type() == QEvent::Close && m_display_widget)
|
||||
{
|
||||
m_display_widget->handleCloseEvent(static_cast<QCloseEvent*>(event));
|
||||
return true;
|
||||
}
|
||||
|
||||
const bool res = QStackedWidget::event(event);
|
||||
if (!m_display_widget)
|
||||
return res;
|
||||
|
||||
switch (event->type())
|
||||
{
|
||||
case QEvent::KeyPress:
|
||||
case QEvent::KeyRelease:
|
||||
#ifdef _WIN32
|
||||
// Nvidia overlay causes the child window to lose focus, but not its parent.
|
||||
// Refocus the child window.
|
||||
requestActivate();
|
||||
#endif
|
||||
handleKeyInputEvent(event);
|
||||
return true;
|
||||
|
||||
// These events only work on the top level control.
|
||||
// Which is this container when render to seperate or fullscreen is active.
|
||||
case QEvent::Close:
|
||||
handleCloseEvent(static_cast<QCloseEvent*>(event));
|
||||
return true;
|
||||
case QEvent::WindowStateChange:
|
||||
{
|
||||
if (static_cast<QWindowStateChangeEvent*>(event)->oldState() & Qt::WindowMinimized)
|
||||
emit m_display_widget->windowRestoredEvent();
|
||||
}
|
||||
break;
|
||||
emit windowRestoredEvent();
|
||||
return false;
|
||||
|
||||
case QEvent::ChildRemoved:
|
||||
if (static_cast<QChildEvent*>(event)->child() == m_container)
|
||||
{
|
||||
object->removeEventFilter(this);
|
||||
m_container = nullptr;
|
||||
}
|
||||
return false;
|
||||
|
||||
default:
|
||||
break;
|
||||
return false;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -3,40 +3,44 @@
|
||||
|
||||
#pragma once
|
||||
#include "common/WindowInfo.h"
|
||||
#include <QtWidgets/QStackedWidget>
|
||||
#include <QtWidgets/QWidget>
|
||||
#include <QtGui/QDragMoveEvent>
|
||||
#include <QtGui/QWindow>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
class QCloseEvent;
|
||||
|
||||
class DisplayWidget final : public QWidget
|
||||
class DisplaySurface final : public QWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit DisplayWidget(QWidget* parent);
|
||||
~DisplayWidget();
|
||||
explicit DisplaySurface();
|
||||
~DisplaySurface();
|
||||
|
||||
QPaintEngine* paintEngine() const override;
|
||||
|
||||
int scaledWindowWidth() const;
|
||||
int scaledWindowHeight() const;
|
||||
// while QWindow can be used directly as a window, Popups requre a QWidget parent.
|
||||
// Additionally, we use saveGeometry/restoreGeometry for render to seperate window mode
|
||||
// but those functions only exist on QWidget.
|
||||
// Thus, we always need a container widget.
|
||||
QWidget* createWindowContainer(QWidget* parent = nullptr);
|
||||
|
||||
std::optional<WindowInfo> getWindowInfo();
|
||||
|
||||
void updateRelativeMode(bool enabled);
|
||||
void updateCursor(bool hidden);
|
||||
|
||||
void handleCloseEvent(QCloseEvent* event);
|
||||
void destroy();
|
||||
|
||||
Q_SIGNALS:
|
||||
void windowResizedEvent(int width, int height, float scale);
|
||||
void windowRestoredEvent();
|
||||
|
||||
void dragEnterEvent(QDragEnterEvent* event);
|
||||
void dropEvent(QDropEvent* event);
|
||||
|
||||
protected:
|
||||
void handleCloseEvent(QCloseEvent* event);
|
||||
void handleKeyInputEvent(QEvent* event);
|
||||
bool event(QEvent* event) override;
|
||||
bool eventFilter(QObject* object, QEvent* event) override;
|
||||
|
||||
private:
|
||||
bool isActuallyFullscreen() const;
|
||||
@@ -49,34 +53,12 @@ private:
|
||||
bool m_clip_mouse_enabled = false;
|
||||
#endif
|
||||
bool m_cursor_hidden = false;
|
||||
bool m_destroying = false;
|
||||
|
||||
std::vector<int> m_keys_pressed_with_modifiers;
|
||||
|
||||
u32 m_last_window_width = 0;
|
||||
u32 m_last_window_height = 0;
|
||||
float m_last_window_scale = 1.0f;
|
||||
};
|
||||
|
||||
class DisplayContainer final : public QStackedWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
DisplayContainer();
|
||||
~DisplayContainer();
|
||||
|
||||
// Wayland is broken in lots of ways, so we need to check for it.
|
||||
static bool isRunningOnWayland();
|
||||
|
||||
static bool isNeeded(bool fullscreen, bool render_to_main);
|
||||
|
||||
void setDisplayWidget(DisplayWidget* widget);
|
||||
DisplayWidget* removeDisplayWidget();
|
||||
|
||||
protected:
|
||||
bool event(QEvent* event) override;
|
||||
|
||||
private:
|
||||
DisplayWidget* m_display_widget = nullptr;
|
||||
|
||||
QWidget* m_container = nullptr;
|
||||
};
|
||||
|
||||
@@ -31,51 +31,6 @@ static constexpr int SIZE_HINT_HEIGHT_TITLES = SIZE_HINT_HEIGHT + COVER_ART_SPAC
|
||||
|
||||
static constexpr int MIN_COVER_CACHE_SIZE = 256;
|
||||
|
||||
static int DPRScale(int size, qreal dpr)
|
||||
{
|
||||
return static_cast<int>(static_cast<qreal>(size) * dpr);
|
||||
}
|
||||
|
||||
static int DPRUnscale(int size, qreal dpr)
|
||||
{
|
||||
return static_cast<int>(static_cast<qreal>(size) / dpr);
|
||||
}
|
||||
|
||||
static void resizeAndPadPixmap(QPixmap* pm, int expected_width, int expected_height, qreal dpr)
|
||||
{
|
||||
const int dpr_expected_width = DPRScale(expected_width, dpr);
|
||||
const int dpr_expected_height = DPRScale(expected_height, dpr);
|
||||
if (pm->width() == dpr_expected_width && pm->height() == dpr_expected_height)
|
||||
return;
|
||||
|
||||
*pm = pm->scaled(dpr_expected_width, dpr_expected_height, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
if (pm->width() == dpr_expected_width && pm->height() == dpr_expected_height)
|
||||
return;
|
||||
|
||||
// QPainter works in unscaled coordinates.
|
||||
int xoffs = 0;
|
||||
int yoffs = 0;
|
||||
if (pm->width() < dpr_expected_width)
|
||||
xoffs = DPRUnscale((dpr_expected_width - pm->width()) / 2, dpr);
|
||||
if (pm->height() < dpr_expected_height)
|
||||
yoffs = DPRUnscale((dpr_expected_height - pm->height()) / 2, dpr);
|
||||
|
||||
QPixmap padded_image(dpr_expected_width, dpr_expected_height);
|
||||
padded_image.setDevicePixelRatio(dpr);
|
||||
padded_image.fill(Qt::transparent);
|
||||
QPainter painter;
|
||||
if (painter.begin(&padded_image))
|
||||
{
|
||||
painter.setCompositionMode(QPainter::CompositionMode_Source);
|
||||
painter.drawPixmap(xoffs, yoffs, *pm);
|
||||
painter.setCompositionMode(QPainter::CompositionMode_Destination);
|
||||
painter.fillRect(padded_image.rect(), QColor(0, 0, 0, 0));
|
||||
painter.end();
|
||||
}
|
||||
|
||||
*pm = padded_image;
|
||||
}
|
||||
|
||||
static QPixmap createPlaceholderImage(const QPixmap& placeholder_pixmap, int width, int height, float scale,
|
||||
qreal dpr, const std::string& title)
|
||||
{
|
||||
@@ -84,7 +39,7 @@ static QPixmap createPlaceholderImage(const QPixmap& placeholder_pixmap, int wid
|
||||
if (pm.isNull())
|
||||
return QPixmap();
|
||||
|
||||
resizeAndPadPixmap(&pm, width, height, dpr);
|
||||
QtUtils::resizeAndScalePixmap(&pm, width, height, dpr, QtUtils::ScalingMode::Fit, 100);
|
||||
QPainter painter;
|
||||
if (painter.begin(&pm))
|
||||
{
|
||||
@@ -102,9 +57,9 @@ static QPixmap createPlaceholderImage(const QPixmap& placeholder_pixmap, int wid
|
||||
return pm;
|
||||
}
|
||||
|
||||
std::optional<GameListModel::Column> GameListModel::getColumnIdForName(std::string_view name)
|
||||
std::optional<GameListModel::Column> GameListModel::getColumnIdForName(const std::string_view name)
|
||||
{
|
||||
for (int column = 0; column < Column_Count; column++)
|
||||
for (u32 column = 0; column < Column_Count; column++)
|
||||
{
|
||||
if (name == s_column_names[column])
|
||||
return static_cast<Column>(column);
|
||||
@@ -113,12 +68,12 @@ std::optional<GameListModel::Column> GameListModel::getColumnIdForName(std::stri
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const char* GameListModel::getColumnName(Column col)
|
||||
const char* GameListModel::getColumnName(const Column col)
|
||||
{
|
||||
return s_column_names[static_cast<int>(col)];
|
||||
}
|
||||
|
||||
GameListModel::GameListModel(float cover_scale, bool show_cover_titles, qreal dpr, QObject* parent /* = nullptr */)
|
||||
GameListModel::GameListModel(const float cover_scale, const bool show_cover_titles, const qreal dpr, QObject* parent /* = nullptr */)
|
||||
: QAbstractTableModel(parent)
|
||||
, m_show_titles_for_covers(show_cover_titles)
|
||||
, m_dpr{dpr}
|
||||
@@ -181,29 +136,31 @@ void GameListModel::loadOrGenerateCover(const GameList::Entry* ge)
|
||||
|
||||
QFuture<QPixmap> future = QtConcurrent::run([this, entry = *ge, counter]() -> QPixmap {
|
||||
QPixmap image;
|
||||
|
||||
// Initial check that the scale is unchanged before we run costly image generation.
|
||||
if (m_cover_scale_counter.load(std::memory_order_acquire) == counter)
|
||||
{
|
||||
const std::string cover_path(GameList::GetCoverImagePathForEntry(&entry));
|
||||
if (!cover_path.empty())
|
||||
{
|
||||
image = QPixmap(QString::fromStdString(cover_path));
|
||||
if (!image.isNull())
|
||||
{
|
||||
image.setDevicePixelRatio(m_dpr);
|
||||
resizeAndPadPixmap(&image, getCoverArtWidth(), getCoverArtHeight(), m_dpr);
|
||||
}
|
||||
|
||||
// Create placeholder image if no user-provided cover exists.
|
||||
if (image.isNull())
|
||||
{
|
||||
const std::string& title = entry.GetTitle(m_prefer_english_titles);
|
||||
image = createPlaceholderImage(m_placeholder_pixmap, getCoverArtWidth(), getCoverArtHeight(), m_cover_scale, m_dpr, title);
|
||||
}
|
||||
// Create resized image from user-provided cover.
|
||||
else
|
||||
{
|
||||
image.setDevicePixelRatio(m_dpr);
|
||||
QtUtils::resizeAndScalePixmap(&image, getCoverArtWidth(), getCoverArtHeight(), m_dpr, QtUtils::ScalingMode::Fit, 100);
|
||||
}
|
||||
}
|
||||
|
||||
const std::string& title = entry.GetTitle(m_prefer_english_titles);
|
||||
// Final check that scale is unchanged before we send out the produced image.
|
||||
return m_cover_scale_counter.load(std::memory_order_acquire) == counter ? image : QPixmap();
|
||||
|
||||
if (image.isNull())
|
||||
image = createPlaceholderImage(m_placeholder_pixmap, getCoverArtWidth(), getCoverArtHeight(), m_cover_scale, m_dpr, title);
|
||||
|
||||
if (m_cover_scale_counter.load(std::memory_order_acquire) != counter)
|
||||
image = {};
|
||||
|
||||
return image;
|
||||
});
|
||||
|
||||
// Context must be 'this' so we run on the UI thread.
|
||||
@@ -219,25 +176,18 @@ void GameListModel::loadOrGenerateCover(const GameList::Entry* ge)
|
||||
void GameListModel::invalidateCoverForPath(const std::string& path)
|
||||
{
|
||||
// This isn't ideal, but not sure how else we can get the row, when it might change while scanning...
|
||||
auto lock = GameList::GetLock();
|
||||
const auto lock = GameList::GetLock();
|
||||
const u32 count = GameList::GetEntryCount();
|
||||
std::optional<u32> row;
|
||||
for (u32 i = 0; i < count; i++)
|
||||
|
||||
for (u32 row = 0; row < count; row++)
|
||||
{
|
||||
if (GameList::GetEntryByIndex(i)->path == path)
|
||||
if (GameList::GetEntryByIndex(row)->path == path)
|
||||
{
|
||||
row = i;
|
||||
break;
|
||||
const QModelIndex mi(index(row, Column_Cover));
|
||||
emit dataChanged(mi, mi, {Qt::DecorationRole});
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!row.has_value())
|
||||
{
|
||||
// Game removed?
|
||||
return;
|
||||
}
|
||||
|
||||
const QModelIndex mi(index(static_cast<int>(row.value()), Column_Cover));
|
||||
emit dataChanged(mi, mi, {Qt::DecorationRole});
|
||||
}
|
||||
|
||||
int GameListModel::getCoverArtWidth() const
|
||||
@@ -257,32 +207,29 @@ int GameListModel::getCoverArtSpacing() const
|
||||
|
||||
int GameListModel::rowCount(const QModelIndex& parent) const
|
||||
{
|
||||
if (parent.isValid())
|
||||
return 0;
|
||||
|
||||
return static_cast<int>(GameList::GetEntryCount());
|
||||
return parent.isValid() ? 0 : static_cast<int>(GameList::GetEntryCount());
|
||||
}
|
||||
|
||||
int GameListModel::columnCount(const QModelIndex& parent) const
|
||||
{
|
||||
if (parent.isValid())
|
||||
return 0;
|
||||
|
||||
return Column_Count;
|
||||
return parent.isValid() ? 0 : Column_Count;
|
||||
}
|
||||
|
||||
QString GameListModel::formatTimespan(time_t timespan)
|
||||
QString GameListModel::formatTimespan(const time_t timespan)
|
||||
{
|
||||
// avoid an extra string conversion
|
||||
// Avoid an extra string conversion over calling QString::fromStdString(GameList::FormatTimespan).
|
||||
const u32 hours = static_cast<u32>(timespan / 3600);
|
||||
const u32 minutes = static_cast<u32>((timespan % 3600) / 60);
|
||||
if (hours > 0)
|
||||
return qApp->translate("GameList", "%n hours", "", hours);
|
||||
else
|
||||
|
||||
const u32 minutes = static_cast<u32>((timespan % 3600) / 60);
|
||||
if (minutes > 0)
|
||||
return qApp->translate("GameList", "%n minutes", "", minutes);
|
||||
else
|
||||
return qApp->translate("GameList", "%n seconds", "", static_cast<u32>((timespan % 3600) % 60));
|
||||
}
|
||||
|
||||
QVariant GameListModel::data(const QModelIndex& index, int role) const
|
||||
QVariant GameListModel::data(const QModelIndex& index, const int role) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return QVariant();
|
||||
@@ -354,8 +301,7 @@ QVariant GameListModel::data(const QModelIndex& index, int role) const
|
||||
if (pm)
|
||||
return *pm;
|
||||
|
||||
// We insert the placeholder into the cache, so that we don't repeatedly
|
||||
// queue loading jobs for this game.
|
||||
// Insert the placeholder into the cache so we don't repeatedly queue loading jobs for this game.
|
||||
const_cast<GameListModel*>(this)->loadOrGenerateCover(ge);
|
||||
return *m_cover_pixmap_cache.Insert(ge->path, m_loading_pixmap);
|
||||
}
|
||||
@@ -383,7 +329,7 @@ QVariant GameListModel::data(const QModelIndex& index, int role) const
|
||||
}
|
||||
}
|
||||
|
||||
QVariant GameListModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
QVariant GameListModel::headerData(const int section, const Qt::Orientation orientation, const int role) const
|
||||
{
|
||||
if (orientation != Qt::Horizontal || role != Qt::DisplayRole || section < 0 || section >= Column_Count)
|
||||
return QVariant();
|
||||
@@ -398,7 +344,7 @@ void GameListModel::refresh()
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
bool GameListModel::titlesLessThan(int left_row, int right_row) const
|
||||
bool GameListModel::titlesLessThan(const int left_row, const int right_row) const
|
||||
{
|
||||
if (left_row < 0 || left_row >= static_cast<int>(GameList::GetEntryCount()) || right_row < 0 ||
|
||||
right_row >= static_cast<int>(GameList::GetEntryCount()))
|
||||
@@ -412,7 +358,7 @@ bool GameListModel::titlesLessThan(int left_row, int right_row) const
|
||||
QString::fromStdString(right->GetTitleSort(m_prefer_english_titles))) < 0;
|
||||
}
|
||||
|
||||
bool GameListModel::lessThan(const QModelIndex& left_index, const QModelIndex& right_index, int column) const
|
||||
bool GameListModel::lessThan(const QModelIndex& left_index, const QModelIndex& right_index, const int column) const
|
||||
{
|
||||
if (!left_index.isValid() || !right_index.isValid())
|
||||
return false;
|
||||
@@ -445,13 +391,12 @@ bool GameListModel::lessThan(const QModelIndex& left_index, const QModelIndex& r
|
||||
{
|
||||
if (left->serial == right->serial)
|
||||
return titlesLessThan(left_row, right_row);
|
||||
|
||||
return (StringUtil::Strcasecmp(left->serial.c_str(), right->serial.c_str()) < 0);
|
||||
}
|
||||
|
||||
case Column_Title:
|
||||
{
|
||||
return titlesLessThan(left_row, right_row);
|
||||
}
|
||||
|
||||
case Column_FileTitle:
|
||||
{
|
||||
@@ -468,6 +413,7 @@ bool GameListModel::lessThan(const QModelIndex& left_index, const QModelIndex& r
|
||||
{
|
||||
if (left->region == right->region)
|
||||
return titlesLessThan(left_row, right_row);
|
||||
|
||||
return (static_cast<int>(left->region) < static_cast<int>(right->region));
|
||||
}
|
||||
|
||||
@@ -521,7 +467,7 @@ void GameListModel::loadSettings()
|
||||
m_prefer_english_titles = Host::GetBaseBoolSettingValue("UI", "PreferEnglishGameList", false);
|
||||
}
|
||||
|
||||
QIcon GameListModel::getIconForType(GameList::EntryType type)
|
||||
QIcon GameListModel::getIconForType(const GameList::EntryType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
@@ -535,7 +481,7 @@ QIcon GameListModel::getIconForType(GameList::EntryType type)
|
||||
}
|
||||
}
|
||||
|
||||
QIcon GameListModel::getIconForRegion(GameList::Region region)
|
||||
QIcon GameListModel::getIconForRegion(const GameList::Region region)
|
||||
{
|
||||
return QIcon(
|
||||
QStringLiteral("%1/icons/flags/%2.svg").arg(QtHost::GetResourcesBasePath()).arg(GameList::RegionToString(region, false)));
|
||||
@@ -546,8 +492,8 @@ void GameListModel::loadThemeSpecificImages()
|
||||
for (u32 type = 0; type < static_cast<u32>(GameList::EntryType::Count); type++)
|
||||
m_type_pixmaps[type] = getIconForType(static_cast<GameList::EntryType>(type)).pixmap(QSize(24, 24), m_dpr);
|
||||
|
||||
for (u32 i = 0; i < static_cast<u32>(GameList::Region::Count); i++)
|
||||
m_region_pixmaps[i] = getIconForRegion(static_cast<GameList::Region>(i)).pixmap(QSize(36, 26), m_dpr);
|
||||
for (u32 region = 0; region < static_cast<u32>(GameList::Region::Count); region++)
|
||||
m_region_pixmaps[region] = getIconForRegion(static_cast<GameList::Region>(region)).pixmap(QSize(36, 26), m_dpr);
|
||||
}
|
||||
|
||||
void GameListModel::loadCommonImages()
|
||||
@@ -555,8 +501,8 @@ void GameListModel::loadCommonImages()
|
||||
loadThemeSpecificImages();
|
||||
|
||||
const QString base_path(QtHost::GetResourcesBasePath());
|
||||
for (u32 i = 1; i < GameList::CompatibilityRatingCount; i++)
|
||||
m_compatibility_pixmaps[i] = QIcon((QStringLiteral("%1/icons/star-%2.svg").arg(base_path).arg(i - 1))).pixmap(QSize(88, 16), m_dpr);
|
||||
for (u32 rating = 1; rating < GameList::CompatibilityRatingCount; rating++)
|
||||
m_compatibility_pixmaps[rating] = QIcon((QStringLiteral("%1/icons/star-%2.svg").arg(base_path).arg(rating - 1))).pixmap(QSize(88, 16), m_dpr);
|
||||
|
||||
m_placeholder_pixmap.load(QStringLiteral("%1/cover-placeholder.png").arg(base_path));
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ private:
|
||||
void loadOrGenerateCover(const GameList::Entry* ge);
|
||||
void invalidateCoverForPath(const std::string& path);
|
||||
|
||||
static QString formatTimespan(time_t timespan);
|
||||
static QString formatTimespan(const time_t timespan);
|
||||
|
||||
float m_cover_scale = 0.0f;
|
||||
std::atomic<u32> m_cover_scale_counter{0};
|
||||
|
||||
@@ -4,14 +4,17 @@
|
||||
#include "GameListRefreshThread.h"
|
||||
|
||||
#include "pcsx2/GameList.h"
|
||||
#include "pcsx2/Host.h"
|
||||
|
||||
#include "common/Assertions.h"
|
||||
#include "common/Console.h"
|
||||
#include "common/ProgressCallback.h"
|
||||
#include "common/Timer.h"
|
||||
|
||||
#include <QtWidgets/QMessageBox>
|
||||
|
||||
AsyncRefreshProgressCallback::AsyncRefreshProgressCallback(GameListRefreshThread* parent)
|
||||
AsyncRefreshProgressCallback::AsyncRefreshProgressCallback(bool popup_on_error, GameListRefreshThread* parent)
|
||||
: m_parent(parent)
|
||||
, m_popup_on_error(popup_on_error)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -57,17 +60,20 @@ void AsyncRefreshProgressCallback::SetTitle(const char* title)
|
||||
|
||||
void AsyncRefreshProgressCallback::DisplayError(const char* message)
|
||||
{
|
||||
QMessageBox::critical(nullptr, QStringLiteral("Error"), QString::fromUtf8(message));
|
||||
if (m_popup_on_error)
|
||||
Host::ReportErrorAsync(TRANSLATE_SV("GameListRefreshThread", "Error"), message);
|
||||
else
|
||||
ERROR_LOG("{}", message);
|
||||
}
|
||||
|
||||
void AsyncRefreshProgressCallback::DisplayWarning(const char* message)
|
||||
{
|
||||
QMessageBox::warning(nullptr, QStringLiteral("Warning"), QString::fromUtf8(message));
|
||||
pxFailRel("Not implemented.");
|
||||
}
|
||||
|
||||
void AsyncRefreshProgressCallback::DisplayInformation(const char* message)
|
||||
{
|
||||
QMessageBox::information(nullptr, QStringLiteral("Information"), QString::fromUtf8(message));
|
||||
pxFailRel("Not implemented.");
|
||||
}
|
||||
|
||||
void AsyncRefreshProgressCallback::DisplayDebugMessage(const char* message)
|
||||
@@ -77,17 +83,18 @@ void AsyncRefreshProgressCallback::DisplayDebugMessage(const char* message)
|
||||
|
||||
void AsyncRefreshProgressCallback::ModalError(const char* message)
|
||||
{
|
||||
QMessageBox::critical(nullptr, QStringLiteral("Error"), QString::fromUtf8(message));
|
||||
pxFailRel("Not implemented.");
|
||||
}
|
||||
|
||||
bool AsyncRefreshProgressCallback::ModalConfirmation(const char* message)
|
||||
{
|
||||
return QMessageBox::question(nullptr, QStringLiteral("Question"), QString::fromUtf8(message)) == QMessageBox::Yes;
|
||||
pxFailRel("Not implemented.");
|
||||
return false;
|
||||
}
|
||||
|
||||
void AsyncRefreshProgressCallback::ModalInformation(const char* message)
|
||||
{
|
||||
QMessageBox::information(nullptr, QStringLiteral("Information"), QString::fromUtf8(message));
|
||||
pxFailRel("Not implemented.");
|
||||
}
|
||||
|
||||
void AsyncRefreshProgressCallback::fireUpdate()
|
||||
@@ -95,9 +102,9 @@ void AsyncRefreshProgressCallback::fireUpdate()
|
||||
m_parent->refreshProgress(m_status_text, m_last_value, m_last_range);
|
||||
}
|
||||
|
||||
GameListRefreshThread::GameListRefreshThread(bool invalidate_cache)
|
||||
GameListRefreshThread::GameListRefreshThread(bool invalidate_cache, bool popup_on_error)
|
||||
: QThread()
|
||||
, m_progress(this)
|
||||
, m_progress(popup_on_error, this)
|
||||
, m_invalidate_cache(invalidate_cache)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ class GameListRefreshThread;
|
||||
class AsyncRefreshProgressCallback : public BaseProgressCallback
|
||||
{
|
||||
public:
|
||||
AsyncRefreshProgressCallback(GameListRefreshThread* parent);
|
||||
AsyncRefreshProgressCallback(bool popup_on_error, GameListRefreshThread* parent);
|
||||
|
||||
void Cancel();
|
||||
|
||||
@@ -38,6 +38,7 @@ private:
|
||||
QString m_status_text;
|
||||
int m_last_range = 1;
|
||||
int m_last_value = 0;
|
||||
bool m_popup_on_error = false;
|
||||
};
|
||||
|
||||
class GameListRefreshThread final : public QThread
|
||||
@@ -45,7 +46,7 @@ class GameListRefreshThread final : public QThread
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
GameListRefreshThread(bool invalidate_cache);
|
||||
GameListRefreshThread(bool invalidate_cache, bool popup_on_error);
|
||||
~GameListRefreshThread();
|
||||
|
||||
void cancel();
|
||||
@@ -60,4 +61,5 @@ protected:
|
||||
private:
|
||||
AsyncRefreshProgressCallback m_progress;
|
||||
bool m_invalidate_cache;
|
||||
bool m_popup_on_error;
|
||||
};
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "QtHost.h"
|
||||
#include "QtUtils.h"
|
||||
|
||||
#include "Settings/InterfaceSettingsWidget.h"
|
||||
#include "pcsx2/GameList.h"
|
||||
#include "pcsx2/Host.h"
|
||||
|
||||
@@ -44,6 +45,25 @@ static const char* SUPPORTED_FORMATS_STRING = QT_TRANSLATE_NOOP(GameListWidget,
|
||||
static constexpr float MIN_SCALE = 0.1f;
|
||||
static constexpr float MAX_SCALE = 2.0f;
|
||||
|
||||
static constexpr GameListModel::Column DEFAULT_SORT_COLUMN = GameListModel::Column_Title;
|
||||
static constexpr int DEFAULT_SORT_INDEX = static_cast<int>(DEFAULT_SORT_COLUMN);
|
||||
static constexpr Qt::SortOrder DEFAULT_SORT_ORDER = Qt::AscendingOrder;
|
||||
|
||||
static constexpr std::array<int, GameListModel::Column_Count> DEFAULT_COLUMN_WIDTHS = {{
|
||||
55, // type
|
||||
85, // code
|
||||
-1, // title
|
||||
-1, // file title
|
||||
75, // crc
|
||||
95, // time played
|
||||
90, // last played
|
||||
80, // size
|
||||
60, // region
|
||||
120 // compatibility
|
||||
}};
|
||||
static_assert(static_cast<int>(DEFAULT_COLUMN_WIDTHS.size()) <= GameListModel::Column_Count,
|
||||
"Game List: More default column widths than column types.");
|
||||
|
||||
class GameListSortModel final : public QSortFilterProxyModel
|
||||
{
|
||||
public:
|
||||
@@ -55,18 +75,21 @@ public:
|
||||
|
||||
void setFilterType(GameList::EntryType type)
|
||||
{
|
||||
beginFilterChange();
|
||||
m_filter_type = type;
|
||||
invalidateRowsFilter();
|
||||
endFilterChange(Direction::Rows);
|
||||
}
|
||||
void setFilterRegion(GameList::Region region)
|
||||
{
|
||||
beginFilterChange();
|
||||
m_filter_region = region;
|
||||
invalidateRowsFilter();
|
||||
endFilterChange(Direction::Rows);
|
||||
}
|
||||
void setFilterName(const QString& name)
|
||||
{
|
||||
beginFilterChange();
|
||||
m_filter_name = name;
|
||||
invalidateRowsFilter();
|
||||
endFilterChange(Direction::Rows);
|
||||
}
|
||||
|
||||
bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override
|
||||
@@ -106,6 +129,7 @@ private:
|
||||
|
||||
namespace
|
||||
{
|
||||
// Used for Type, Region, and Compatibility columns to center icons; Qt::AlignCenter only works on DisplayRole (text).
|
||||
class GameListIconStyleDelegate final : public QStyledItemDelegate
|
||||
{
|
||||
public:
|
||||
@@ -115,62 +139,61 @@ namespace
|
||||
}
|
||||
~GameListIconStyleDelegate() = default;
|
||||
|
||||
// See: QStyledItemDelegate::paint(), QItemDelegate::drawDecoration(), and Qt::QStyleOptionViewItem.
|
||||
void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override
|
||||
{
|
||||
// https://stackoverflow.com/questions/32216568/how-to-set-icon-center-in-qtableview
|
||||
Q_ASSERT(index.isValid());
|
||||
|
||||
// Draw the base item, with a blank icon
|
||||
QStyleOptionViewItem opt = option;
|
||||
initStyleOption(&opt, index);
|
||||
opt.icon = QIcon();
|
||||
// Based on QStyledItemDelegate::paint()
|
||||
const QStyle* style = option.widget ? option.widget->style() : QApplication::style();
|
||||
style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, option.widget);
|
||||
// Draw highlight for cell.
|
||||
QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &option, painter, option.widget);
|
||||
|
||||
// Fetch icon pixmap
|
||||
const QRect r = option.rect;
|
||||
const QPixmap pix = qvariant_cast<QPixmap>(index.data(Qt::DecorationRole));
|
||||
// Fetch icon pixmap and stop if no icon exists
|
||||
const QPixmap icon = qvariant_cast<QPixmap>(index.data(Qt::DecorationRole));
|
||||
|
||||
if (pix.isNull())
|
||||
if (icon.isNull())
|
||||
return;
|
||||
|
||||
const int pix_width = static_cast<int>(pix.width() / pix.devicePixelRatio());
|
||||
const int pix_height = static_cast<int>(pix.height() / pix.devicePixelRatio());
|
||||
|
||||
// Clip the pixmaps so they don't extend outside the column
|
||||
// Save painter state and restore later so clip setting doesn't persist across cell draws.
|
||||
painter->save();
|
||||
painter->setClipRect(option.rect);
|
||||
|
||||
// Draw the icon, using code derived from QItemDelegate::drawDecoration()
|
||||
const bool enabled = option.state & QStyle::State_Enabled;
|
||||
const QPoint p = QPoint((r.width() - pix_width) / 2, (r.height() - pix_height) / 2);
|
||||
// Clip pixmap so it doesn't extend outside the cell.
|
||||
const QRect rect = option.rect;
|
||||
painter->setClipRect(rect);
|
||||
|
||||
// Determine starting location of icon (Qt uses top-left origin).
|
||||
const int icon_width = static_cast<int>(static_cast<qreal>(icon.width()) / icon.devicePixelRatio());
|
||||
const int icon_height = static_cast<int>(static_cast<qreal>(icon.height()) / icon.devicePixelRatio());
|
||||
const QPoint icon_top_left = QPoint((rect.width() - icon_width) / 2, (rect.height() - icon_height) / 2);
|
||||
|
||||
// Change palette if the item is selected.
|
||||
if (option.state & QStyle::State_Selected)
|
||||
{
|
||||
// See QItemDelegate::selectedPixmap()
|
||||
// Set color based on whether cell is enabled.
|
||||
const bool enabled = option.state & QStyle::State_Enabled;
|
||||
QColor color = option.palette.color(enabled ? QPalette::Normal : QPalette::Disabled, QPalette::Highlight);
|
||||
color.setAlphaF(0.3f);
|
||||
|
||||
QString key = QString::fromStdString(fmt::format("{:016X}-{:d}-{:08X}", pix.cacheKey(), enabled, color.rgba()));
|
||||
QPixmap pm;
|
||||
if (!QPixmapCache::find(key, &pm))
|
||||
// Fetch pixmap from cache or construct a new one.
|
||||
const QString key = QString::fromStdString(fmt::format("{:016X}-{:d}-{:08X}", icon.cacheKey(), enabled, color.rgba()));
|
||||
QPixmap highlighted_icon;
|
||||
if (!QPixmapCache::find(key, &highlighted_icon))
|
||||
{
|
||||
QImage img = pix.toImage().convertToFormat(QImage::Format_ARGB32_Premultiplied);
|
||||
QImage img = icon.toImage().convertToFormat(QImage::Format_ARGB32_Premultiplied);
|
||||
|
||||
QPainter tinted_painter(&img);
|
||||
tinted_painter.setCompositionMode(QPainter::CompositionMode_SourceAtop);
|
||||
tinted_painter.fillRect(0, 0, img.width(), img.height(), color);
|
||||
tinted_painter.end();
|
||||
|
||||
pm = QPixmap(QPixmap::fromImage(img));
|
||||
QPixmapCache::insert(key, pm);
|
||||
highlighted_icon = QPixmap(QPixmap::fromImage(img));
|
||||
QPixmapCache::insert(key, highlighted_icon);
|
||||
}
|
||||
|
||||
painter->drawPixmap(r.topLeft() + p, pm);
|
||||
painter->drawPixmap(rect.topLeft() + icon_top_left, highlighted_icon);
|
||||
}
|
||||
else
|
||||
{
|
||||
painter->drawPixmap(r.topLeft() + p, pix);
|
||||
painter->drawPixmap(rect.topLeft() + icon_top_left, icon);
|
||||
}
|
||||
|
||||
// Restore the old clip path.
|
||||
@@ -234,33 +257,46 @@ void GameListWidget::initialize()
|
||||
m_table_view = new QTableView(m_ui.stack);
|
||||
m_table_view->setModel(m_sort_model);
|
||||
m_table_view->setSortingEnabled(true);
|
||||
m_table_view->horizontalHeader()->setSectionsMovable(true);
|
||||
m_table_view->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
m_table_view->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||
m_table_view->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
m_table_view->setAlternatingRowColors(true);
|
||||
m_table_view->setMouseTracking(true);
|
||||
m_table_view->setShowGrid(false);
|
||||
m_table_view->setCurrentIndex({});
|
||||
m_table_view->setCurrentIndex(QModelIndex());
|
||||
m_table_view->horizontalHeader()->setHighlightSections(false);
|
||||
m_table_view->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
m_table_view->verticalHeader()->hide();
|
||||
m_table_view->setVerticalScrollMode(QAbstractItemView::ScrollMode::ScrollPerPixel);
|
||||
|
||||
// Custom painter to center-align DisplayRoles (icons)
|
||||
m_table_view->setItemDelegateForColumn(0, new GameListIconStyleDelegate(this));
|
||||
m_table_view->setItemDelegateForColumn(8, new GameListIconStyleDelegate(this));
|
||||
m_table_view->setItemDelegateForColumn(9, new GameListIconStyleDelegate(this));
|
||||
|
||||
loadTableViewColumnVisibilitySettings();
|
||||
loadTableViewColumnSortSettings();
|
||||
|
||||
connect(m_table_view->selectionModel(), &QItemSelectionModel::currentChanged, this,
|
||||
&GameListWidget::onSelectionModelCurrentChanged);
|
||||
connect(m_table_view, &QTableView::activated, this, &GameListWidget::onTableViewItemActivated);
|
||||
connect(m_table_view, &QTableView::customContextMenuRequested, this,
|
||||
&GameListWidget::onTableViewContextMenuRequested);
|
||||
connect(m_table_view->horizontalHeader(), &QHeaderView::customContextMenuRequested, this,
|
||||
&GameListWidget::onTableViewHeaderContextMenuRequested);
|
||||
&GameListWidget::onTableViewHeaderContextMenuRequested);
|
||||
|
||||
// Save state when header state changes (hiding and showing handled within onTableViewHeaderContextMenuRequested).
|
||||
connect(m_table_view->horizontalHeader(), &QHeaderView::sectionMoved, this, &GameListWidget::onTableHeaderStateChanged);
|
||||
connect(m_table_view->horizontalHeader(), &QHeaderView::sectionResized, this, &GameListWidget::onTableHeaderStateChanged);
|
||||
connect(m_table_view->horizontalHeader(), &QHeaderView::sortIndicatorChanged, this,
|
||||
&GameListWidget::saveTableViewColumnSortSettings);
|
||||
[this](const int column, const Qt::SortOrder sort_order) { GameListWidget::saveSortSettings(column, sort_order); GameListWidget::onTableHeaderStateChanged(); });
|
||||
|
||||
// Load the last session's header state or create a new one.
|
||||
if (Host::ContainsBaseSettingValue("GameListTableView", "HeaderState"))
|
||||
loadTableHeaderState();
|
||||
else
|
||||
applyTableHeaderDefaults();
|
||||
|
||||
// After header state load to account for user-specified sort.
|
||||
m_table_view->setSortingEnabled(true);
|
||||
|
||||
m_ui.stack->insertWidget(0, m_table_view);
|
||||
|
||||
@@ -292,7 +328,7 @@ void GameListWidget::initialize()
|
||||
m_empty_ui.setupUi(m_empty_widget);
|
||||
m_empty_ui.supportedFormats->setText(qApp->translate("GameListWidget", SUPPORTED_FORMATS_STRING));
|
||||
connect(m_empty_ui.addGameDirectory, &QPushButton::clicked, this, [this]() { emit addGameDirectoryRequested(); });
|
||||
connect(m_empty_ui.scanForNewGames, &QPushButton::clicked, this, [this]() { refresh(false); });
|
||||
connect(m_empty_ui.scanForNewGames, &QPushButton::clicked, this, [this]() { refresh(false, true); });
|
||||
connect(qApp, &QGuiApplication::applicationStateChanged, this, [this]() { GameListWidget::updateCustomBackgroundState(); });
|
||||
m_ui.stack->insertWidget(2, m_empty_widget);
|
||||
|
||||
@@ -308,84 +344,30 @@ void GameListWidget::initialize()
|
||||
setCustomBackground();
|
||||
}
|
||||
|
||||
static void resizeAndPadImage(QImage* image, int expected_width, int expected_height, bool fill_with_top_left, bool expand_to_fill)
|
||||
void GameListWidget::setCustomBackground()
|
||||
{
|
||||
const qreal dpr = image->devicePixelRatio();
|
||||
const int dpr_expected_width = static_cast<int>(static_cast<qreal>(expected_width) * dpr);
|
||||
const int dpr_expected_height = static_cast<int>(static_cast<qreal>(expected_height) * dpr);
|
||||
if (image->width() == dpr_expected_width && image->height() == dpr_expected_height)
|
||||
return;
|
||||
|
||||
// Resize
|
||||
if (((static_cast<float>(image->width()) / static_cast<float>(image->height())) >=
|
||||
(static_cast<float>(dpr_expected_width) / static_cast<float>(dpr_expected_height))) != expand_to_fill)
|
||||
{
|
||||
*image = image->scaledToWidth(dpr_expected_width, Qt::SmoothTransformation);
|
||||
}
|
||||
else
|
||||
{
|
||||
*image = image->scaledToHeight(dpr_expected_height, Qt::SmoothTransformation);
|
||||
}
|
||||
|
||||
if (image->width() == dpr_expected_width && image->height() == dpr_expected_height)
|
||||
return;
|
||||
|
||||
// Padding
|
||||
int xoffs = 0;
|
||||
int yoffs = 0;
|
||||
const int image_width = image->width();
|
||||
const int image_height = image->height();
|
||||
if ((image_width < dpr_expected_width) != expand_to_fill)
|
||||
xoffs = static_cast<int>(static_cast<qreal>((dpr_expected_width - image_width) / 2) / dpr);
|
||||
if ((image_height < dpr_expected_height) != expand_to_fill)
|
||||
yoffs = static_cast<int>(static_cast<qreal>((dpr_expected_height - image_height) / 2) / dpr);
|
||||
|
||||
QImage padded_image(dpr_expected_width, dpr_expected_height, QImage::Format_ARGB32);
|
||||
padded_image.setDevicePixelRatio(dpr);
|
||||
if (fill_with_top_left)
|
||||
padded_image.fill(image->pixel(0, 0));
|
||||
else
|
||||
padded_image.fill(Qt::transparent);
|
||||
|
||||
// Painting
|
||||
QPainter painter;
|
||||
const float opacity = Host::GetBaseFloatSettingValue("UI", "GameListBackgroundOpacity");
|
||||
if (painter.begin(&padded_image))
|
||||
{
|
||||
painter.setOpacity((static_cast<float>(opacity / 100.0f))); // Qt expects the range to be from 0.0 to 1.0
|
||||
painter.setCompositionMode(QPainter::CompositionMode_Source);
|
||||
painter.drawImage(xoffs, yoffs, *image);
|
||||
painter.end();
|
||||
}
|
||||
|
||||
*image = std::move(padded_image);
|
||||
}
|
||||
|
||||
void GameListWidget::setCustomBackground(bool force_refresh)
|
||||
{
|
||||
std::string path = Host::GetBaseStringSettingValue("UI", "GameListBackgroundPath");
|
||||
bool enabled = Host::GetBaseBoolSettingValue("UI", "GameListBackgroundEnabled");
|
||||
bool fill = Host::GetBaseBoolSettingValue("UI", "GameListBackgroundFill");
|
||||
|
||||
// Cleanup old animation if it still exists on gamelist
|
||||
if (m_background_movie != nullptr)
|
||||
{
|
||||
m_background_movie->disconnect(this);
|
||||
delete m_background_movie;
|
||||
m_background_movie = nullptr;
|
||||
}
|
||||
|
||||
std::string path = Host::GetBaseStringSettingValue("UI", "GameListBackgroundPath");
|
||||
if (!Path::IsAbsolute(path))
|
||||
path = Path::Combine(EmuFolders::DataRoot, path);
|
||||
|
||||
// Only try to create background both if path are valid and custom background are enabled
|
||||
if ((!path.empty() && FileSystem::FileExists(path.c_str())) && enabled)
|
||||
// Only try to create background if path are valid
|
||||
if (!path.empty() && FileSystem::FileExists(path.c_str()))
|
||||
{
|
||||
QMovie* new_movie;
|
||||
if (Path::GetExtension(path) == "png")
|
||||
QString img_path = QString::fromStdString(path);
|
||||
if (img_path.endsWith(".png", Qt::CaseInsensitive))
|
||||
// Use apng plugin
|
||||
new_movie = new QMovie(QString::fromStdString(path), "apng", this);
|
||||
new_movie = new QMovie(img_path, "apng", this);
|
||||
else
|
||||
new_movie = new QMovie(QString::fromStdString(path), QByteArray(), this);
|
||||
new_movie = new QMovie(img_path, QByteArray(), this);
|
||||
|
||||
if (new_movie->isValid())
|
||||
m_background_movie = new_movie;
|
||||
@@ -396,7 +378,7 @@ void GameListWidget::setCustomBackground(bool force_refresh)
|
||||
}
|
||||
}
|
||||
|
||||
// If there is no valid background then reset fallback to UI state
|
||||
// If there is no valid background then reset fallback to default UI state
|
||||
if (!m_background_movie)
|
||||
{
|
||||
m_ui.stack->setPalette(QApplication::palette());
|
||||
@@ -404,36 +386,60 @@ void GameListWidget::setCustomBackground(bool force_refresh)
|
||||
return;
|
||||
}
|
||||
|
||||
// Background is valid, connect the signals and start animation in gamelist
|
||||
connect(m_background_movie, &QMovie::frameChanged, this, [this, fill]() { processBackgroundFrames(fill); });
|
||||
updateCustomBackgroundState(force_refresh);
|
||||
// Retrieve scaling setting
|
||||
m_background_scaling = QtUtils::ScalingMode::Fit;
|
||||
const std::string ar_value = Host::GetBaseStringSettingValue("UI", "GameListBackgroundMode", InterfaceSettingsWidget::BACKGROUND_SCALE_NAMES[static_cast<u8>(QtUtils::ScalingMode::Fit)]);
|
||||
for (u8 i = 0; i < static_cast<u8>(QtUtils::ScalingMode::MaxCount); i++)
|
||||
{
|
||||
if (!(InterfaceSettingsWidget::BACKGROUND_SCALE_NAMES[i] == nullptr))
|
||||
{
|
||||
if (ar_value == InterfaceSettingsWidget::BACKGROUND_SCALE_NAMES[i])
|
||||
{
|
||||
m_background_scaling = static_cast<QtUtils::ScalingMode>(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve opacity setting
|
||||
m_background_opacity = Host::GetBaseFloatSettingValue("UI", "GameListBackgroundOpacity", 100.0f);
|
||||
|
||||
// Selected Custom background is valid, connect the signals and start animation in gamelist
|
||||
connect(m_background_movie, &QMovie::frameChanged, this, &GameListWidget::processBackgroundFrames, Qt::UniqueConnection);
|
||||
updateCustomBackgroundState(true);
|
||||
m_table_view->setAlternatingRowColors(false);
|
||||
}
|
||||
|
||||
void GameListWidget::updateCustomBackgroundState(bool force_start)
|
||||
void GameListWidget::updateCustomBackgroundState(const bool force_start)
|
||||
{
|
||||
if (m_background_movie)
|
||||
if (m_background_movie && m_background_movie->isValid())
|
||||
{
|
||||
if ((isVisible() && (isActiveWindow() || force_start)) && qGuiApp->applicationState() == Qt::ApplicationActive)
|
||||
m_background_movie->start();
|
||||
m_background_movie->setPaused(false);
|
||||
else
|
||||
m_background_movie->stop();
|
||||
m_background_movie->setPaused(true);
|
||||
}
|
||||
}
|
||||
|
||||
void GameListWidget::processBackgroundFrames(bool fill_area)
|
||||
void GameListWidget::processBackgroundFrames()
|
||||
{
|
||||
QImage img = m_background_movie->currentImage();
|
||||
img.setDevicePixelRatio(devicePixelRatioF());
|
||||
const int widget_width = m_ui.stack->width();
|
||||
const int widget_height = m_ui.stack->height();
|
||||
if (m_background_movie && m_background_movie->isValid())
|
||||
{
|
||||
const int widget_width = m_ui.stack->width();
|
||||
const int widget_height = m_ui.stack->height();
|
||||
|
||||
resizeAndPadImage(&img, widget_width, widget_height, false, fill_area);
|
||||
if (widget_width <= 0 || widget_height <= 0)
|
||||
return;
|
||||
|
||||
QPalette new_palette(m_ui.stack->palette());
|
||||
new_palette.setBrush(QPalette::Base, img);
|
||||
m_ui.stack->setPalette(new_palette);
|
||||
QPixmap pm = m_background_movie->currentPixmap();
|
||||
const qreal dpr = devicePixelRatioF();
|
||||
|
||||
QtUtils::resizeAndScalePixmap(&pm, widget_width, widget_height, dpr, m_background_scaling, m_background_opacity);
|
||||
|
||||
QPalette bg_palette(m_ui.stack->palette());
|
||||
bg_palette.setBrush(QPalette::Base, pm);
|
||||
m_ui.stack->setPalette(bg_palette);
|
||||
}
|
||||
}
|
||||
|
||||
bool GameListWidget::isShowingGameList() const
|
||||
@@ -451,11 +457,11 @@ bool GameListWidget::getShowGridCoverTitles() const
|
||||
return m_model->getShowCoverTitles();
|
||||
}
|
||||
|
||||
void GameListWidget::refresh(bool invalidate_cache)
|
||||
void GameListWidget::refresh(bool invalidate_cache, bool popup_on_error)
|
||||
{
|
||||
cancelRefresh();
|
||||
|
||||
m_refresh_thread = new GameListRefreshThread(invalidate_cache);
|
||||
m_refresh_thread = new GameListRefreshThread(invalidate_cache, popup_on_error);
|
||||
connect(m_refresh_thread, &GameListRefreshThread::refreshProgress, this, &GameListWidget::onRefreshProgress,
|
||||
Qt::QueuedConnection);
|
||||
connect(m_refresh_thread, &GameListRefreshThread::refreshComplete, this, &GameListWidget::onRefreshComplete,
|
||||
@@ -552,18 +558,24 @@ void GameListWidget::onListViewContextMenuRequested(const QPoint& point)
|
||||
void GameListWidget::onTableViewHeaderContextMenuRequested(const QPoint& point)
|
||||
{
|
||||
QMenu menu;
|
||||
QHeaderView* header = m_table_view->horizontalHeader();
|
||||
if (!header)
|
||||
return;
|
||||
|
||||
int column_visual = 0;
|
||||
for (int column = 0; column < GameListModel::Column_Count; column++)
|
||||
{
|
||||
// The "cover" column is the game grid and cannot be hidden.
|
||||
if (column == GameListModel::Column_Cover)
|
||||
continue;
|
||||
|
||||
QAction* action = menu.addAction(m_model->getColumnDisplayName(column));
|
||||
column_visual = header->visualIndex(column);
|
||||
QAction* action = menu.addAction(m_model->getColumnDisplayName(column_visual));
|
||||
action->setCheckable(true);
|
||||
action->setChecked(!m_table_view->isColumnHidden(column));
|
||||
connect(action, &QAction::toggled, [this, column](bool enabled) {
|
||||
m_table_view->setColumnHidden(column, !enabled);
|
||||
saveTableViewColumnVisibilitySettings(column);
|
||||
action->setChecked(!m_table_view->isColumnHidden(column_visual));
|
||||
connect(action, &QAction::toggled, [this, column_visual](bool enabled) {
|
||||
m_table_view->setColumnHidden(column_visual, !enabled);
|
||||
onTableHeaderStateChanged();
|
||||
resizeTableViewColumnsToFit();
|
||||
});
|
||||
}
|
||||
@@ -706,7 +718,7 @@ void GameListWidget::resizeEvent(QResizeEvent* event)
|
||||
QWidget::resizeEvent(event);
|
||||
resizeTableViewColumnsToFit();
|
||||
m_model->updateCacheSize(width(), height());
|
||||
setCustomBackground();
|
||||
processBackgroundFrames();
|
||||
}
|
||||
|
||||
bool GameListWidget::event(QEvent* event)
|
||||
@@ -724,123 +736,141 @@ bool GameListWidget::event(QEvent* event)
|
||||
void GameListWidget::resizeTableViewColumnsToFit()
|
||||
{
|
||||
QtUtils::ResizeColumnsForTableView(m_table_view, {
|
||||
45, // type
|
||||
80, // code
|
||||
-1, // title
|
||||
-1, // file title
|
||||
65, // crc
|
||||
80, // time played
|
||||
80, // last played
|
||||
80, // size
|
||||
60, // region
|
||||
120 // compatibility
|
||||
DEFAULT_COLUMN_WIDTHS[GameListModel::Column_Type],
|
||||
DEFAULT_COLUMN_WIDTHS[GameListModel::Column_Serial],
|
||||
DEFAULT_COLUMN_WIDTHS[GameListModel::Column_FileTitle],
|
||||
DEFAULT_COLUMN_WIDTHS[GameListModel::Column_Type],
|
||||
DEFAULT_COLUMN_WIDTHS[GameListModel::Column_CRC],
|
||||
DEFAULT_COLUMN_WIDTHS[GameListModel::Column_TimePlayed],
|
||||
DEFAULT_COLUMN_WIDTHS[GameListModel::Column_LastPlayed],
|
||||
DEFAULT_COLUMN_WIDTHS[GameListModel::Column_Size],
|
||||
DEFAULT_COLUMN_WIDTHS[GameListModel::Column_Region],
|
||||
DEFAULT_COLUMN_WIDTHS[GameListModel::Column_Compatibility],
|
||||
});
|
||||
}
|
||||
|
||||
static std::string getColumnVisibilitySettingsKeyName(int column)
|
||||
void GameListWidget::loadTableHeaderState()
|
||||
{
|
||||
return StringUtil::StdStringFromFormat("Show%s",
|
||||
QHeaderView* header = m_table_view->horizontalHeader();
|
||||
if (!header)
|
||||
return;
|
||||
|
||||
// Decode Base64 string from settings to QByteArray state.
|
||||
const std::string state_setting = Host::GetBaseStringSettingValue("GameListTableView", "HeaderState");
|
||||
if (state_setting.empty())
|
||||
return;
|
||||
|
||||
QSignalBlocker blocker(header);
|
||||
header->restoreState(QByteArray::fromBase64(QByteArray::fromStdString(state_setting)));
|
||||
}
|
||||
|
||||
void GameListWidget::onTableHeaderStateChanged()
|
||||
{
|
||||
QHeaderView* header = m_table_view->horizontalHeader();
|
||||
if (!header)
|
||||
return;
|
||||
|
||||
// Encode QByteArray state as Base64 string for storage.
|
||||
Host::SetBaseStringSettingValue("GameListTableView", "HeaderState", header->saveState().toBase64());
|
||||
}
|
||||
|
||||
void GameListWidget::applyTableHeaderDefaults()
|
||||
{
|
||||
QHeaderView* header = m_table_view->horizontalHeader();
|
||||
if (!header)
|
||||
return;
|
||||
|
||||
{
|
||||
QSignalBlocker blocker(header);
|
||||
header->hideSection(GameListModel::Column_FileTitle);
|
||||
header->hideSection(GameListModel::Column_CRC);
|
||||
header->hideSection(GameListModel::Column_Cover);
|
||||
for (int column = 0; column < GameListModel::Column_Count; column++)
|
||||
{
|
||||
if (column == GameListModel::Column_Cover)
|
||||
continue;
|
||||
|
||||
header->resizeSection(column, DEFAULT_COLUMN_WIDTHS[column]);
|
||||
}
|
||||
header->setSortIndicator(DEFAULT_SORT_INDEX, DEFAULT_SORT_ORDER);
|
||||
}
|
||||
|
||||
Host::SetBaseStringSettingValue("GameListTableView", "HeaderState", header->saveState().toBase64());
|
||||
}
|
||||
|
||||
// TODO (Tech): Create a button for this in the minibar. Currently unused.
|
||||
void GameListWidget::resetTableHeaderToDefault()
|
||||
{
|
||||
QHeaderView* header = m_table_view->horizontalHeader();
|
||||
if (!header)
|
||||
return;
|
||||
|
||||
{
|
||||
QSignalBlocker blocker(header);
|
||||
for (int column = 0; column < GameListModel::Column_Count; column++)
|
||||
{
|
||||
if (column == GameListModel::Column_Cover)
|
||||
continue;
|
||||
|
||||
// Reset size, position, and visibility.
|
||||
header->resizeSection(column, DEFAULT_COLUMN_WIDTHS[column]);
|
||||
header->moveSection(header->visualIndex(column), column);
|
||||
header->setSectionHidden(column,
|
||||
column == GameListModel::Column_CRC || column == GameListModel::Column_FileTitle);
|
||||
}
|
||||
header->hideSection(GameListModel::Column_Cover);
|
||||
header->setSortIndicator(DEFAULT_SORT_INDEX, DEFAULT_SORT_ORDER);
|
||||
}
|
||||
|
||||
Host::SetBaseStringSettingValue("GameListTableView", "HeaderState", header->saveState().toBase64());
|
||||
}
|
||||
|
||||
void GameListWidget::saveSortSettings(const int column, const Qt::SortOrder sort_order)
|
||||
{
|
||||
Host::SetBaseStringSettingValue("GameListTableView", "SortColumn",
|
||||
GameListModel::getColumnName(static_cast<GameListModel::Column>(column)));
|
||||
Host::SetBaseBoolSettingValue("GameListTableView", "SortDescending", static_cast<bool>(sort_order));
|
||||
}
|
||||
|
||||
void GameListWidget::loadTableViewColumnVisibilitySettings()
|
||||
std::optional<GameList::Entry> GameListWidget::getSelectedEntry() const
|
||||
{
|
||||
static constexpr std::array<bool, GameListModel::Column_Count> DEFAULT_VISIBILITY = {{
|
||||
true, // type
|
||||
true, // code
|
||||
true, // title
|
||||
false, // file title
|
||||
false, // crc
|
||||
true, // time played
|
||||
true, // last played
|
||||
true, // size
|
||||
true, // region
|
||||
true // compatibility
|
||||
}};
|
||||
auto lock = GameList::GetLock();
|
||||
|
||||
for (int column = 0; column < GameListModel::Column_Count; column++)
|
||||
{
|
||||
const bool visible = Host::GetBaseBoolSettingValue(
|
||||
"GameListTableView", getColumnVisibilitySettingsKeyName(column).c_str(), DEFAULT_VISIBILITY[column]);
|
||||
m_table_view->setColumnHidden(column, !visible);
|
||||
}
|
||||
}
|
||||
|
||||
void GameListWidget::saveTableViewColumnVisibilitySettings()
|
||||
{
|
||||
for (int column = 0; column < GameListModel::Column_Count; column++)
|
||||
{
|
||||
const bool visible = !m_table_view->isColumnHidden(column);
|
||||
Host::SetBaseBoolSettingValue("GameListTableView", getColumnVisibilitySettingsKeyName(column).c_str(), visible);
|
||||
Host::CommitBaseSettingChanges();
|
||||
}
|
||||
}
|
||||
|
||||
void GameListWidget::saveTableViewColumnVisibilitySettings(int column)
|
||||
{
|
||||
const bool visible = !m_table_view->isColumnHidden(column);
|
||||
Host::SetBaseBoolSettingValue("GameListTableView", getColumnVisibilitySettingsKeyName(column).c_str(), visible);
|
||||
Host::CommitBaseSettingChanges();
|
||||
}
|
||||
|
||||
void GameListWidget::loadTableViewColumnSortSettings()
|
||||
{
|
||||
const GameListModel::Column DEFAULT_SORT_COLUMN = GameListModel::Column_Type;
|
||||
const bool DEFAULT_SORT_DESCENDING = false;
|
||||
|
||||
const GameListModel::Column sort_column =
|
||||
GameListModel::getColumnIdForName(Host::GetBaseStringSettingValue("GameListTableView", "SortColumn"))
|
||||
.value_or(DEFAULT_SORT_COLUMN);
|
||||
const bool sort_descending =
|
||||
Host::GetBaseBoolSettingValue("GameListTableView", "SortDescending", DEFAULT_SORT_DESCENDING);
|
||||
const Qt::SortOrder sort_order = sort_descending ? Qt::DescendingOrder : Qt::AscendingOrder;
|
||||
m_sort_model->sort(sort_column, sort_order);
|
||||
if (QHeaderView* hv = m_table_view->horizontalHeader())
|
||||
hv->setSortIndicator(sort_column, sort_order);
|
||||
}
|
||||
|
||||
void GameListWidget::saveTableViewColumnSortSettings(const int sort_column, const Qt::SortOrder sort_order)
|
||||
{
|
||||
if (sort_column >= 0 && sort_column < GameListModel::Column_Count)
|
||||
{
|
||||
Host::SetBaseStringSettingValue(
|
||||
"GameListTableView", "SortColumn", GameListModel::getColumnName(static_cast<GameListModel::Column>(sort_column)));
|
||||
}
|
||||
|
||||
Host::SetBaseBoolSettingValue("GameListTableView", "SortDescending", sort_order == Qt::DescendingOrder);
|
||||
Host::CommitBaseSettingChanges();
|
||||
}
|
||||
|
||||
const GameList::Entry* GameListWidget::getSelectedEntry() const
|
||||
{
|
||||
const GameList::Entry* entry;
|
||||
if (m_ui.stack->currentIndex() == 0)
|
||||
{
|
||||
const QItemSelectionModel* selection_model = m_table_view->selectionModel();
|
||||
if (!selection_model->hasSelection())
|
||||
return nullptr;
|
||||
return std::nullopt;
|
||||
|
||||
const QModelIndexList selected_rows = selection_model->selectedRows();
|
||||
if (selected_rows.empty())
|
||||
return nullptr;
|
||||
return std::nullopt;
|
||||
|
||||
const QModelIndex source_index = m_sort_model->mapToSource(selected_rows[0]);
|
||||
if (!source_index.isValid())
|
||||
return nullptr;
|
||||
return std::nullopt;
|
||||
|
||||
return GameList::GetEntryByIndex(source_index.row());
|
||||
entry = GameList::GetEntryByIndex(source_index.row());
|
||||
}
|
||||
else
|
||||
{
|
||||
const QItemSelectionModel* selection_model = m_list_view->selectionModel();
|
||||
if (!selection_model->hasSelection())
|
||||
return nullptr;
|
||||
return std::nullopt;
|
||||
|
||||
const QModelIndex source_index = m_sort_model->mapToSource(selection_model->currentIndex());
|
||||
if (!source_index.isValid())
|
||||
return nullptr;
|
||||
return std::nullopt;
|
||||
|
||||
return GameList::GetEntryByIndex(source_index.row());
|
||||
entry = GameList::GetEntryByIndex(source_index.row());
|
||||
}
|
||||
|
||||
if (!entry)
|
||||
return std::nullopt;
|
||||
|
||||
// Copy the entry here instead of keeping the lock held to avoid deadlocks.
|
||||
return *entry;
|
||||
}
|
||||
|
||||
void GameListWidget::rescanFile(const std::string& path)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "QtUtils.h"
|
||||
#include "ui_EmptyGameListWidget.h"
|
||||
#include "ui_GameListWidget.h"
|
||||
|
||||
@@ -46,18 +47,18 @@ public:
|
||||
void initialize();
|
||||
void resizeTableViewColumnsToFit();
|
||||
|
||||
void refresh(bool invalidate_cache);
|
||||
void refresh(bool invalidate_cache, bool popup_on_error);
|
||||
void cancelRefresh();
|
||||
void reloadThemeSpecificImages();
|
||||
void setCustomBackground(bool force = false);
|
||||
void updateCustomBackgroundState(bool force_start = false);
|
||||
void processBackgroundFrames(bool fill_area);
|
||||
void setCustomBackground();
|
||||
void updateCustomBackgroundState(const bool force_start = false);
|
||||
void processBackgroundFrames();
|
||||
|
||||
bool isShowingGameList() const;
|
||||
bool isShowingGameGrid() const;
|
||||
bool getShowGridCoverTitles() const;
|
||||
|
||||
const GameList::Entry* getSelectedEntry() const;
|
||||
std::optional<GameList::Entry> getSelectedEntry() const;
|
||||
|
||||
/// Rescans a single file. NOTE: Happens on UI thread.
|
||||
void rescanFile(const std::string& path);
|
||||
@@ -84,6 +85,7 @@ private Q_SLOTS:
|
||||
void onListViewItemActivated(const QModelIndex& index);
|
||||
void onListViewContextMenuRequested(const QPoint& point);
|
||||
void onCoverScaleChanged();
|
||||
void onTableHeaderStateChanged();
|
||||
|
||||
public Q_SLOTS:
|
||||
void showGameList();
|
||||
@@ -101,11 +103,10 @@ protected:
|
||||
bool event(QEvent* event) override;
|
||||
|
||||
private:
|
||||
void loadTableViewColumnVisibilitySettings();
|
||||
void saveTableViewColumnVisibilitySettings();
|
||||
void saveTableViewColumnVisibilitySettings(int column);
|
||||
void loadTableViewColumnSortSettings();
|
||||
void saveTableViewColumnSortSettings(const int sort_column, const Qt::SortOrder sort_order);
|
||||
void loadTableHeaderState();
|
||||
void applyTableHeaderDefaults();
|
||||
void resetTableHeaderToDefault();
|
||||
void saveSortSettings(int column, Qt::SortOrder sort_order);
|
||||
void listZoom(float delta);
|
||||
void updateToolbar();
|
||||
|
||||
@@ -122,4 +123,6 @@ private:
|
||||
GameListRefreshThread* m_refresh_thread = nullptr;
|
||||
|
||||
QMovie* m_background_movie = nullptr;
|
||||
QtUtils::ScalingMode m_background_scaling = QtUtils::ScalingMode::Fit;
|
||||
float m_background_opacity = 100.0f;
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -20,7 +20,7 @@
|
||||
class QProgressBar;
|
||||
|
||||
class AutoUpdaterDialog;
|
||||
class DisplayWidget;
|
||||
class DisplaySurface;
|
||||
class DisplayContainer;
|
||||
class GameListWidget;
|
||||
class ControllerSettingsWindow;
|
||||
@@ -69,6 +69,7 @@ public:
|
||||
friend MainWindow;
|
||||
|
||||
QWidget* m_dialog_parent;
|
||||
bool m_has_lock;
|
||||
bool m_was_paused;
|
||||
bool m_was_fullscreen;
|
||||
};
|
||||
@@ -113,7 +114,7 @@ public:
|
||||
void checkMousePosition(int x, int y);
|
||||
public Q_SLOTS:
|
||||
void checkForUpdates(bool display_message, bool force_check);
|
||||
void refreshGameList(bool invalidate_cache);
|
||||
void refreshGameList(bool invalidate_cache, bool popup_on_error);
|
||||
void cancelGameListRefresh();
|
||||
void reportInfo(const QString& title, const QString& message);
|
||||
void reportError(const QString& title, const QString& message);
|
||||
@@ -170,6 +171,9 @@ private Q_SLOTS:
|
||||
void onAboutActionTriggered();
|
||||
void onToolsOpenDataDirectoryTriggered();
|
||||
void onToolsCoverDownloaderTriggered();
|
||||
#if !defined(__APPLE__)
|
||||
void onCreateGameShortcutTriggered();
|
||||
#endif
|
||||
void onToolsEditCheatsPatchesTriggered(bool cheats);
|
||||
void onCreateMemoryCardOpenRequested();
|
||||
void updateTheme();
|
||||
@@ -254,7 +258,6 @@ private:
|
||||
bool shouldAbortForMemcardBusy(const VMLock& lock);
|
||||
|
||||
QWidget* getContentParent();
|
||||
QWidget* getDisplayContainer() const;
|
||||
void saveDisplayWindowGeometryToConfig();
|
||||
void restoreDisplayWindowGeometryFromConfig();
|
||||
void createDisplayWidget(bool fullscreen, bool render_to_main);
|
||||
@@ -271,11 +274,11 @@ private:
|
||||
QString getDiscDevicePath(const QString& title);
|
||||
|
||||
void startGameListEntry(
|
||||
const GameList::Entry* entry, std::optional<s32> save_slot = std::nullopt, std::optional<bool> fast_boot = std::nullopt, bool load_backup = false);
|
||||
void setGameListEntryCoverImage(const GameList::Entry* entry);
|
||||
void clearGameListEntryPlayTime(const GameList::Entry* entry);
|
||||
void goToWikiPage(const GameList::Entry* entry);
|
||||
void openScreenshotsFolderForGame(const GameList::Entry* entry);
|
||||
const GameList::Entry& entry, std::optional<s32> save_slot = std::nullopt, std::optional<bool> fast_boot = std::nullopt, bool load_backup = false);
|
||||
void setGameListEntryCoverImage(const GameList::Entry& entry);
|
||||
void clearGameListEntryPlayTime(const GameList::Entry& entry, const time_t entry_played_time);
|
||||
void goToWikiPage(const GameList::Entry& entry);
|
||||
void openSnapshotsFolderForGame(const GameList::Entry& entry);
|
||||
|
||||
std::optional<bool> promptForResumeState(const QString& save_state_path);
|
||||
void loadSaveStateSlot(s32 slot, bool load_backup = false);
|
||||
@@ -288,8 +291,8 @@ private:
|
||||
Ui::MainWindow m_ui;
|
||||
|
||||
GameListWidget* m_game_list_widget = nullptr;
|
||||
DisplayWidget* m_display_widget = nullptr;
|
||||
DisplayContainer* m_display_container = nullptr;
|
||||
DisplaySurface* m_display_surface = nullptr;
|
||||
QWidget* m_display_container = nullptr;
|
||||
|
||||
SettingsWindow* m_settings_window = nullptr;
|
||||
ControllerSettingsWindow* m_controller_settings_window = nullptr;
|
||||
|
||||
@@ -88,12 +88,13 @@ static QTimer* s_settings_save_timer = nullptr;
|
||||
static std::unique_ptr<INISettingsInterface> s_base_settings_interface;
|
||||
static bool s_batch_mode = false;
|
||||
static bool s_nogui_mode = false;
|
||||
static bool s_start_fullscreen_ui = false;
|
||||
static bool s_start_fullscreen_ui_fullscreen = false;
|
||||
static bool s_start_big_picture_mode = false;
|
||||
static bool s_start_fullscreen = false;
|
||||
static bool s_test_config_and_exit = false;
|
||||
static bool s_run_setup_wizard = false;
|
||||
static bool s_cleanup_after_update = false;
|
||||
static bool s_boot_and_debug = false;
|
||||
static std::atomic_int s_vm_locked_with_dialog = 0;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// CPU Thread
|
||||
@@ -495,6 +496,11 @@ void EmuThread::setFullscreen(bool fullscreen, bool allow_render_to_main)
|
||||
return;
|
||||
}
|
||||
|
||||
// HACK: Prevent entering/exiting fullscreen mode when a dialog is shown, so
|
||||
// that we don't destroy the dialog while inside its exec function.
|
||||
if (s_vm_locked_with_dialog > 0)
|
||||
return;
|
||||
|
||||
if (!MTGS::IsOpen() || m_is_fullscreen == fullscreen)
|
||||
return;
|
||||
|
||||
@@ -764,12 +770,12 @@ void EmuThread::enumerateVibrationMotors()
|
||||
onVibrationMotorsEnumerated(qmotors);
|
||||
}
|
||||
|
||||
void EmuThread::connectDisplaySignals(DisplayWidget* widget)
|
||||
void EmuThread::connectDisplaySignals(DisplaySurface* widget)
|
||||
{
|
||||
widget->disconnect(this);
|
||||
|
||||
connect(widget, &DisplayWidget::windowResizedEvent, this, &EmuThread::onDisplayWindowResized);
|
||||
connect(widget, &DisplayWidget::windowRestoredEvent, this, &EmuThread::redrawDisplayWindow);
|
||||
connect(widget, &DisplaySurface::windowResizedEvent, this, &EmuThread::onDisplayWindowResized);
|
||||
connect(widget, &DisplaySurface::windowRestoredEvent, this, &EmuThread::redrawDisplayWindow);
|
||||
}
|
||||
|
||||
void EmuThread::onDisplayWindowResized(int width, int height, float scale)
|
||||
@@ -959,6 +965,9 @@ void Host::OnGameChanged(const std::string& title, const std::string& elf_overri
|
||||
|
||||
void EmuThread::updatePerformanceMetrics(bool force)
|
||||
{
|
||||
if (!g_main_window)
|
||||
return;
|
||||
|
||||
if (VMManager::HasValidVM())
|
||||
{
|
||||
QString gs_stat;
|
||||
@@ -1152,7 +1161,7 @@ void Host::OpenHostFileSelectorAsync(std::string_view title, bool select_directo
|
||||
if (!filters.empty())
|
||||
{
|
||||
filters_str.append(QStringLiteral("All File Types (%1)")
|
||||
.arg(QString::fromStdString(StringUtil::JoinString(filters.begin(), filters.end(), " "))));
|
||||
.arg(QString::fromStdString(StringUtil::JoinString(filters.begin(), filters.end(), " "))));
|
||||
for (const std::string& filter : filters)
|
||||
{
|
||||
filters_str.append(
|
||||
@@ -1713,6 +1722,16 @@ void Host::SetMouseMode(bool relative_mode, bool hide_cursor)
|
||||
emit g_emu_thread->onMouseModeRequested(relative_mode, hide_cursor);
|
||||
}
|
||||
|
||||
void QtHost::LockVMWithDialog()
|
||||
{
|
||||
s_vm_locked_with_dialog++;
|
||||
}
|
||||
|
||||
void QtHost::UnlockVMWithDialog()
|
||||
{
|
||||
s_vm_locked_with_dialog--;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
class QtHostProgressCallback final : public BaseProgressCallback
|
||||
@@ -2168,7 +2187,7 @@ bool QtHost::ParseCommandLineOptions(const QStringList& args, std::shared_ptr<VM
|
||||
else if (CHECK_ARG(QStringLiteral("-fullscreen")))
|
||||
{
|
||||
AutoBoot(autoboot)->fullscreen = true;
|
||||
s_start_fullscreen_ui_fullscreen = true;
|
||||
s_start_fullscreen = true;
|
||||
continue;
|
||||
}
|
||||
else if (CHECK_ARG(QStringLiteral("-nofullscreen")))
|
||||
@@ -2183,7 +2202,7 @@ bool QtHost::ParseCommandLineOptions(const QStringList& args, std::shared_ptr<VM
|
||||
}
|
||||
else if (CHECK_ARG(QStringLiteral("-bigpicture")))
|
||||
{
|
||||
s_start_fullscreen_ui = true;
|
||||
s_start_big_picture_mode = true;
|
||||
continue;
|
||||
}
|
||||
else if (CHECK_ARG(QStringLiteral("-testconfig")))
|
||||
@@ -2244,7 +2263,7 @@ bool QtHost::ParseCommandLineOptions(const QStringList& args, std::shared_ptr<VM
|
||||
|
||||
// if we don't have autoboot, we definitely don't want batch mode (because that'll skip
|
||||
// scanning the game list).
|
||||
if (s_batch_mode && !s_start_fullscreen_ui && !autoboot)
|
||||
if (s_batch_mode && !s_start_big_picture_mode && !autoboot)
|
||||
{
|
||||
QMessageBox::critical(nullptr, QStringLiteral("Error"),
|
||||
s_nogui_mode ? QStringLiteral("Cannot use no-gui mode, because no boot filename was specified.") :
|
||||
@@ -2328,7 +2347,12 @@ int main(int argc, char* argv[])
|
||||
{
|
||||
CrashHandler::Install();
|
||||
|
||||
// Exceptions are disabled, so we can't try/catch this.
|
||||
// Timestamps in some locales showed up wrong on Windows.
|
||||
// Qt already applies the user locale on Unix-like systems.
|
||||
#ifdef _WIN32
|
||||
std::locale::global(std::locale(""));
|
||||
#endif
|
||||
|
||||
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
|
||||
QtHost::RegisterTypes();
|
||||
@@ -2380,7 +2404,7 @@ int main(int argc, char* argv[])
|
||||
|
||||
// When running in batch mode, ensure game list is loaded, but don't scan for any new files.
|
||||
if (!s_batch_mode)
|
||||
g_main_window->refreshGameList(false);
|
||||
g_main_window->refreshGameList(false, false);
|
||||
else
|
||||
GameList::Refresh(false, true);
|
||||
|
||||
@@ -2393,8 +2417,9 @@ int main(int argc, char* argv[])
|
||||
}
|
||||
|
||||
// Initialize big picture mode if requested by command line or settings.
|
||||
if (s_start_fullscreen_ui || Host::GetBaseBoolSettingValue("UI", "StartBigPictureMode", false))
|
||||
g_emu_thread->startFullscreenUI(s_start_fullscreen_ui_fullscreen);
|
||||
// As CLI arguments are baked-in, they're tracked separately from settings which can be changed during runtime.
|
||||
if (s_start_big_picture_mode || Host::GetBaseBoolSettingValue("UI", "StartBigPictureMode", false))
|
||||
g_emu_thread->startFullscreenUI(s_start_fullscreen || Host::GetBaseBoolSettingValue("UI", "StartFullscreen", false));
|
||||
|
||||
if (s_boot_and_debug || DebuggerWindow::shouldShowOnStartup())
|
||||
{
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
class SettingsInterface;
|
||||
|
||||
class DisplayWidget;
|
||||
class DisplaySurface;
|
||||
struct VMBootParameters;
|
||||
|
||||
enum class CDVD_SourceType : uint8_t;
|
||||
@@ -69,7 +69,7 @@ public:
|
||||
|
||||
/// Called back from the GS thread when the display state changes (e.g. fullscreen, render to main).
|
||||
std::optional<WindowInfo> acquireRenderWindow(bool recreate_window);
|
||||
void connectDisplaySignals(DisplayWidget* widget);
|
||||
void connectDisplaySignals(DisplaySurface* widget);
|
||||
void releaseRenderWindow();
|
||||
|
||||
void startBackgroundControllerPollTimer();
|
||||
@@ -290,4 +290,10 @@ namespace QtHost
|
||||
|
||||
/// Compare strings in the locale of the current UI language
|
||||
int LocaleSensitiveCompare(QStringView lhs, QStringView rhs);
|
||||
|
||||
/// Determines whether or not requests to enter/exit fullscreen mode should
|
||||
/// be ignored. This is a hack so that we don't destroy a dialog box while
|
||||
/// inside its exec function, which would cause a crash.
|
||||
void LockVMWithDialog();
|
||||
void UnlockVMWithDialog();
|
||||
} // namespace QtHost
|
||||
|
||||
@@ -8,10 +8,9 @@
|
||||
#include <QtCore/QtGlobal>
|
||||
#include <QtCore/QMetaObject>
|
||||
#include <QtGui/QAction>
|
||||
#include <QtGui/QGuiApplication>
|
||||
#include <QtGui/QDesktopServices>
|
||||
#include <QtGui/QKeyEvent>
|
||||
#include <QtGui/QScreen>
|
||||
#include <QtGui/QPainter>
|
||||
#include <QtWidgets/QComboBox>
|
||||
#include <QtWidgets/QDialog>
|
||||
#include <QtWidgets/QHeaderView>
|
||||
@@ -40,8 +39,6 @@
|
||||
#if defined(_WIN32)
|
||||
#include "common/RedtapeWindows.h"
|
||||
#include <Shlobj.h>
|
||||
#elif !defined(APPLE)
|
||||
#include <qpa/qplatformnativeinterface.h>
|
||||
#endif
|
||||
|
||||
namespace QtUtils
|
||||
@@ -139,6 +136,126 @@ namespace QtUtils
|
||||
ResizeColumnsForView(view, widths);
|
||||
}
|
||||
|
||||
void resizeAndScalePixmap(QPixmap* pm, const int expected_width, const int expected_height, const qreal dpr, const ScalingMode scaling_mode, const float opacity)
|
||||
{
|
||||
if (!pm || pm->isNull() || pm->width() <= 0 || pm->height() <= 0)
|
||||
return;
|
||||
|
||||
const int dpr_expected_width = qRound(expected_width * dpr);
|
||||
const int dpr_expected_height = qRound(expected_height * dpr);
|
||||
|
||||
if (pm->width() == dpr_expected_width &&
|
||||
pm->height() == dpr_expected_height &&
|
||||
pm->devicePixelRatio() == dpr &&
|
||||
opacity == 100.0f)
|
||||
{
|
||||
switch (scaling_mode)
|
||||
{
|
||||
case ScalingMode::Fit:
|
||||
case ScalingMode::Stretch:
|
||||
case ScalingMode::Center:
|
||||
return;
|
||||
|
||||
case ScalingMode::Fill:
|
||||
case ScalingMode::Tile:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
QPixmap final_pixmap(dpr_expected_width, dpr_expected_height);
|
||||
final_pixmap.setDevicePixelRatio(dpr);
|
||||
final_pixmap.fill(Qt::transparent);
|
||||
|
||||
QPainter painter;
|
||||
painter.begin(&final_pixmap);
|
||||
painter.setRenderHint(QPainter::Antialiasing, true);
|
||||
painter.setRenderHint(QPainter::SmoothPixmapTransform, true);
|
||||
painter.setOpacity(opacity / 100.0f);
|
||||
painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
|
||||
|
||||
const QRectF srcRect(0, 0, pm->width(), pm->height());
|
||||
const QRectF painterRect(0, 0, expected_width, expected_height);
|
||||
|
||||
switch (scaling_mode)
|
||||
{
|
||||
case ScalingMode::Fit:
|
||||
case ScalingMode::Fill:
|
||||
{
|
||||
auto const aspect_mode = (scaling_mode == ScalingMode::Fit) ?
|
||||
Qt::KeepAspectRatio :
|
||||
Qt::KeepAspectRatioByExpanding;
|
||||
|
||||
QSizeF scaledSize(pm->width(), pm->height());
|
||||
scaledSize.scale(dpr_expected_width, dpr_expected_height, aspect_mode);
|
||||
|
||||
*pm = pm->scaled(
|
||||
qRound(scaledSize.width()),
|
||||
qRound(scaledSize.height()),
|
||||
Qt::IgnoreAspectRatio,
|
||||
Qt::SmoothTransformation
|
||||
);
|
||||
|
||||
const QRectF scaledSrcRect(0, 0, pm->width(), pm->height());
|
||||
|
||||
QSizeF logicalSize = pm->size() / dpr;
|
||||
QRectF destRect(QPointF(0, 0), logicalSize);
|
||||
|
||||
destRect.moveCenter(painterRect.center());
|
||||
|
||||
painter.drawPixmap(destRect, *pm, scaledSrcRect);
|
||||
break;
|
||||
}
|
||||
case ScalingMode::Stretch:
|
||||
{
|
||||
*pm = pm->scaled(
|
||||
dpr_expected_width,
|
||||
dpr_expected_height,
|
||||
Qt::IgnoreAspectRatio,
|
||||
Qt::SmoothTransformation
|
||||
);
|
||||
|
||||
const QRectF scaledSrcRect(0, 0, pm->width(), pm->height());
|
||||
|
||||
painter.drawPixmap(painterRect, *pm, scaledSrcRect);
|
||||
break;
|
||||
}
|
||||
case ScalingMode::Center:
|
||||
{
|
||||
const qreal pmWidth = pm->width() / dpr;
|
||||
const qreal pmHeight = pm->height() / dpr;
|
||||
|
||||
QRectF destRect(0, 0, pmWidth, pmHeight);
|
||||
|
||||
destRect.moveCenter(painterRect.center());
|
||||
|
||||
painter.drawPixmap(destRect, *pm, srcRect);
|
||||
break;
|
||||
}
|
||||
case ScalingMode::Tile:
|
||||
{
|
||||
const qreal tileWidth = pm->width() / dpr;
|
||||
const qreal tileHeight = pm->height() / dpr;
|
||||
|
||||
if (tileWidth <= 0 || tileHeight <= 0)
|
||||
break;
|
||||
|
||||
QPixmap tileSource = pm->scaled(tileWidth, tileHeight, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
tileSource.setDevicePixelRatio(dpr);
|
||||
|
||||
QBrush tileBrush(tileSource);
|
||||
tileBrush.setTextureImage(tileSource.toImage());
|
||||
|
||||
painter.fillRect(painterRect, tileBrush);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
painter.end();
|
||||
*pm = std::move(final_pixmap);
|
||||
}
|
||||
|
||||
void ShowInFileExplorer(QWidget* parent, const QFileInfo& file)
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
@@ -254,68 +371,6 @@ namespace QtUtils
|
||||
widget->resize(width, height);
|
||||
}
|
||||
|
||||
std::optional<WindowInfo> GetWindowInfoForWidget(QWidget* widget)
|
||||
{
|
||||
WindowInfo wi;
|
||||
|
||||
// Windows and Apple are easy here since there's no display connection.
|
||||
#if defined(_WIN32)
|
||||
wi.type = WindowInfo::Type::Win32;
|
||||
wi.window_handle = reinterpret_cast<void*>(widget->winId());
|
||||
#elif defined(__APPLE__)
|
||||
wi.type = WindowInfo::Type::MacOS;
|
||||
wi.window_handle = reinterpret_cast<void*>(widget->winId());
|
||||
#else
|
||||
QPlatformNativeInterface* pni = QGuiApplication::platformNativeInterface();
|
||||
const QString platform_name = QGuiApplication::platformName();
|
||||
if (platform_name == QStringLiteral("xcb"))
|
||||
{
|
||||
// Can't get a handle for an unmapped window in X, it doesn't like it.
|
||||
if (!widget->isVisible())
|
||||
{
|
||||
Console.WriteLn("Returning null window info for widget because it is not visible.");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
wi.type = WindowInfo::Type::X11;
|
||||
wi.display_connection = pni->nativeResourceForWindow("display", widget->windowHandle());
|
||||
wi.window_handle = reinterpret_cast<void*>(widget->winId());
|
||||
}
|
||||
else if (platform_name == QStringLiteral("wayland"))
|
||||
{
|
||||
wi.type = WindowInfo::Type::Wayland;
|
||||
wi.display_connection = pni->nativeResourceForWindow("display", widget->windowHandle());
|
||||
wi.window_handle = pni->nativeResourceForWindow("surface", widget->windowHandle());
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLn("Unknown PNI platform '%s'.", platform_name.toUtf8().constData());
|
||||
return std::nullopt;
|
||||
}
|
||||
#endif
|
||||
|
||||
const qreal dpr = widget->devicePixelRatioF();
|
||||
wi.surface_width = static_cast<u32>(static_cast<qreal>(widget->width()) * dpr);
|
||||
wi.surface_height = static_cast<u32>(static_cast<qreal>(widget->height()) * dpr);
|
||||
wi.surface_scale = static_cast<float>(dpr);
|
||||
|
||||
// Query refresh rate, we need it for sync.
|
||||
std::optional<float> surface_refresh_rate = WindowInfo::QueryRefreshRateForWindow(wi);
|
||||
if (!surface_refresh_rate.has_value())
|
||||
{
|
||||
// Fallback to using the screen, getting the rate for Wayland is an utter mess otherwise.
|
||||
const QScreen* widget_screen = widget->screen();
|
||||
if (!widget_screen)
|
||||
widget_screen = QGuiApplication::primaryScreen();
|
||||
surface_refresh_rate = widget_screen ? static_cast<float>(widget_screen->refreshRate()) : 0.0f;
|
||||
}
|
||||
|
||||
wi.surface_refresh_rate = surface_refresh_rate.value();
|
||||
INFO_LOG("Surface refresh rate: {} hz", wi.surface_refresh_rate);
|
||||
|
||||
return wi;
|
||||
}
|
||||
|
||||
QString AbstractItemModelToCSV(QAbstractItemModel* model, int role, bool useQuotes)
|
||||
{
|
||||
QString csv;
|
||||
|
||||
@@ -9,11 +9,20 @@
|
||||
#include <QtCore/QMetaType>
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QAbstractItemModel>
|
||||
#include <QtGui/QGuiApplication>
|
||||
#include <QtGui/QWindow>
|
||||
#if !defined(_WIN32) and !defined(__APPLE__)
|
||||
#include <qpa/qplatformnativeinterface.h>
|
||||
#endif
|
||||
#include <QtGui/QScreen>
|
||||
#include <functional>
|
||||
#include <initializer_list>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <optional>
|
||||
|
||||
#include "common/Console.h"
|
||||
|
||||
class ByteStream;
|
||||
|
||||
class QAction;
|
||||
@@ -49,6 +58,20 @@ namespace QtUtils
|
||||
void ResizeColumnsForTableView(QTableView* view, const std::initializer_list<int>& widths);
|
||||
void ResizeColumnsForTreeView(QTreeView* view, const std::initializer_list<int>& widths);
|
||||
|
||||
enum struct ScalingMode
|
||||
{
|
||||
Fit,
|
||||
Fill,
|
||||
Stretch,
|
||||
Center,
|
||||
Tile,
|
||||
|
||||
MaxCount
|
||||
};
|
||||
|
||||
/// Resize and scale a given Pixmap (and optionally adjust opacity)
|
||||
void resizeAndScalePixmap(QPixmap* pm, const int expected_width, const int expected_height, const qreal dpr, const ScalingMode scaling_mode, const float opacity);
|
||||
|
||||
/// Returns a key id for a key event, including any modifiers that we need (e.g. Keypad).
|
||||
/// NOTE: Defined in QtKeyCodes.cpp, not QtUtils.cpp.
|
||||
u32 KeyEventToCode(const QKeyEvent* ev);
|
||||
@@ -83,8 +106,82 @@ namespace QtUtils
|
||||
/// Adjusts the fixed size for a window if it's not resizeable.
|
||||
void ResizePotentiallyFixedSizeWindow(QWidget* widget, int width, int height);
|
||||
|
||||
/// Returns the common window info structure for a Qt widget.
|
||||
std::optional<WindowInfo> GetWindowInfoForWidget(QWidget* widget);
|
||||
/// Returns the common window info structure for a Qt Window/Widget.
|
||||
template <class T>
|
||||
requires std::is_base_of_v<QWidget, T> || std::is_base_of_v<QWindow, T>
|
||||
std::optional<WindowInfo> GetWindowInfoForWindow(T* window)
|
||||
{
|
||||
WindowInfo wi;
|
||||
|
||||
// Windows and Apple are easy here since there's no display connection.
|
||||
#if defined(_WIN32)
|
||||
wi.type = WindowInfo::Type::Win32;
|
||||
wi.window_handle = reinterpret_cast<void*>(window->winId());
|
||||
#elif defined(__APPLE__)
|
||||
wi.type = WindowInfo::Type::MacOS;
|
||||
wi.window_handle = reinterpret_cast<void*>(window->winId());
|
||||
#else
|
||||
QPlatformNativeInterface* pni = QGuiApplication::platformNativeInterface();
|
||||
const QString platform_name = QGuiApplication::platformName();
|
||||
|
||||
QWindow* windowHandle;
|
||||
if constexpr (std::is_base_of_v<QWidget, T>)
|
||||
windowHandle = window->windowHandle();
|
||||
else
|
||||
windowHandle = window;
|
||||
|
||||
if (platform_name == QStringLiteral("xcb"))
|
||||
{
|
||||
// Can't get a handle for an unmapped window in X, it doesn't like it.
|
||||
if (!window->isVisible())
|
||||
{
|
||||
Console.WriteLn("Returning null window info for widget because it is not visible.");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
wi.type = WindowInfo::Type::X11;
|
||||
wi.display_connection = pni->nativeResourceForWindow("display", windowHandle);
|
||||
wi.window_handle = reinterpret_cast<void*>(window->winId());
|
||||
}
|
||||
else if (platform_name == QStringLiteral("wayland"))
|
||||
{
|
||||
wi.type = WindowInfo::Type::Wayland;
|
||||
wi.display_connection = pni->nativeResourceForWindow("display", windowHandle);
|
||||
wi.window_handle = pni->nativeResourceForWindow("surface", windowHandle);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLn("Unknown PNI platform '%s'.", platform_name.toUtf8().constData());
|
||||
return std::nullopt;
|
||||
}
|
||||
#endif
|
||||
|
||||
qreal dpr;
|
||||
if constexpr (std::is_base_of_v<QWidget, T>)
|
||||
dpr = window->devicePixelRatioF();
|
||||
else
|
||||
dpr = window->devicePixelRatio();
|
||||
|
||||
wi.surface_width = static_cast<u32>(std::max(static_cast<int>(std::round(static_cast<qreal>(window->width()) * dpr)), 1));
|
||||
wi.surface_height = static_cast<u32>(std::max(static_cast<int>(std::round(static_cast<qreal>(window->height()) * dpr)), 1));
|
||||
wi.surface_scale = static_cast<float>(dpr);
|
||||
|
||||
// Query refresh rate, we need it for sync.
|
||||
std::optional<float> surface_refresh_rate = WindowInfo::QueryRefreshRateForWindow(wi);
|
||||
if (!surface_refresh_rate.has_value())
|
||||
{
|
||||
// Fallback to using the screen, getting the rate for Wayland is an utter mess otherwise.
|
||||
const QScreen* widget_screen = window->screen();
|
||||
if (!widget_screen)
|
||||
widget_screen = QGuiApplication::primaryScreen();
|
||||
surface_refresh_rate = widget_screen ? static_cast<float>(widget_screen->refreshRate()) : 0.0f;
|
||||
}
|
||||
|
||||
wi.surface_refresh_rate = surface_refresh_rate.value();
|
||||
INFO_LOG("Surface refresh rate: {} hz", wi.surface_refresh_rate);
|
||||
|
||||
return wi;
|
||||
}
|
||||
|
||||
/// Converts a value to a QString of said value with a proper fixed width
|
||||
template <typename T>
|
||||
|
||||
@@ -127,7 +127,25 @@ void AchievementLoginDialog::processLoginResult(bool result, const QString& mess
|
||||
}
|
||||
}
|
||||
|
||||
done(0);
|
||||
// Show success messagebox
|
||||
const std::string username = Host::GetBaseStringSettingValue("Achievements", "Username");
|
||||
QMessageBox::information(
|
||||
this, tr("Login Successful"),
|
||||
tr("Successfully logged in to RetroAchievements as %1.").arg(QString::fromStdString(username)));
|
||||
|
||||
m_ui.status->setText(tr("Successfully logged in as %1.").arg(QString::fromStdString(username)));
|
||||
m_ui.status->setStyleSheet("color: green; font-weight: bold;");
|
||||
|
||||
disconnect(m_ui.buttonBox, &QDialogButtonBox::accepted, this, &AchievementLoginDialog::loginClicked);
|
||||
|
||||
m_login->setVisible(false);
|
||||
QPushButton* dismissButton = m_ui.buttonBox->addButton(tr("&Dismiss"), QDialogButtonBox::AcceptRole);
|
||||
dismissButton->setDefault(true);
|
||||
dismissButton->setFocus();
|
||||
|
||||
connect(dismissButton, &QPushButton::clicked, this, [this]() { done(0); });
|
||||
|
||||
enableUI(false);
|
||||
}
|
||||
|
||||
void AchievementLoginDialog::connectUi()
|
||||
|
||||
@@ -429,6 +429,9 @@ Login token generated at:</string>
|
||||
<property name="buddy">
|
||||
<cstring>viewProfile</cstring>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::TextBrowserInteraction</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
@@ -469,6 +472,9 @@ Login token generated at:</string>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop</set>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::TextBrowserInteraction</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
|
||||
@@ -133,6 +133,9 @@
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignmentFlag::AlignCenter</set>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::TextBrowserInteraction</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
|
||||
@@ -40,6 +40,9 @@
|
||||
<property name="buddy">
|
||||
<cstring>searchDirectory</cstring>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::TextBrowserInteraction</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
|
||||
@@ -64,6 +64,9 @@
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::TextBrowserInteraction</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
@@ -90,6 +93,9 @@
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::TextBrowserInteraction</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
@@ -109,6 +115,9 @@
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::TextBrowserInteraction</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
@@ -142,6 +151,9 @@
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::TextBrowserInteraction</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
@@ -200,9 +212,15 @@
|
||||
<property name="text">
|
||||
<string>PCSX2 allows you to use your mouse to simulate analog stick movement.</string>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::TextBrowserInteraction</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::TextBrowserInteraction</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
@@ -240,6 +258,9 @@
|
||||
<property name="text">
|
||||
<string>The XInput source provides support for Xbox 360 / Xbox One / Xbox Series controllers, and third party controllers which implement the XInput protocol.</string>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::TextBrowserInteraction</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
|
||||
@@ -33,13 +33,19 @@
|
||||
<widget class="QListWidget" name="bindList"/>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<widget class="QLabel" name="bindListLabel">
|
||||
<property name="text">
|
||||
<string>Select the buttons which you want to trigger with this macro. All buttons are activated concurrently.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::TextBrowserInteraction</set>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>bindList</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
@@ -52,13 +58,19 @@
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_3">
|
||||
<widget class="QLabel" name="pressureLabel">
|
||||
<property name="text">
|
||||
<string>For buttons which are pressure sensitive, this slider controls how much force will be simulated when the macro is active.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::TextBrowserInteraction</set>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>pressure</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
@@ -116,6 +128,9 @@
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::TextBrowserInteraction</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
@@ -137,10 +152,13 @@
|
||||
<item row="2" column="0">
|
||||
<layout class="QHBoxLayout" name="deadzoneLayout" stretch="0,1,0">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_4">
|
||||
<widget class="QLabel" name="deadzoneLabel">
|
||||
<property name="text">
|
||||
<string>Deadzone:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>deadzone</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
@@ -190,6 +208,9 @@
|
||||
<property name="text">
|
||||
<string>Macro will toggle every N frames.</string>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::TextBrowserInteraction</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
|
||||
@@ -28,6 +28,9 @@
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::TextBrowserInteraction</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
|
||||
@@ -71,7 +71,21 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<item row="4" column="0">
|
||||
<widget class="QCheckBox" name="saveDrawStats">
|
||||
<property name="text">
|
||||
<string>Save Draw Stats</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QCheckBox" name="saveFrameStats">
|
||||
<property name="text">
|
||||
<string>Save Frame Stats</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QCheckBox" name="saveTransferImages">
|
||||
<property name="text">
|
||||
<string>Save Transfer Image Data</string>
|
||||
|
||||
@@ -110,6 +110,8 @@ DebugSettingsWidget::DebugSettingsWidget(SettingsWindow* settings_dialog, QWidge
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_gs.saveAlpha, "EmuCore/GS", "SaveAlpha", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_gs.saveInfo, "EmuCore/GS", "SaveInfo", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_gs.saveTransferImages, "EmuCore/GS", "SaveTransferImages", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_gs.saveDrawStats, "EmuCore/GS", "SaveDrawStats", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_gs.saveFrameStats, "EmuCore/GS", "SaveFrameStats", false);
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_gs.saveDrawStart, "EmuCore/GS", "SaveDrawStart", 0);
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_gs.saveDrawCount, "EmuCore/GS", "SaveDrawCount", 5000);
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_gs.saveFrameStart, "EmuCore/GS", "SaveFrameStart", 0);
|
||||
@@ -215,6 +217,8 @@ void DebugSettingsWidget::onDrawDumpingChanged()
|
||||
m_gs.saveAlpha->setEnabled(enabled);
|
||||
m_gs.saveInfo->setEnabled(enabled);
|
||||
m_gs.saveTransferImages->setEnabled(enabled);
|
||||
m_gs.saveDrawStats->setEnabled(enabled);
|
||||
m_gs.saveFrameStats->setEnabled(enabled);
|
||||
m_gs.saveDrawStart->setEnabled(enabled);
|
||||
m_gs.saveDrawCount->setEnabled(enabled);
|
||||
m_gs.saveFrameStart->setEnabled(enabled);
|
||||
|
||||
@@ -164,7 +164,6 @@ EmulationSettingsWidget::EmulationSettingsWidget(SettingsWindow* settings_dialog
|
||||
dialog()->registerWidgetHelp(m_ui.rtcDateTime, tr("Real-Time Clock"), tr("Current date and time"),
|
||||
tr("Real-time clock (RTC) used by the virtual PlayStation 2.<br>"
|
||||
"This time is only applied upon booting the PS2; changing it while in-game will have no effect.<br>"
|
||||
"NOTE: This assumes you have your PS2 set to the default timezone of GMT+0 and default DST of Summer Time.<br>"
|
||||
"Some games require an RTC date/time set after their release date."));
|
||||
dialog()->registerWidgetHelp(m_ui.rtcUseSystemLocaleFormat, tr("Use System Locale Format"), tr("User Preference"),
|
||||
tr("Uses the operating system's date/time format rather than \"yyyy-MM-dd HH:mm:ss\". May exclude seconds."));
|
||||
|
||||
@@ -19,14 +19,13 @@ FolderSettingsWidget::FolderSettingsWidget(SettingsWindow* settings_dialog, QWid
|
||||
SettingWidgetBinder::BindWidgetToFolderSetting(sif, m_ui.cheats, m_ui.cheatsBrowse, m_ui.cheatsOpen, m_ui.cheatsReset, "Folders", "Cheats", Path::Combine(EmuFolders::DataRoot, "cheats"));
|
||||
SettingWidgetBinder::BindWidgetToFolderSetting(sif, m_ui.covers, m_ui.coversBrowse, m_ui.coversOpen, m_ui.coversReset, "Folders", "Covers", Path::Combine(EmuFolders::DataRoot, "covers"));
|
||||
SettingWidgetBinder::BindWidgetToFolderSetting(sif, m_ui.snapshots, m_ui.snapshotsBrowse, m_ui.snapshotsOpen, m_ui.snapshotsReset, "Folders", "Snapshots", Path::Combine(EmuFolders::DataRoot, "snaps"));
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.organizeScreenshotsByGame, "EmuCore/GS", "OrganizeScreenshotsByGame", false);
|
||||
connect(m_ui.organizeScreenshotsByGame, &QCheckBox::checkStateChanged, this, [](int state) {
|
||||
GSConfig.OrganizeScreenshotsByGame = (state == Qt::Checked);
|
||||
});
|
||||
SettingWidgetBinder::BindWidgetToFolderSetting(sif, m_ui.saveStates, m_ui.saveStatesBrowse, m_ui.saveStatesOpen, m_ui.saveStatesReset, "Folders", "SaveStates", Path::Combine(EmuFolders::DataRoot, "sstates"));
|
||||
SettingWidgetBinder::BindWidgetToFolderSetting(sif, m_ui.videoDumpingDirectory, m_ui.videoDumpingDirectoryBrowse, m_ui.videoDumpingDirectoryOpen, m_ui.videoDumpingDirectoryReset, "Folders", "Videos", Path::Combine(EmuFolders::DataRoot, "videos"));
|
||||
dialog()->registerWidgetHelp(m_ui.organizeScreenshotsByGame, tr("Organize Screenshots by Game"), tr("Unchecked"),
|
||||
tr("When enabled, screenshots will be saved in a folder with the game's name, instead of all being saved in the Snapshots folder"));
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.organizeSnapshotsByGame, "EmuCore/GS", "OrganizeScreenshotsByGame", false);
|
||||
SettingWidgetBinder::BindWidgetToFolderSetting(sif, m_ui.saveStates, m_ui.saveStatesBrowse, m_ui.saveStatesOpen, m_ui.saveStatesReset,
|
||||
"Folders", "SaveStates", Path::Combine(EmuFolders::DataRoot, "sstates"));
|
||||
SettingWidgetBinder::BindWidgetToFolderSetting(sif, m_ui.videoDumpingDirectory, m_ui.videoDumpingDirectoryBrowse, m_ui.videoDumpingDirectoryOpen, m_ui.videoDumpingDirectoryReset,
|
||||
"Folders", "Videos", Path::Combine(EmuFolders::DataRoot, "videos"));
|
||||
dialog()->registerWidgetHelp(m_ui.organizeSnapshotsByGame, tr("Organize Snapshots by Game"), tr("Unchecked"),
|
||||
tr("Saves snapshots to per-game subfolders instead of a shared folder."));
|
||||
}
|
||||
|
||||
FolderSettingsWidget::~FolderSettingsWidget() = default;
|
||||
|
||||
@@ -49,6 +49,9 @@
|
||||
<property name="buddy">
|
||||
<cstring>cache</cstring>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::TextBrowserInteraction</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
@@ -92,6 +95,9 @@
|
||||
<property name="buddy">
|
||||
<cstring>cheats</cstring>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::TextBrowserInteraction</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
@@ -130,7 +136,10 @@
|
||||
<item row="0" column="0" colspan="4">
|
||||
<widget class="QLabel" name="snaphotsLabel">
|
||||
<property name="text">
|
||||
<string>Used for screenshots and saving GS dumps.</string>
|
||||
<string>Used for saving screenshots and GS dumps.</string>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::TextBrowserInteraction</set>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>snapshots</cstring>
|
||||
@@ -138,7 +147,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="4">
|
||||
<widget class="QCheckBox" name="organizeScreenshotsByGame">
|
||||
<widget class="QCheckBox" name="organizeSnapshotsByGame">
|
||||
<property name="text">
|
||||
<string>Save Snapshots in Game-Specific Folders</string>
|
||||
</property>
|
||||
@@ -161,6 +170,9 @@
|
||||
<property name="buddy">
|
||||
<cstring>saveStates</cstring>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::TextBrowserInteraction</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
@@ -228,6 +240,9 @@
|
||||
<property name="buddy">
|
||||
<cstring>covers</cstring>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::TextBrowserInteraction</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
@@ -271,6 +286,9 @@
|
||||
<property name="buddy">
|
||||
<cstring>videoDumpingDirectory</cstring>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::TextBrowserInteraction</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
@@ -304,7 +322,7 @@
|
||||
<tabstop>snapshotsBrowse</tabstop>
|
||||
<tabstop>snapshotsOpen</tabstop>
|
||||
<tabstop>snapshotsReset</tabstop>
|
||||
<tabstop>organizeScreenshotsByGame</tabstop>
|
||||
<tabstop>organizeSnapshotsByGame</tabstop>
|
||||
<tabstop>saveStates</tabstop>
|
||||
<tabstop>saveStatesBrowse</tabstop>
|
||||
<tabstop>saveStatesOpen</tabstop>
|
||||
|
||||
@@ -63,7 +63,7 @@ bool GameListSettingsWidget::addExcludedPath(const std::string& path)
|
||||
|
||||
Host::CommitBaseSettingChanges();
|
||||
m_ui.excludedPaths->addItem(QString::fromStdString(path));
|
||||
g_main_window->refreshGameList(false);
|
||||
g_main_window->refreshGameList(false, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@ void GameListSettingsWidget::addSearchDirectory(const QString& path, bool recurs
|
||||
Host::AddBaseValueToStringList("GameList", recursive ? "RecursivePaths" : "Paths", spath.c_str());
|
||||
Host::CommitBaseSettingChanges();
|
||||
refreshDirectoryList();
|
||||
g_main_window->refreshGameList(false);
|
||||
g_main_window->refreshGameList(false, true);
|
||||
}
|
||||
|
||||
void GameListSettingsWidget::removeSearchDirectory(const QString& path)
|
||||
@@ -166,7 +166,7 @@ void GameListSettingsWidget::removeSearchDirectory(const QString& path)
|
||||
|
||||
Host::CommitBaseSettingChanges();
|
||||
refreshDirectoryList();
|
||||
g_main_window->refreshGameList(false);
|
||||
g_main_window->refreshGameList(false, true);
|
||||
}
|
||||
|
||||
void GameListSettingsWidget::onDirectoryListContextMenuRequested(const QPoint& point)
|
||||
@@ -261,7 +261,7 @@ void GameListSettingsWidget::onRemoveExcludedPathButtonClicked()
|
||||
|
||||
delete item;
|
||||
|
||||
g_main_window->refreshGameList(false);
|
||||
g_main_window->refreshGameList(false, true);
|
||||
}
|
||||
|
||||
void GameListSettingsWidget::onExcludedPathsSelectionChanged()
|
||||
@@ -271,10 +271,10 @@ void GameListSettingsWidget::onExcludedPathsSelectionChanged()
|
||||
|
||||
void GameListSettingsWidget::onRescanAllGamesClicked()
|
||||
{
|
||||
g_main_window->refreshGameList(true);
|
||||
g_main_window->refreshGameList(true, true);
|
||||
}
|
||||
|
||||
void GameListSettingsWidget::onScanForNewGamesClicked()
|
||||
{
|
||||
g_main_window->refreshGameList(false);
|
||||
g_main_window->refreshGameList(false, true);
|
||||
}
|
||||
|
||||
@@ -39,6 +39,9 @@
|
||||
<property name="buddy">
|
||||
<cstring>searchDirectoryList</cstring>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::TextBrowserInteraction</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
@@ -118,6 +121,9 @@
|
||||
<property name="buddy">
|
||||
<cstring>excludedPaths</cstring>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::TextBrowserInteraction</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
|
||||
@@ -45,7 +45,8 @@ GameSummaryWidget::GameSummaryWidget(const GameList::Entry* entry, SettingsWindo
|
||||
connect(m_ui.inputProfile, &QComboBox::currentIndexChanged, this, &GameSummaryWidget::onInputProfileChanged);
|
||||
connect(m_ui.verify, &QAbstractButton::clicked, this, &GameSummaryWidget::onVerifyClicked);
|
||||
connect(m_ui.searchHash, &QAbstractButton::clicked, this, &GameSummaryWidget::onSearchHashClicked);
|
||||
connect(m_ui.checkWiki, &QAbstractButton::clicked, this, [this, entry]() { onCheckWikiClicked(entry); });
|
||||
connect(m_ui.checkWiki, &QAbstractButton::clicked, this,
|
||||
[this, serial = entry->serial]() { onCheckWikiClicked(serial); });
|
||||
|
||||
bool has_custom_title = false, has_custom_region = false;
|
||||
GameList::CheckCustomAttributesForPath(m_entry_path, has_custom_title, has_custom_region);
|
||||
@@ -280,7 +281,8 @@ void GameSummaryWidget::onVerifyClicked()
|
||||
Error error;
|
||||
if (!hasher.Open(m_entry_path, &error))
|
||||
{
|
||||
setVerifyResult(QString::fromStdString(error.GetDescription()));
|
||||
QString message(QString::fromStdString(error.GetDescription()));
|
||||
QMessageBox::critical(QtUtils::GetRootWidget(this), tr("Error"), message);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -365,9 +367,9 @@ void GameSummaryWidget::onSearchHashClicked()
|
||||
QtUtils::OpenURL(this, fmt::format("http://redump.org/discs/quicksearch/{}", m_redump_search_keyword).c_str());
|
||||
}
|
||||
|
||||
void GameSummaryWidget::onCheckWikiClicked(const GameList::Entry* entry)
|
||||
void GameSummaryWidget::onCheckWikiClicked(const std::string& serial)
|
||||
{
|
||||
QtUtils::OpenURL(this, fmt::format("https://wiki.pcsx2.net/{}", entry->serial).c_str());
|
||||
QtUtils::OpenURL(this, fmt::format("https://wiki.pcsx2.net/{}", serial).c_str());
|
||||
}
|
||||
|
||||
void GameSummaryWidget::setVerifyResult(QString error)
|
||||
|
||||
@@ -26,7 +26,7 @@ private Q_SLOTS:
|
||||
void onDiscPathBrowseClicked();
|
||||
void onVerifyClicked();
|
||||
void onSearchHashClicked();
|
||||
void onCheckWikiClicked(const GameList::Entry* entry);
|
||||
void onCheckWikiClicked(const std::string& serial);
|
||||
|
||||
private:
|
||||
void populateInputProfiles();
|
||||
|
||||
@@ -970,7 +970,6 @@ void GraphicsSettingsWidget::onTextureReplacementChanged()
|
||||
m_texture.precacheTextureReplacements->setEnabled(enabled);
|
||||
}
|
||||
|
||||
|
||||
void GraphicsSettingsWidget::onCaptureContainerChanged()
|
||||
{
|
||||
const std::string container(
|
||||
|
||||
@@ -103,6 +103,9 @@
|
||||
<property name="buddy">
|
||||
<cstring>texturesDirectory</cstring>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::TextBrowserInteraction</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
|
||||
@@ -136,6 +136,16 @@
|
||||
<string>Aggressive</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Normal (Maintain Upscale)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Aggressive (Maintain Upscale)</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
|
||||
@@ -6,13 +6,11 @@
|
||||
#include "Common.h"
|
||||
#include "Host.h"
|
||||
#include "MainWindow.h"
|
||||
#include "QtUtils.h"
|
||||
#include "SettingWidgetBinder.h"
|
||||
#include "SettingsWindow.h"
|
||||
#include "QtHost.h"
|
||||
|
||||
static const char* IMAGE_FILE_FILTER = QT_TRANSLATE_NOOP(GameListWidget,
|
||||
"Supported Image Types (*.bmp *.gif *.jpg *.jpeg *.png *.webp)");
|
||||
|
||||
const char* InterfaceSettingsWidget::THEME_NAMES[] = {
|
||||
QT_TRANSLATE_NOOP("InterfaceSettingsWidget", "Native"),
|
||||
//: Ignore what Crowdin says in this string about "[Light]/[Dark]" being untouchable here, these are not variables in this case and must be translated.
|
||||
@@ -75,6 +73,17 @@ const char* InterfaceSettingsWidget::THEME_VALUES[] = {
|
||||
"Custom",
|
||||
nullptr};
|
||||
|
||||
const char* InterfaceSettingsWidget::BACKGROUND_SCALE_NAMES[] = {
|
||||
"fit",
|
||||
"fill",
|
||||
"stretch",
|
||||
"center",
|
||||
"tile",
|
||||
nullptr};
|
||||
|
||||
const char* InterfaceSettingsWidget::IMAGE_FILE_FILTER = QT_TRANSLATE_NOOP("InterfaceSettingsWidget",
|
||||
"Supported Image Types (*.bmp *.gif *.jpg *.jpeg *.png *.webp)");
|
||||
|
||||
InterfaceSettingsWidget::InterfaceSettingsWidget(SettingsWindow* settings_dialog, QWidget* parent)
|
||||
: SettingsWidget(settings_dialog, parent)
|
||||
{
|
||||
@@ -88,13 +97,27 @@ InterfaceSettingsWidget::InterfaceSettingsWidget(SettingsWindow* settings_dialog
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.pauseOnControllerDisconnection, "UI", "PauseOnControllerDisconnection", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.discordPresence, "EmuCore", "EnableDiscordPresence", false);
|
||||
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.mouseLock, "EmuCore", "EnableMouseLock", false);
|
||||
connect(m_ui.mouseLock, &QCheckBox::checkStateChanged, [](Qt::CheckState state) {
|
||||
if (state == Qt::Checked)
|
||||
Common::AttachMousePositionCb([](int x, int y) { g_main_window->checkMousePosition(x, y); });
|
||||
else
|
||||
Common::DetachMousePositionCb();
|
||||
});
|
||||
#ifdef __linux__ // Mouse locking is only supported on X11
|
||||
const bool mouse_lock_supported = QGuiApplication::platformName().toLower() == "xcb";
|
||||
#else
|
||||
const bool mouse_lock_supported = true;
|
||||
#endif
|
||||
|
||||
if(mouse_lock_supported)
|
||||
{
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.mouseLock, "EmuCore", "EnableMouseLock", false);
|
||||
connect(m_ui.mouseLock, &QCheckBox::checkStateChanged, [](Qt::CheckState state) {
|
||||
if (state == Qt::Checked)
|
||||
Common::AttachMousePositionCb([](int x, int y) { g_main_window->checkMousePosition(x, y); });
|
||||
else
|
||||
Common::DetachMousePositionCb();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ui.mouseLock->setEnabled(false);
|
||||
}
|
||||
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.startFullscreen, "UI", "StartFullscreen", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.doubleClickTogglesFullscreen, "UI", "DoubleClickTogglesFullscreen",
|
||||
true);
|
||||
@@ -109,12 +132,12 @@ InterfaceSettingsWidget::InterfaceSettingsWidget(SettingsWindow* settings_dialog
|
||||
QtHost::GetDefaultThemeName(), "InterfaceSettingsWidget");
|
||||
connect(m_ui.theme, QOverload<int>::of(&QComboBox::currentIndexChanged), [this]() { emit themeChanged(); });
|
||||
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.backgroundOpacity, "UI", "GameListBackgroundOpacity", 100);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.backgroundFill, "UI", "GameListBackgroundFill", false);
|
||||
SettingWidgetBinder::BindWidgetToFloatSetting(sif, m_ui.backgroundOpacity, "UI", "GameListBackgroundOpacity", 100.0f);
|
||||
SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.backgroundScale, "UI", "GameListBackgroundMode", BACKGROUND_SCALE_NAMES, QtUtils::ScalingMode::Fit);
|
||||
connect(m_ui.backgroundBrowse, &QPushButton::clicked, [this]() { onSetGameListBackgroundTriggered(); });
|
||||
connect(m_ui.backgroundReset, &QPushButton::clicked, [this]() { onClearGameListBackgroundTriggered(); });
|
||||
connect(m_ui.backgroundOpacity, &QSpinBox::valueChanged, [this]() { emit backgroundChanged(); });
|
||||
connect(m_ui.backgroundFill, &QCheckBox::checkStateChanged, [this]() {emit backgroundChanged(); });
|
||||
connect(m_ui.backgroundOpacity, &QSpinBox::editingFinished, [this]() { emit backgroundChanged(); });
|
||||
connect(m_ui.backgroundScale, QOverload<int>::of(&QComboBox::currentIndexChanged), [this]() { emit backgroundChanged(); });
|
||||
|
||||
populateLanguages();
|
||||
SettingWidgetBinder::BindWidgetToStringSetting(sif, m_ui.language, "UI", "Language", QtHost::GetDefaultLanguage());
|
||||
@@ -198,7 +221,7 @@ InterfaceSettingsWidget::InterfaceSettingsWidget(SettingsWindow* settings_dialog
|
||||
tr("Automatically starts Big Picture Mode instead of the regular Qt interface when PCSX2 launches."));
|
||||
dialog()->registerWidgetHelp(
|
||||
m_ui.backgroundBrowse, tr("Game List Background"), tr("None"),
|
||||
tr("Enable an animated / static background on the game list (where you launch your games).<br>"
|
||||
tr("Enable an animated/static background on the game list (where you launch your games).<br>"
|
||||
"This background is only visible in the library and will be hidden once a game is launched. It will also be paused when it's not in focus."));
|
||||
dialog()->registerWidgetHelp(
|
||||
m_ui.backgroundReset, tr("Disable/Reset Game List Background"), tr("None"),
|
||||
@@ -207,8 +230,9 @@ InterfaceSettingsWidget::InterfaceSettingsWidget(SettingsWindow* settings_dialog
|
||||
m_ui.backgroundOpacity, tr("Game List Background Opacity"), tr("100%"),
|
||||
tr("Sets the opacity of the custom background."));
|
||||
dialog()->registerWidgetHelp(
|
||||
m_ui.backgroundFill, tr("Fill Image"), tr("Unchecked"),
|
||||
tr("Expand the image to fill all available background area."));
|
||||
m_ui.backgroundScale, tr("Background Image Scaling"), tr("Fit"),
|
||||
tr("Select how to display the background image: <br><br>Fit (Preserve aspect ratio, fit to screen)"
|
||||
"<br>Fill (Preserve aspect ratio, fill the screen) <br>Stretch (Ignore aspect ratio) <br>Center (Centers the image without any scaling) <br>Tile (Repeat the image to fill the screen)"));
|
||||
|
||||
onRenderToSeparateWindowChanged();
|
||||
}
|
||||
@@ -235,20 +259,15 @@ void InterfaceSettingsWidget::onSetGameListBackgroundTriggered()
|
||||
return;
|
||||
|
||||
std::string relative_path = Path::MakeRelative(QDir::toNativeSeparators(path).toStdString(), EmuFolders::DataRoot);
|
||||
Host::SetBaseBoolSettingValue("UI", "GameListBackgroundEnabled", true);
|
||||
Host::SetBaseStringSettingValue("UI", "GameListBackgroundPath", relative_path.c_str());
|
||||
|
||||
if (!Host::ContainsBaseSettingValue("UI", "GameListBackgroundOpacity"))
|
||||
Host::SetBaseFloatSettingValue("UI", "GameListBackgroundOpacity", 100.0f);
|
||||
|
||||
Host::CommitBaseSettingChanges();
|
||||
emit backgroundChanged();
|
||||
}
|
||||
|
||||
void InterfaceSettingsWidget::onClearGameListBackgroundTriggered()
|
||||
{
|
||||
Host::SetBaseBoolSettingValue("UI", "GameListBackgroundEnabled", false);
|
||||
Host::RemoveBaseSettingValue("UI", "GameListBackgroundPath");
|
||||
Host::CommitBaseSettingChanges();
|
||||
emit backgroundChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,4 +33,6 @@ private:
|
||||
public:
|
||||
static const char* THEME_NAMES[];
|
||||
static const char* THEME_VALUES[];
|
||||
static const char* BACKGROUND_SCALE_NAMES[];
|
||||
static const char* IMAGE_FILE_FILTER;
|
||||
};
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>725</width>
|
||||
<height>617</height>
|
||||
<height>625</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
@@ -224,9 +224,6 @@
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="backgroundOpacity">
|
||||
<property name="wrapping">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="buttonSymbols">
|
||||
<enum>QAbstractSpinBox::ButtonSymbols::PlusMinus</enum>
|
||||
</property>
|
||||
@@ -251,10 +248,54 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="backgroundFill">
|
||||
<property name="text">
|
||||
<string>Fill Image</string>
|
||||
<widget class="QLabel" name="backgroundScaleLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Scaling:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>backgroundScale</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="backgroundScale">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Fit</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Fill</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Stretch</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Center</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Tile</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
@@ -351,7 +392,7 @@
|
||||
<tabstop>backgroundBrowse</tabstop>
|
||||
<tabstop>backgroundReset</tabstop>
|
||||
<tabstop>backgroundOpacity</tabstop>
|
||||
<tabstop>backgroundFill</tabstop>
|
||||
<tabstop>backgroundScale</tabstop>
|
||||
<tabstop>autoUpdateTag</tabstop>
|
||||
<tabstop>autoUpdateEnabled</tabstop>
|
||||
<tabstop>checkForUpdates</tabstop>
|
||||
|
||||
@@ -138,7 +138,7 @@ void MemoryCardSettingsWidget::autoSizeUI()
|
||||
void MemoryCardSettingsWidget::tryInsertCard(u32 slot, const QString& newCard)
|
||||
{
|
||||
// handle where the card is dragged in from explorer or something
|
||||
const int lastSlashPos = std::max(newCard.lastIndexOf('/'), newCard.lastIndexOf('\\'));
|
||||
const qsizetype lastSlashPos = std::max(newCard.lastIndexOf('/'), newCard.lastIndexOf('\\'));
|
||||
const std::string newCardStr(
|
||||
(lastSlashPos >= 0) ? newCard.mid(0, lastSlashPos).toStdString() : newCard.toStdString());
|
||||
if (newCardStr.empty())
|
||||
|
||||
518
pcsx2-qt/ShortcutCreationDialog.cpp
Normal file
518
pcsx2-qt/ShortcutCreationDialog.cpp
Normal file
@@ -0,0 +1,518 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "ShortcutCreationDialog.h"
|
||||
#include "QtHost.h"
|
||||
#include <fmt/format.h>
|
||||
#include <QtWidgets/QFileDialog>
|
||||
#include <QtWidgets/QMessageBox>
|
||||
#include "common/Console.h"
|
||||
#include "common/FileSystem.h"
|
||||
#include "common/Path.h"
|
||||
#include "common/StringUtil.h"
|
||||
#include "VMManager.h"
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include <Windows.h>
|
||||
#include <shlobj.h>
|
||||
#include <winnls.h>
|
||||
#include <shobjidl.h>
|
||||
#include <objbase.h>
|
||||
#include <objidl.h>
|
||||
#include <shlguid.h>
|
||||
#include <comdef.h>
|
||||
|
||||
#include <wrl/client.h>
|
||||
#endif
|
||||
|
||||
ShortcutCreationDialog::ShortcutCreationDialog(QWidget* parent, const QString& title, const QString& path)
|
||||
: QDialog(parent)
|
||||
, m_title(title)
|
||||
, m_path(path)
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
this->setWindowTitle(tr("Create Shortcut For %1").arg(title));
|
||||
this->setWindowIcon(QtHost::GetAppIcon());
|
||||
|
||||
#if defined(_WIN32)
|
||||
m_ui.shortcutStartMenu->setText(tr("Start Menu"));
|
||||
#else
|
||||
m_ui.shortcutStartMenu->setText(tr("Application Launcher"));
|
||||
#endif
|
||||
|
||||
connect(m_ui.overrideBootELFButton, &QPushButton::clicked, [&]() {
|
||||
const QString path = QFileDialog::getOpenFileName(this, tr("Select ELF File"), QString(), tr("ELF Files (*.elf);;All Files (*.*)"));
|
||||
if (!path.isEmpty())
|
||||
m_ui.overrideBootELFPath->setText(Path::ToNativePath(path.toStdString()).c_str());
|
||||
});
|
||||
|
||||
connect(m_ui.loadStateFileBrowse, &QPushButton::clicked, [&]() {
|
||||
const QString path = QFileDialog::getOpenFileName(this, tr("Select Save State File"), QString(), tr("Save States (*.p2s);;All Files (*.*)"));
|
||||
if (!path.isEmpty())
|
||||
m_ui.loadStateFilePath->setText(Path::ToNativePath(path.toStdString()).c_str());
|
||||
});
|
||||
|
||||
connect(m_ui.overrideBootELFToggle, &QCheckBox::toggled, m_ui.overrideBootELFPath, &QLineEdit::setEnabled);
|
||||
connect(m_ui.overrideBootELFToggle, &QCheckBox::toggled, m_ui.overrideBootELFButton, &QPushButton::setEnabled);
|
||||
connect(m_ui.gameArgsToggle, &QCheckBox::toggled, m_ui.gameArgs, &QLineEdit::setEnabled);
|
||||
connect(m_ui.loadStateIndexToggle, &QCheckBox::toggled, m_ui.loadStateIndex, &QSpinBox::setEnabled);
|
||||
connect(m_ui.loadStateFileToggle, &QCheckBox::toggled, m_ui.loadStateFilePath, &QLineEdit::setEnabled);
|
||||
connect(m_ui.loadStateFileToggle, &QCheckBox::toggled, m_ui.loadStateFileBrowse, &QPushButton::setEnabled);
|
||||
connect(m_ui.bootOptionToggle, &QCheckBox::toggled, m_ui.bootOptionDropdown, &QPushButton::setEnabled);
|
||||
connect(m_ui.fullscreenMode, &QCheckBox::toggled, m_ui.fullscreenModeDropdown, &QPushButton::setEnabled);
|
||||
|
||||
m_ui.shortcutDesktop->setChecked(true);
|
||||
m_ui.overrideBootELFPath->setEnabled(false);
|
||||
m_ui.overrideBootELFButton->setEnabled(false);
|
||||
m_ui.gameArgs->setEnabled(false);
|
||||
m_ui.bootOptionDropdown->setEnabled(false);
|
||||
m_ui.fullscreenModeDropdown->setEnabled(false);
|
||||
m_ui.loadStateIndex->setEnabled(false);
|
||||
m_ui.loadStateFileBrowse->setEnabled(false);
|
||||
m_ui.loadStateFilePath->setEnabled(false);
|
||||
|
||||
m_ui.loadStateIndex->setMaximum(VMManager::NUM_SAVE_STATE_SLOTS);
|
||||
|
||||
if (std::getenv("container"))
|
||||
{
|
||||
m_ui.portableModeToggle->setEnabled(false);
|
||||
m_ui.shortcutDesktop->setEnabled(false);
|
||||
m_ui.shortcutStartMenu->setEnabled(false);
|
||||
}
|
||||
|
||||
connect(m_ui.dialogButtons, &QDialogButtonBox::accepted, this, [&]() {
|
||||
std::vector<std::string> args;
|
||||
|
||||
if (m_ui.portableModeToggle->isChecked())
|
||||
args.push_back("-portable");
|
||||
|
||||
if (m_ui.overrideBootELFToggle->isChecked() && !m_ui.overrideBootELFPath->text().isEmpty())
|
||||
{
|
||||
args.push_back("-elf");
|
||||
args.push_back(m_ui.overrideBootELFPath->text().toStdString());
|
||||
}
|
||||
|
||||
if (m_ui.gameArgsToggle->isChecked() && !m_ui.gameArgs->text().isEmpty())
|
||||
{
|
||||
args.push_back("-gameargs");
|
||||
args.push_back(m_ui.gameArgs->text().toStdString());
|
||||
}
|
||||
|
||||
if (m_ui.bootOptionToggle->isChecked())
|
||||
args.push_back(m_ui.bootOptionDropdown->currentIndex() ? "-slowboot" : "-fastboot");
|
||||
|
||||
if (m_ui.loadStateIndexToggle->isChecked())
|
||||
{
|
||||
const s32 load_state_index = m_ui.loadStateIndex->value();
|
||||
if (load_state_index >= 1 && load_state_index <= VMManager::NUM_SAVE_STATE_SLOTS)
|
||||
{
|
||||
args.push_back("-state");
|
||||
args.push_back(StringUtil::ToChars(load_state_index));
|
||||
}
|
||||
}
|
||||
|
||||
if (m_ui.fullscreenMode->isChecked())
|
||||
args.push_back(m_ui.fullscreenModeDropdown->currentIndex() ? "-nofullscreen" : "-fullscreen");
|
||||
|
||||
if (m_ui.bigPictureModeToggle->isChecked())
|
||||
args.push_back("-bigpicture");
|
||||
|
||||
m_desktop = m_ui.shortcutDesktop->isChecked();
|
||||
|
||||
std::string custom_args = m_ui.customArgsInput->text().toStdString();
|
||||
|
||||
ShortcutCreationDialog::CreateShortcut(title.toStdString(), path.toStdString(), args, custom_args, m_desktop);
|
||||
|
||||
accept();
|
||||
});
|
||||
connect(m_ui.dialogButtons, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
}
|
||||
|
||||
void ShortcutCreationDialog::CreateShortcut(const std::string name, const std::string game_path, std::vector<std::string> passed_cli_args, std::string custom_args, bool is_desktop)
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
if (name.empty())
|
||||
{
|
||||
Console.Error("Cannot create shortcuts without a name.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Sanitize filename
|
||||
const std::string clean_name = Path::SanitizeFileName(name).c_str();
|
||||
std::string clean_path = Path::ToNativePath(Path::RealPath(game_path)).c_str();
|
||||
if (!Path::IsValidFileName(clean_name))
|
||||
{
|
||||
QMessageBox::critical(this, tr("Failed to create shortcut"), tr("Filename contains illegal character."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
// Locate home directory
|
||||
std::string link_file;
|
||||
if (const char* home = std::getenv("USERPROFILE"))
|
||||
{
|
||||
if (is_desktop)
|
||||
link_file = Path::ToNativePath(fmt::format("{}/Desktop/{}.lnk", home, clean_name));
|
||||
else
|
||||
{
|
||||
const std::string start_menu_dir = Path::ToNativePath(fmt::format("{}/AppData/Roaming/Microsoft/Windows/Start Menu/Programs/PCSX2", home));
|
||||
if (!FileSystem::EnsureDirectoryExists(start_menu_dir.c_str(), false))
|
||||
{
|
||||
QMessageBox::critical(this, tr("Failed to create shortcut"), tr("Could not create start menu directory."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
link_file = Path::ToNativePath(fmt::format("{}/{}.lnk", start_menu_dir, clean_name));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox::critical(this, tr("Failed to create shortcut"), tr("Home path is empty."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the same shortcut already exists
|
||||
if (FileSystem::FileExists(link_file.c_str()))
|
||||
{
|
||||
QMessageBox::critical(this, tr("Failed to create shortcut"), tr("A shortcut with the same name already exists."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
// Shortcut CmdLine Args
|
||||
bool lossless = true;
|
||||
for (std::string& arg : passed_cli_args)
|
||||
lossless &= ShortcutCreationDialog::EscapeShortcutCommandLine(&arg);
|
||||
|
||||
if (!lossless)
|
||||
QMessageBox::warning(this, tr("Failed to create shortcut"), tr("File path contains invalid character(s). The resulting shortcut may not work."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
|
||||
|
||||
ShortcutCreationDialog::EscapeShortcutCommandLine(&clean_path);
|
||||
std::string combined_args = StringUtil::JoinString(passed_cli_args.begin(), passed_cli_args.end(), " ");
|
||||
std::string final_args = fmt::format("{} {} -- {}", combined_args, custom_args, clean_path);
|
||||
|
||||
Console.WriteLnFmt("Creating a shortcut '{}' with arguments '{}'", link_file, final_args);
|
||||
|
||||
const auto str_error = [](HRESULT hr) -> std::string {
|
||||
_com_error err(hr);
|
||||
const TCHAR* errMsg = err.ErrorMessage();
|
||||
return fmt::format("{} [{}]", StringUtil::WideStringToUTF8String(errMsg), hr);
|
||||
};
|
||||
|
||||
// Construct the shortcut
|
||||
// https://stackoverflow.com/questions/3906974/how-to-programmatically-create-a-shortcut-using-win32
|
||||
HRESULT res = CoInitialize(NULL);
|
||||
if (FAILED(res))
|
||||
{
|
||||
Console.ErrorFmt("Failed to create shortcut: CoInitialize failed ({})", str_error(res));
|
||||
QMessageBox::critical(this, tr("Failed to create shortcut"), tr("CoInitialize failed (%1").arg(str_error(res)), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
Microsoft::WRL::ComPtr<IShellLink> pShellLink;
|
||||
Microsoft::WRL::ComPtr<IPersistFile> pPersistFile;
|
||||
|
||||
const auto cleanup = [&](bool return_value, const QString& fail_reason) -> bool {
|
||||
if (!return_value)
|
||||
{
|
||||
Console.ErrorFmt("Failed to create shortcut: {}", fail_reason.toStdString());
|
||||
QMessageBox::critical(this, tr("Failed to create shortcut"), fail_reason, QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
|
||||
}
|
||||
CoUninitialize();
|
||||
return return_value;
|
||||
};
|
||||
|
||||
res = CoCreateInstance(__uuidof(ShellLink), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pShellLink));
|
||||
if (FAILED(res))
|
||||
{
|
||||
cleanup(false, tr("CoCreateInstance failed"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Set path to the executable
|
||||
const std::wstring target_file = StringUtil::UTF8StringToWideString(FileSystem::GetProgramPath());
|
||||
res = pShellLink->SetPath(target_file.c_str());
|
||||
if (FAILED(res))
|
||||
{
|
||||
cleanup(false, tr("SetPath failed (%1)").arg(str_error(res)));
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the working directory
|
||||
const std::wstring working_dir = StringUtil::UTF8StringToWideString(FileSystem::GetWorkingDirectory());
|
||||
res = pShellLink->SetWorkingDirectory(working_dir.c_str());
|
||||
if (FAILED(res))
|
||||
{
|
||||
cleanup(false, tr("SetWorkingDirectory failed (%1)").arg(str_error(res)));
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the launch arguments
|
||||
if (!final_args.empty())
|
||||
{
|
||||
const std::wstring target_cli_args = StringUtil::UTF8StringToWideString(final_args);
|
||||
res = pShellLink->SetArguments(target_cli_args.c_str());
|
||||
if (FAILED(res))
|
||||
{
|
||||
cleanup(false, tr("SetArguments failed (%1)").arg(str_error(res)));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Set the icon
|
||||
std::string icon_path = Path::ToNativePath(Path::Combine(Path::GetDirectory(FileSystem::GetProgramPath()), "resources/icons/AppIconLarge.ico"));
|
||||
const std::wstring w_icon_path = StringUtil::UTF8StringToWideString(icon_path);
|
||||
res = pShellLink->SetIconLocation(w_icon_path.c_str(), 0);
|
||||
if (FAILED(res))
|
||||
{
|
||||
cleanup(false, tr("SetIconLocation failed (%1)").arg(str_error(res)));
|
||||
return;
|
||||
}
|
||||
|
||||
// Use the IPersistFile object to save the shell link
|
||||
res = pShellLink.As(&pPersistFile);
|
||||
if (FAILED(res))
|
||||
{
|
||||
cleanup(false, tr("QueryInterface failed (%1)").arg(str_error(res)));
|
||||
return;
|
||||
}
|
||||
|
||||
// Save shortcut link to disk
|
||||
const std::wstring w_link_file = StringUtil::UTF8StringToWideString(link_file);
|
||||
res = pPersistFile->Save(w_link_file.c_str(), TRUE);
|
||||
if (FAILED(res))
|
||||
{
|
||||
cleanup(false, tr("Failed to save the shortcut (%1)").arg(str_error(res)));
|
||||
return;
|
||||
}
|
||||
|
||||
cleanup(true, {});
|
||||
|
||||
#else
|
||||
|
||||
if (name.empty())
|
||||
{
|
||||
QMessageBox::critical(this, tr("Failed to create shortcut"), tr("Cannot create a shortcut without a title."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
bool is_flatpak = (std::getenv("container"));
|
||||
|
||||
// Sanitize filename and game path
|
||||
const std::string clean_name = Path::SanitizeFileName(name);
|
||||
std::string clean_path = Path::Canonicalize(Path::RealPath(game_path));
|
||||
if (!Path::IsValidFileName(clean_name))
|
||||
{
|
||||
QMessageBox::critical(this, tr("Failed to create shortcut"), tr("Filename contains illegal character."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the executable path
|
||||
std::string executable_path = FileSystem::GetPackagePath();
|
||||
if (executable_path.empty())
|
||||
{
|
||||
QMessageBox::critical(this, tr("Failed to create shortcut"), tr("Executable path is empty."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_flatpak) // Flatpak
|
||||
executable_path = "flatpak run net.pcsx2.PCSX2";
|
||||
|
||||
// Find home directory
|
||||
std::string link_path;
|
||||
const char* home = std::getenv("HOME");
|
||||
const char* xdg_desktop_dir = std::getenv("XDG_DESKTOP_DIR");
|
||||
const char* xdg_data_home = std::getenv("XDG_DATA_HOME");
|
||||
if (home)
|
||||
{
|
||||
if (is_desktop)
|
||||
{
|
||||
if (xdg_desktop_dir)
|
||||
link_path = fmt::format("{}/{}.desktop", xdg_desktop_dir, clean_name);
|
||||
else
|
||||
link_path = fmt::format("{}/Desktop/{}.desktop", home, clean_name);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (xdg_data_home)
|
||||
link_path = fmt::format("{}/applications/{}.desktop", xdg_data_home, clean_name);
|
||||
else
|
||||
link_path = fmt::format("{}/.local/share/applications/{}.desktop", home, clean_name);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox::critical(this, tr("Failed to create shortcut"), tr("Home path is empty."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
// Checks if a shortcut already exist
|
||||
if (FileSystem::FileExists(link_path.c_str()))
|
||||
{
|
||||
QMessageBox::critical(this, tr("Failed to create shortcut"), tr("A shortcut with the same name already exists."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
// Shortcut CmdLine Args
|
||||
bool lossless = true;
|
||||
for (std::string& arg : passed_cli_args)
|
||||
lossless &= ShortcutCreationDialog::EscapeShortcutCommandLine(&arg);
|
||||
|
||||
if (!lossless)
|
||||
QMessageBox::warning(this, tr("Failed to create shortcut"), tr("File path contains invalid character(s). The resulting shortcut may not work."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
|
||||
|
||||
std::string cmdline = StringUtil::JoinString(passed_cli_args.begin(), passed_cli_args.end(), " ");
|
||||
|
||||
if (!is_flatpak)
|
||||
{
|
||||
// Copy PCSX2 icon
|
||||
std::string icon_dest;
|
||||
if (xdg_data_home)
|
||||
icon_dest = fmt::format("{}/icons/hicolor/512x512/apps/", xdg_data_home);
|
||||
else
|
||||
icon_dest = fmt::format("{}/.local/share/icons/hicolor/512x512/apps/", home);
|
||||
|
||||
std::string icon_name = "PCSX2.png";
|
||||
std::string icon_path = fmt::format("{}/{}", icon_dest, icon_name).c_str();
|
||||
if (FileSystem::EnsureDirectoryExists(icon_dest.c_str(), true))
|
||||
FileSystem::CopyFilePath(Path::Combine(EmuFolders::Resources, "icons/AppIconLarge.png").c_str(), icon_path.c_str(), false);
|
||||
}
|
||||
|
||||
// Further string sanitization
|
||||
if (!is_flatpak)
|
||||
ShortcutCreationDialog::EscapeShortcutCommandLine(&executable_path);
|
||||
ShortcutCreationDialog::EscapeShortcutCommandLine(&clean_path);
|
||||
|
||||
// Assembling the .desktop file
|
||||
std::string final_args;
|
||||
final_args = fmt::format("{} {} {} -- {}", executable_path, cmdline, custom_args, clean_path);
|
||||
std::string file_content =
|
||||
"[Desktop Entry]\n"
|
||||
"Encoding=UTF-8\n"
|
||||
"Version=1.0\n"
|
||||
"Type=Application\n"
|
||||
"Terminal=false\n"
|
||||
"StartupWMClass=PCSX2\n"
|
||||
"Exec=" + final_args + "\n"
|
||||
"Name=" + clean_name + "\n"
|
||||
"Icon=net.pcsx2.PCSX2\n"
|
||||
"Categories=Game;Emulator;\n";
|
||||
std::string_view sv(file_content);
|
||||
|
||||
// Prompt user for shortcut saving destination
|
||||
QString final_path(QStringLiteral("%1").arg(QString::fromStdString(link_path)));
|
||||
const QString filter(tr("Desktop Shortcut Files (*.desktop)"));
|
||||
|
||||
final_path = QDir::toNativeSeparators(QFileDialog::getSaveFileName(this, tr("Select Shortcut Save Destination"), final_path, filter));
|
||||
|
||||
if (final_path.isEmpty())
|
||||
return;
|
||||
|
||||
// Write to .desktop file
|
||||
if (!FileSystem::WriteStringToFile(final_path.toStdString().c_str(), sv))
|
||||
{
|
||||
QMessageBox::critical(this, tr("Failed to create shortcut"), tr("Failed to create .desktop file"), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
if (chmod(final_path.toStdString().c_str(), S_IRWXU) != 0) // enables user to execute file
|
||||
Console.ErrorFmt("Failed to change file permissions for .desktop file: {} ({})", strerror(errno), errno);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool ShortcutCreationDialog::EscapeShortcutCommandLine(std::string* arg)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (!arg->empty() && arg->find_first_of(" \t\n\v\"") == std::string::npos)
|
||||
return true;
|
||||
|
||||
std::string temp;
|
||||
temp.reserve(arg->length() + 10);
|
||||
temp += '"';
|
||||
|
||||
for (auto it = arg->begin();; ++it)
|
||||
{
|
||||
int backslash_count = 0;
|
||||
while (it != arg->end() && *it == '\\')
|
||||
{
|
||||
++it;
|
||||
++backslash_count;
|
||||
}
|
||||
|
||||
if (it == arg->end())
|
||||
{
|
||||
temp.append(backslash_count * 2, '\\');
|
||||
break;
|
||||
}
|
||||
|
||||
if (*it == '"')
|
||||
{
|
||||
temp.append(backslash_count * 2 + 1, '\\');
|
||||
temp += '"';
|
||||
}
|
||||
else
|
||||
{
|
||||
temp.append(backslash_count, '\\');
|
||||
temp += *it;
|
||||
}
|
||||
}
|
||||
|
||||
temp += '"';
|
||||
*arg = std::move(temp);
|
||||
return true;
|
||||
#else
|
||||
const char* carg = arg->c_str();
|
||||
const char* cend = carg + arg->size();
|
||||
const char* RESERVED_CHARS = " \t\n\\\"'\\\\><~|%&;$*?#()`"
|
||||
"\x01\x02\x03\x04\x05\x06\x07\x08\x0b\x0c\x0d\x0e\x0f"
|
||||
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x7f";
|
||||
const char* next = carg + std::strcspn(carg, RESERVED_CHARS);
|
||||
|
||||
if (next == cend)
|
||||
return true; // No escaping needed, don't modify
|
||||
|
||||
bool lossless = true;
|
||||
std::string temp = "\"";
|
||||
const char* NOT_VALID_IN_QUOTE = "%`$\"\\\n"
|
||||
"\x01\x02\x03\x04\x05\x06\x07\x08\x0b\x0c\x0d\x0e\x0f"
|
||||
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x7f";
|
||||
|
||||
while (true)
|
||||
{
|
||||
next = carg + std::strcspn(carg, NOT_VALID_IN_QUOTE);
|
||||
temp.append(carg, next);
|
||||
carg = next;
|
||||
|
||||
if (carg == cend)
|
||||
break;
|
||||
|
||||
switch (*carg)
|
||||
{
|
||||
case '"':
|
||||
case '`':
|
||||
temp.push_back('\\');
|
||||
temp.push_back(*carg);
|
||||
break;
|
||||
case '\\':
|
||||
temp.append("\\\\\\\\");
|
||||
break;
|
||||
case '$':
|
||||
temp.push_back('\\');
|
||||
temp.push_back('\\');
|
||||
temp.push_back(*carg);
|
||||
break;
|
||||
case '%':
|
||||
temp.push_back('%');
|
||||
temp.push_back(*carg);
|
||||
break;
|
||||
default:
|
||||
temp.push_back(' ');
|
||||
lossless = false;
|
||||
break;
|
||||
}
|
||||
++carg;
|
||||
}
|
||||
|
||||
temp.push_back('"');
|
||||
*arg = std::move(temp);
|
||||
return lossless;
|
||||
#endif
|
||||
}
|
||||
29
pcsx2-qt/ShortcutCreationDialog.h
Normal file
29
pcsx2-qt/ShortcutCreationDialog.h
Normal file
@@ -0,0 +1,29 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
#include "ui_ShortcutCreationDialog.h"
|
||||
|
||||
#include <QtWidgets/QDialog>
|
||||
|
||||
class ShortcutCreationDialog final : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ShortcutCreationDialog(QWidget* parent, const QString& title, const QString& path);
|
||||
~ShortcutCreationDialog() = default;
|
||||
|
||||
/// Create desktop shortcut for games
|
||||
void CreateShortcut(const std::string name, const std::string game_path, std::vector<std::string> passed_cli_args, std::string custom_args, bool is_desktop);
|
||||
|
||||
/// Escapes the given string for use with command line arguments.
|
||||
/// Returns a bool that indicates whether the escaping operation are lossless or not.
|
||||
bool EscapeShortcutCommandLine(std::string* cmdline);
|
||||
|
||||
protected:
|
||||
QString m_title;
|
||||
QString m_path;
|
||||
bool m_desktop;
|
||||
Ui::ShortcutCreationDialog m_ui;
|
||||
};
|
||||
317
pcsx2-qt/ShortcutCreationDialog.ui
Normal file
317
pcsx2-qt/ShortcutCreationDialog.ui
Normal file
@@ -0,0 +1,317 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ShortcutCreationDialog</class>
|
||||
<widget class="QDialog" name="ShortcutCreationDialog">
|
||||
<property name="windowModality">
|
||||
<enum>Qt::WindowModality::ApplicationModal</enum>
|
||||
</property>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>500</width>
|
||||
<height>685</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="shortcutLayout">
|
||||
<item row="4" column="0" colspan="2">
|
||||
<widget class="QGroupBox" name="launchArgsGroup">
|
||||
<property name="title">
|
||||
<string/>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="7" column="0">
|
||||
<widget class="QLabel" name="displayOptionLabel">
|
||||
<property name="text">
|
||||
<string>Display Options</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QCheckBox" name="overrideBootELFToggle">
|
||||
<property name="text">
|
||||
<string>Override boot ELF:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="11" column="0">
|
||||
<widget class="QLabel" name="customArgsLabel">
|
||||
<property name="text">
|
||||
<string>Custom Arguments</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>customArgsInput</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="0" colspan="4">
|
||||
<widget class="QGroupBox" name="displayOptionGroup">
|
||||
<property name="title">
|
||||
<string/>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="displayOptionGrid">
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="fullscreenMode">
|
||||
<property name="text">
|
||||
<string>Fullscreen mode:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="fullscreenModeDropdown">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Force Enable</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Force Disable</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="bigPictureModeToggle">
|
||||
<property name="text">
|
||||
<string>Use Big Picture mode</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="2" colspan="2">
|
||||
<widget class="QLineEdit" name="gameArgs"/>
|
||||
</item>
|
||||
<item row="4" column="2" colspan="2">
|
||||
<widget class="QComboBox" name="bootOptionDropdown">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Fast Boot</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Full Boot</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QCheckBox" name="bootOptionToggle">
|
||||
<property name="text">
|
||||
<string>Boot mode:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="3">
|
||||
<widget class="QPushButton" name="overrideBootELFButton">
|
||||
<property name="text">
|
||||
<string>Browse...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<widget class="QLineEdit" name="overrideBootELFPath"/>
|
||||
</item>
|
||||
<item row="10" column="0" colspan="4">
|
||||
<widget class="QGroupBox" name="saveStateGroup">
|
||||
<property name="title">
|
||||
<string/>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="savestateGridLayout">
|
||||
<item row="1" column="1">
|
||||
<widget class="QSpinBox" name="loadStateIndex">
|
||||
<property name="showGroupSeparator" stdset="0">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="loadStateFilePath"/>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QRadioButton" name="loadStateNone">
|
||||
<property name="text">
|
||||
<string>Do not load save state</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QRadioButton" name="loadStateIndexToggle">
|
||||
<property name="text">
|
||||
<string>Load save state by slot:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QRadioButton" name="loadStateFileToggle">
|
||||
<property name="text">
|
||||
<string>Load save state from file:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<widget class="QPushButton" name="loadStateFileBrowse">
|
||||
<property name="text">
|
||||
<string>Browse...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="0">
|
||||
<widget class="QLabel" name="saveStateLabel">
|
||||
<property name="text">
|
||||
<string>Save State Options</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QCheckBox" name="gameArgsToggle">
|
||||
<property name="text">
|
||||
<string>Game arguments:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="12" column="0" colspan="4">
|
||||
<widget class="QGroupBox" name="customArgsGroup">
|
||||
<property name="title">
|
||||
<string/>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="customArgsGrid">
|
||||
<item row="1" column="0">
|
||||
<widget class="QLineEdit" name="customArgsInput"/>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="customArgsInstruction">
|
||||
<property name="text">
|
||||
<string>You may add additional (space-separated) <a href="https://pcsx2.net/docs/post/cli/">custom arguments</a> that are not listed above here:</string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::TextFormat::RichText</enum>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::TextInteractionFlag::TextBrowserInteraction</set>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>customArgsInput</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="3">
|
||||
<widget class="QCheckBox" name="portableModeToggle">
|
||||
<property name="text">
|
||||
<string>Portable Mode (Stores data in local PCSX2 directory)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="shortcutTypeLabel">
|
||||
<property name="text">
|
||||
<string>Shortcut Type</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QDialogButtonBox" name="dialogButtons">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok</set>
|
||||
</property>
|
||||
<property name="centerButtons">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="instruction">
|
||||
<property name="text">
|
||||
<string>Launch Options</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QGroupBox" name="shortcutTypeGroup">
|
||||
<property name="title">
|
||||
<string/>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="shortcutTypeLayout">
|
||||
<item row="2" column="0">
|
||||
<widget class="QRadioButton" name="shortcutStartMenu">
|
||||
<property name="text">
|
||||
<string>Launcher</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QRadioButton" name="shortcutDesktop">
|
||||
<property name="text">
|
||||
<string>Desktop</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>shortcutDesktop</tabstop>
|
||||
<tabstop>shortcutStartMenu</tabstop>
|
||||
<tabstop>portableModeToggle</tabstop>
|
||||
<tabstop>overrideBootELFToggle</tabstop>
|
||||
<tabstop>overrideBootELFPath</tabstop>
|
||||
<tabstop>overrideBootELFButton</tabstop>
|
||||
<tabstop>gameArgsToggle</tabstop>
|
||||
<tabstop>gameArgs</tabstop>
|
||||
<tabstop>bootOptionToggle</tabstop>
|
||||
<tabstop>bootOptionDropdown</tabstop>
|
||||
<tabstop>fullscreenMode</tabstop>
|
||||
<tabstop>fullscreenModeDropdown</tabstop>
|
||||
<tabstop>bigPictureModeToggle</tabstop>
|
||||
<tabstop>loadStateNone</tabstop>
|
||||
<tabstop>loadStateIndexToggle</tabstop>
|
||||
<tabstop>loadStateIndex</tabstop>
|
||||
<tabstop>loadStateFileToggle</tabstop>
|
||||
<tabstop>loadStateFilePath</tabstop>
|
||||
<tabstop>loadStateFileBrowse</tabstop>
|
||||
<tabstop>customArgsInput</tabstop>
|
||||
</tabstops>
|
||||
<resources>
|
||||
<include location="resources/resources.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -133,7 +133,7 @@ void QtHost::InstallTranslator(QWidget* dialog_parent)
|
||||
if (!has_base_ts)
|
||||
{
|
||||
// Try without the country suffix.
|
||||
const int index = language.lastIndexOf('-');
|
||||
const qsizetype index = language.lastIndexOf('-');
|
||||
if (index > 0)
|
||||
{
|
||||
base_path = QStringLiteral("%1/qt_%2.qm").arg(base_dir).arg(language.left(index));
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -171,6 +171,7 @@
|
||||
<ClCompile Include="AutoUpdaterDialog.cpp" />
|
||||
<ClCompile Include="CoverDownloadDialog.cpp" />
|
||||
<ClCompile Include="DisplayWidget.cpp" />
|
||||
<ClCompile Include="ShortcutCreationDialog.cpp" />
|
||||
<ClCompile Include="QtHost.cpp" />
|
||||
<ClCompile Include="MainWindow.cpp" />
|
||||
<ClCompile Include="PrecompiledHeader.cpp">
|
||||
@@ -267,6 +268,7 @@
|
||||
<QtMoc Include="AboutDialog.h" />
|
||||
<QtMoc Include="AutoUpdaterDialog.h" />
|
||||
<QtMoc Include="CoverDownloadDialog.h" />
|
||||
<QtMoc Include="ShortcutCreationDialog.h" />
|
||||
<QtMoc Include="MainWindow.h" />
|
||||
<QtMoc Include="DisplayWidget.h" />
|
||||
</ItemGroup>
|
||||
@@ -350,6 +352,7 @@
|
||||
<ClCompile Include="$(IntDir)moc_QtHost.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_QtProgressCallback.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_SetupWizardDialog.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_ShortcutCreationDialog.cpp" />
|
||||
<ClCompile Include="$(IntDir)Tools\InputRecording\moc_NewInputRecordingDlg.cpp" />
|
||||
<ClCompile Include="$(IntDir)Tools\InputRecording\moc_InputRecordingViewer.cpp" />
|
||||
<ClCompile Include="$(IntDir)qrc_resources.cpp">
|
||||
@@ -360,6 +363,7 @@
|
||||
<QtUi Include="AboutDialog.ui" />
|
||||
<QtUi Include="AutoUpdaterDialog.ui" />
|
||||
<QtUi Include="CoverDownloadDialog.ui" />
|
||||
<QtUi Include="ShortcutCreationDialog.ui" />
|
||||
<QtUi Include="Debugger\AnalysisOptionsDialog.ui" />
|
||||
<QtUi Include="Debugger\Breakpoints\BreakpointDialog.ui" />
|
||||
<QtUi Include="Debugger\Breakpoints\BreakpointView.ui" />
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user