Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e9977f2a2c | ||
|
|
df8d809506 | ||
|
|
4d89fec9ff | ||
|
|
27b641d048 | ||
|
|
e475c0df06 | ||
|
|
9877129815 | ||
|
|
3376001d45 | ||
|
|
0cbd884234 | ||
|
|
3e8327e934 | ||
|
|
ca9d88f47b | ||
|
|
bf92ffffa3 | ||
|
|
eccec21ada | ||
|
|
03aaf7db6a | ||
|
|
6d9e0482b4 | ||
|
|
34f2328a79 | ||
|
|
2569193b05 | ||
|
|
b935ec3d19 | ||
|
|
e0e6b0d9a5 | ||
|
|
580218d495 | ||
|
|
519f280fa5 | ||
|
|
a33ee13bb4 | ||
|
|
0c70cc7e5a | ||
|
|
2cba346ff5 | ||
|
|
db2509edb3 | ||
|
|
e3063d6cd6 | ||
|
|
8d30e8cee8 | ||
|
|
ff2f1998ad | ||
|
|
7942ee438a | ||
|
|
f322dfb1d4 | ||
|
|
a7f5ddfe0d | ||
|
|
0cdfb75fd0 | ||
|
|
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 |
@@ -11,7 +11,7 @@ jobs:
|
||||
name: "Update Base Translation"
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Update Base Translation
|
||||
run: ./.github/workflows/scripts/common/update_base_translation.sh
|
||||
|
||||
@@ -9,7 +9,7 @@ jobs:
|
||||
if: github.repository == 'PCSX2/pcsx2'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Get Latest DB and Prepare DB File
|
||||
run: |
|
||||
|
||||
@@ -18,12 +18,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install Packages
|
||||
run: |
|
||||
npm install -g ajv-cli prettier
|
||||
sudo apt-get -y install yamllint
|
||||
pip install yamllint
|
||||
|
||||
- name: Validate YAML
|
||||
run: |
|
||||
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
set-safe-directory: ${{ env.GITHUB_WORKSPACE }}
|
||||
# 10 here, since the odds of having 10 untagged commits in a row should be slim to none
|
||||
|
||||
@@ -59,7 +59,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -104,13 +104,13 @@ jobs:
|
||||
sudo tools/retry.sh apt-add-repository -n 'deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-17 main'
|
||||
sudo tools/retry.sh apt-get update
|
||||
sudo tools/retry.sh apt-get -y install \
|
||||
build-essential ccache clang-17 cmake curl extra-cmake-modules git libasound2-dev libaio-dev libavcodec-dev libavformat-dev libavutil-dev \
|
||||
build-essential ccache clang-17 cmake curl extra-cmake-modules git libasound2-dev libaio-dev \
|
||||
libcurl4-openssl-dev libdbus-1-dev libdecor-0-dev libegl-dev libevdev-dev libfontconfig-dev libfreetype-dev libfuse2 libgtk-3-dev libgudev-1.0-dev \
|
||||
libharfbuzz-dev libinput-dev libopengl-dev libpcap-dev libpipewire-0.3-dev libpulse-dev libssl-dev libswresample-dev libswscale-dev libudev-dev \
|
||||
libwayland-dev libx11-dev libx11-xcb-dev libxcb1-dev libxcb-composite0-dev libxcb-cursor-dev libxcb-damage0-dev libxcb-glx0-dev libxcb-icccm4-dev \
|
||||
libharfbuzz-dev libinput-dev libopengl-dev libopus-dev libpcap-dev libpipewire-0.3-dev libpulse-dev libssl-dev libudev-dev libva-dev libvpl2 libvpl-dev \
|
||||
libwayland-dev libx11-dev libx11-xcb-dev libx264-dev libxcb1-dev libxcb-composite0-dev libxcb-cursor-dev libxcb-damage0-dev libxcb-glx0-dev libxcb-icccm4-dev \
|
||||
libxcb-image0-dev libxcb-keysyms1-dev libxcb-present-dev libxcb-randr0-dev libxcb-render0-dev libxcb-render-util0-dev libxcb-shape0-dev \
|
||||
libxcb-shm0-dev libxcb-sync-dev libxcb-util-dev libxcb-xfixes0-dev libxcb-xinput-dev libxcb-xkb-dev libxext-dev libxkbcommon-x11-dev libxrandr-dev \
|
||||
lld-17 llvm-17 ninja-build patchelf pkg-config zlib1g-dev
|
||||
lld-17 llvm-17 nasm ninja-build patchelf pkg-config zlib1g-dev
|
||||
|
||||
- name: Cache Dependencies
|
||||
id: cache-deps
|
||||
@@ -121,7 +121,7 @@ jobs:
|
||||
|
||||
- name: Build Dependencies
|
||||
if: steps.cache-deps.outputs.cache-hit != 'true'
|
||||
run: .github/workflows/scripts/linux/build-dependencies-qt.sh "$HOME/deps"
|
||||
run: BUILD_FFMPEG=1 .github/workflows/scripts/linux/build-dependencies-qt.sh "$HOME/deps"
|
||||
|
||||
- name: Download patches
|
||||
run: |
|
||||
|
||||
@@ -48,7 +48,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
# actions/checkout elides tags, fetch them primarily for releases
|
||||
- name: Fetch Tags
|
||||
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
outputs:
|
||||
new_tag: ${{ steps.tag_version.outputs.new_tag }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
# Docs - https://github.com/mathieudutour/github-tag-action
|
||||
- name: Bump Version and Push Tag
|
||||
@@ -163,7 +163,7 @@ jobs:
|
||||
name: "Upload Artifacts"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Prepare Artifact Folder
|
||||
run: mkdir ./ci-artifacts/
|
||||
|
||||
@@ -41,12 +41,9 @@ BINARY=pcsx2-qt
|
||||
APPDIRNAME=PCSX2.AppDir
|
||||
STRIP=strip
|
||||
|
||||
# Need both libharfbuzz.so and libharfbuzz.so.0 for bundled libs
|
||||
|
||||
declare -a MANUAL_LIBS=(
|
||||
"libshaderc_shared.so.1"
|
||||
"libharfbuzz.so.0"
|
||||
"libharfbuzz.so"
|
||||
"libfreetype.so.6"
|
||||
)
|
||||
|
||||
@@ -74,18 +71,6 @@ fi
|
||||
OUTDIR=$(realpath "./$APPDIRNAME")
|
||||
rm -fr "$OUTDIR"
|
||||
|
||||
# Our deps build dosn't create libharfbuzz.so.0, so we have to symlink it here
|
||||
hbpath=$(find "$DEPSDIR" -name "libharfbuzz.so")
|
||||
if [ ! -f "$hbpath" ]; then
|
||||
echo "Missing harfbuzz. Exiting."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "$hbpath.0" ]; then
|
||||
echo "Symlinking libharfbuzz.so.0"
|
||||
ln -s "$hbpath" "$hbpath.0"
|
||||
fi
|
||||
|
||||
echo "Locating extra libraries..."
|
||||
EXTRA_LIBS_ARGS=()
|
||||
for lib in "${MANUAL_LIBS[@]}"; do
|
||||
|
||||
@@ -7,6 +7,10 @@ if [ "$#" -ne 1 ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# The bundled ffmpeg has a lot of things disabled to reduce code size.
|
||||
# Users may want to use system ffmpeg for additional features
|
||||
: ${BUILD_FFMPEG:=0}
|
||||
|
||||
SCRIPTDIR=$(realpath $(dirname "${BASH_SOURCE[0]}"))
|
||||
NPROCS="$(getconf _NPROCESSORS_ONLN)"
|
||||
INSTALLDIR="$1"
|
||||
@@ -14,16 +18,19 @@ if [ "${INSTALLDIR:0:1}" != "/" ]; then
|
||||
INSTALLDIR="$PWD/$INSTALLDIR"
|
||||
fi
|
||||
|
||||
FFMPEG=8.0
|
||||
FREETYPE=2.14.1
|
||||
HARFBUZZ=12.0.0
|
||||
LIBBACKTRACE=ad106d5fdd5d960bd33fae1c48a351af567fd075
|
||||
LIBJPEGTURBO=3.1.2
|
||||
LIBPNG=1.6.50
|
||||
LIBWEBP=1.6.0
|
||||
NVENC=11.1.5.3
|
||||
SDL=SDL3-3.2.26
|
||||
QT=6.10.0
|
||||
QT=6.10.1
|
||||
QTAPNG=1.3.0
|
||||
LZ4=1.10.0
|
||||
VULKAN=1.4.328.1
|
||||
ZSTD=1.5.7
|
||||
KDDOCKWIDGETS=2.4.0
|
||||
PLUTOVG=1.3.1
|
||||
@@ -37,7 +44,10 @@ SHADERC_SPIRVTOOLS=33e02568181e3312f49a3cf33df470bf96ef293a
|
||||
mkdir -p deps-build
|
||||
cd deps-build
|
||||
|
||||
export PKG_CONFIG_PATH="$INSTALLDIR/lib/pkgconfig:$PKG_CONFIG_PATH"
|
||||
|
||||
cat > SHASUMS <<EOF
|
||||
b2751fccb6cc4c77708113cd78b561059b6fa904b24162fa0be2d60273d27b8e ffmpeg-$FFMPEG.tar.xz
|
||||
32427e8c471ac095853212a37aef816c60b42052d4d9e48230bab3bdf2936ccc freetype-$FREETYPE.tar.xz
|
||||
c4a398539c3e0fdc9a82dfe7824d0438cae78c1e2124e7c6ada3dfa600cdb6c8 harfbuzz-$HARFBUZZ.tar.gz
|
||||
fd6f417fe9e3a071cf1424a5152d926a34c4a3c5070745470be6cf12a404ed79 $LIBBACKTRACE.zip
|
||||
@@ -47,13 +57,15 @@ e4ab7009bf0629fd11982d4c2aa83964cf244cffba7347ecd39019a9e38c4564 libwebp-$LIBWE
|
||||
dad488474a51a0b01d547cd2834893d6299328d2e30f479a3564088b5476bae2 $SDL.tar.gz
|
||||
687ddc0c7cb128a3ea58e159b5129252537c27ede0c32a93f11f03127f0c0165 libpng-$LIBPNG-apng.patch.gz
|
||||
537512904744b35e232912055ccf8ec66d768639ff3abe5788d90d792ec5f48b lz4-$LZ4.tar.gz
|
||||
2974b91062197e0527dffa3aadd8fe3bfa6681ae45f5ff9181bc0ca6479abd59 nv-codec-headers-$NVENC.tar.gz
|
||||
c465aa56757e7746ac707f582b6e2d51546569a4a2488c1172fb543aa5fdfc2c vulkan-sdk-$VULKAN.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
|
||||
@@ -75,6 +87,9 @@ curl -L \
|
||||
-O "https://github.com/lz4/lz4/releases/download/v$LZ4/lz4-$LZ4.tar.gz" \
|
||||
-O "https://libsdl.org/release/$SDL.tar.gz" \
|
||||
-O "https://github.com/facebook/zstd/releases/download/v$ZSTD/zstd-$ZSTD.tar.gz" \
|
||||
-O "https://github.com/KhronosGroup/Vulkan-Headers/archive/refs/tags/vulkan-sdk-$VULKAN.tar.gz" \
|
||||
-O "https://github.com/FFmpeg/nv-codec-headers/releases/download/n$NVENC/nv-codec-headers-$NVENC.tar.gz" \
|
||||
-O "https://ffmpeg.org/releases/ffmpeg-$FFMPEG.tar.xz" \
|
||||
-O "https://download.qt.io/official_releases/qt/${QT%.*}/$QT/submodules/qtbase-everywhere-src-$QT.tar.xz" \
|
||||
-O "https://download.qt.io/official_releases/qt/${QT%.*}/$QT/submodules/qtimageformats-everywhere-src-$QT.tar.xz" \
|
||||
-O "https://download.qt.io/official_releases/qt/${QT%.*}/$QT/submodules/qtsvg-everywhere-src-$QT.tar.xz" \
|
||||
@@ -92,6 +107,37 @@ curl -L \
|
||||
|
||||
shasum -a 256 --check SHASUMS
|
||||
|
||||
if [ "$BUILD_FFMPEG" -ne 0 ]; then
|
||||
echo "Installing vulkan headers..."
|
||||
rm -fr "Vulkan-Headers-vulkan-sdk-$VULKAN"
|
||||
tar xf "vulkan-sdk-$VULKAN.tar.gz"
|
||||
cd "Vulkan-Headers-vulkan-sdk-$VULKAN"
|
||||
cmake -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="$INSTALLDIR" -DCMAKE_INSTALL_PREFIX="$INSTALLDIR"
|
||||
make -C build install
|
||||
cd ..
|
||||
|
||||
echo "Installing nvenc headers..."
|
||||
rm -fr "nv-codec-headers-$NVENC"
|
||||
tar xf "nv-codec-headers-$NVENC.tar.gz"
|
||||
make -C "nv-codec-headers-$NVENC" PREFIX="$INSTALLDIR" install
|
||||
|
||||
echo "Installing FFmpeg..."
|
||||
rm -fr "ffmpeg-$FFMPEG"
|
||||
tar xf "ffmpeg-$FFMPEG.tar.xz"
|
||||
cd "ffmpeg-$FFMPEG"
|
||||
CFLAGS="-Os $CFLAGS" CXXFLAGS="-Os $CXXFLAGS" \
|
||||
./configure --prefix="$INSTALLDIR" \
|
||||
--disable-all --disable-autodetect --disable-static --enable-shared \
|
||||
--enable-avcodec --enable-avformat --enable-avutil --enable-swresample --enable-swscale \
|
||||
--enable-gpl --enable-libx264 --enable-libopus --enable-vulkan --enable-ffnvcodec --enable-nvenc --enable-vaapi --enable-libvpl \
|
||||
--enable-encoder=ffv1,qtrle,libx264*,aac,flac,libopus,pcm_s16be,pcm_s16le,*_vulkan,*_qsv,*_nvenc,*_vaapi \
|
||||
--enable-muxer=avi,matroska,mov,mp3,mp4,wav \
|
||||
--enable-protocol=file
|
||||
make "-j$NPROCS"
|
||||
make install
|
||||
cd ..
|
||||
fi
|
||||
|
||||
echo "Building libbacktrace..."
|
||||
rm -fr "libbacktrace-$LIBBACKTRACE"
|
||||
unzip "$LIBBACKTRACE.zip"
|
||||
@@ -165,7 +211,9 @@ echo "Building HarfBuzz..."
|
||||
rm -fr "harfbuzz-$HARFBUZZ"
|
||||
tar xf "harfbuzz-$HARFBUZZ.tar.gz"
|
||||
cd "harfbuzz-$HARFBUZZ"
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="$INSTALLDIR" -DCMAKE_INSTALL_PREFIX="$INSTALLDIR" -DBUILD_SHARED_LIBS=ON -DHB_BUILD_UTILS=OFF -DHB_HAVE_FREETYPE=ON -B build -G Ninja
|
||||
# Add an SOVERSION to match system harfbuzz
|
||||
sed -i 's/PROPERTIES VISIBILITY_INLINES_HIDDEN TRUE)/PROPERTIES VISIBILITY_INLINES_HIDDEN TRUE SOVERSION 0)/g' CMakeLists.txt
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="$INSTALLDIR" -DCMAKE_INSTALL_PREFIX="$INSTALLDIR" -DBUILD_SHARED_LIBS=ON -DHB_BUILD_UTILS=OFF -DHB_HAVE_FREETYPE=ON -DHB_HAVE_GOBJECT=ON -B build -G Ninja
|
||||
cmake --build build --parallel
|
||||
ninja -C build install
|
||||
cd ..
|
||||
@@ -199,7 +247,7 @@ tar xf "qtbase-everywhere-src-$QT.tar.xz"
|
||||
cd "qtbase-everywhere-src-$QT"
|
||||
mkdir build
|
||||
cd build
|
||||
../configure -prefix "$INSTALLDIR" -release -dbus-linked -gui -widgets -fontconfig -qt-doubleconversion -ssl -openssl-runtime -opengl desktop -qpa xcb,wayland -xkbcommon -xcb -gtk -- -DFEATURE_dbus=ON -DFEATURE_icu=OFF -DFEATURE_printsupport=OFF -DFEATURE_sql=OFF -DFEATURE_system_png=ON -DFEATURE_system_jpeg=ON -DFEATURE_system_zlib=ON -DFEATURE_system_freetype=ON -DFEATURE_system_harfbuzz=ON
|
||||
../configure -prefix "$INSTALLDIR" -release -dbus-linked -gui -widgets -fontconfig -qt-doubleconversion -ssl -openssl-runtime -opengl desktop -qpa xcb,wayland -xkbcommon -xcb -gtk -- --log-level=STATUS -DCMAKE_PREFIX_PATH="$INSTALLDIR" -DFEATURE_dbus=ON -DFEATURE_icu=OFF -DFEATURE_printsupport=OFF -DFEATURE_sql=OFF -DFEATURE_system_png=ON -DFEATURE_system_jpeg=ON -DFEATURE_system_zlib=ON -DFEATURE_system_freetype=ON -DFEATURE_system_harfbuzz=ON
|
||||
cmake --build . --parallel
|
||||
ninja install
|
||||
cd ../../
|
||||
|
||||
@@ -48,7 +48,7 @@ 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.4.0
|
||||
PLUTOVG=1.3.1
|
||||
@@ -89,11 +89,11 @@ 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
|
||||
|
||||
@@ -30,7 +30,7 @@ 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.4.0
|
||||
PLUTOVG=1.3.1
|
||||
@@ -70,11 +70,11 @@ 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
|
||||
@@ -237,9 +237,6 @@ cd "qtbase-everywhere-src-$QT"
|
||||
# Patch Qt to support macOS 11
|
||||
patch -p1 < "$SCRIPTDIR/qt-macos11compat.patch"
|
||||
|
||||
# Patch Qt to fix a bug with message boxes on Tahoe
|
||||
patch -p1 < "$SCRIPTDIR/clickbutton.patch"
|
||||
|
||||
# since we don't have a direct reference to QtSvg, it doesn't deployed directly from the main binary
|
||||
# (only indirectly from iconengines), and the libqsvg.dylib imageformat plugin does not get deployed.
|
||||
# We could run macdeployqt twice, but that's even more janky than patching it.
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
--- a/src/plugins/platforms/cocoa/qcocoamessagedialog.mm
|
||||
+++ b/src/plugins/platforms/cocoa/qcocoamessagedialog.mm
|
||||
@@ -88,6 +88,11 @@ bool QCocoaMessageDialog::show(Qt::WindowModality windowModality)
|
||||
qCWarning(lcQpaDialogs, "Cannot run window modal dialog without parent window");
|
||||
return false;
|
||||
}
|
||||
+
|
||||
+ // Tahoe has issues with window-modal alert buttons not responding to mouse
|
||||
+ if (windowModality == Qt::WindowModal
|
||||
+ && QOperatingSystemVersion::current() >= QOperatingSystemVersion::MacOSTahoe)
|
||||
+ return false;
|
||||
|
||||
// And without options we don't know what to show
|
||||
if (!options())
|
||||
|
||||
# Source https://codereview.qt-project.org/c/qt/qtbase/+/689796
|
||||
@@ -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.
|
||||
|
||||
@@ -48,7 +48,7 @@ set LIBJPEGTURBO=3.1.2
|
||||
set LIBPNG=1650
|
||||
set LIBPNGLONG=1.6.50
|
||||
set SDL=SDL3-3.2.26
|
||||
set QT=6.10.0
|
||||
set QT=6.10.1
|
||||
set QTMINOR=6.10
|
||||
set QTAPNG=1.3.0
|
||||
set LZ4=1.10.0
|
||||
@@ -72,11 +72,11 @@ call :downloadfile "lpng%LIBPNG%-apng.patch.gz" https://download.sourceforge.net
|
||||
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" 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" 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 "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
|
||||
@@ -264,7 +264,7 @@ rmdir /S /Q "QtApng-%QTAPNG%"
|
||||
%SEVENZIP% x "QtApng-%QTAPNG%.zip" || goto error
|
||||
cd "QtApng-%QTAPNG%" || goto error
|
||||
%PATCH% -p1 < "%SCRIPTDIR%\..\common\qtapng-cmake.patch" || goto error
|
||||
cmake -B build -DCMAKE_PREFIX_PATH="%INSTALLDIR%" -DCMAKE_INSTALL_PREFIX="%INSTALLDIR%" %FORCEPDB% %QTAPNGBUILDSPEC% || goto error
|
||||
cmake -B build %ARM64TOOLCHAIN% -DCMAKE_PREFIX_PATH="%INSTALLDIR%" -DCMAKE_INSTALL_PREFIX="%INSTALLDIR%" %FORCEPDB% %QTAPNGBUILDSPEC% || goto error
|
||||
cmake --build build --parallel || goto error
|
||||
ninja -C build install || goto error
|
||||
cd .. || goto error
|
||||
|
||||
@@ -46,7 +46,7 @@ set LIBJPEGTURBO=3.1.2
|
||||
set LIBPNG=1650
|
||||
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
|
||||
@@ -70,11 +70,11 @@ call :downloadfile "lpng%LIBPNG%-apng.patch.gz" https://download.sourceforge.net
|
||||
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" 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" 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 "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
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
runs-on: windows-2025
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
- name: Verify VS Project Files
|
||||
run: .github\workflows\scripts\windows\validate-vs-filters.ps1
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Configure MSBuild Clang Version
|
||||
if: inputs.configuration != 'CMake'
|
||||
|
||||
@@ -113,13 +113,27 @@ typename SymbolList<SymbolType>::AddressToHandleMapIterators SymbolList<SymbolTy
|
||||
template <typename SymbolType>
|
||||
typename SymbolList<SymbolType>::AddressToHandleMapIterators SymbolList<SymbolType>::handles_from_address_range(AddressRange range) const
|
||||
{
|
||||
if(range.low.valid()) {
|
||||
return {m_address_to_handle.lower_bound(range.low.value), m_address_to_handle.lower_bound(range.high.value)};
|
||||
} else if(range.high.valid()) {
|
||||
return {m_address_to_handle.begin(), m_address_to_handle.lower_bound(range.high.value)};
|
||||
typename AddressToHandleMap::const_iterator begin, end;
|
||||
if (range.low.valid() && range.high.valid()) {
|
||||
if (range.low.value < range.high.value) {
|
||||
begin = m_address_to_handle.lower_bound(range.low.value);
|
||||
end = m_address_to_handle.lower_bound(range.high.value);
|
||||
} else {
|
||||
begin = m_address_to_handle.end();
|
||||
end = m_address_to_handle.end();
|
||||
}
|
||||
} else if (range.low.valid()) {
|
||||
begin = m_address_to_handle.lower_bound(range.low.value);
|
||||
end = m_address_to_handle.end();
|
||||
} else if (range.high.valid()) {
|
||||
begin = m_address_to_handle.begin();
|
||||
end = m_address_to_handle.lower_bound(range.high.value);
|
||||
} else {
|
||||
return {m_address_to_handle.end(), m_address_to_handle.end()};
|
||||
begin = m_address_to_handle.end();
|
||||
end = m_address_to_handle.end();
|
||||
}
|
||||
|
||||
return {begin, end};
|
||||
}
|
||||
|
||||
template <typename SymbolType>
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
†---------------------†
|
||||
@@ -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,
|
||||
@@ -839,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,
|
||||
@@ -1119,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,
|
||||
@@ -1774,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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -446,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");
|
||||
@@ -533,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"))
|
||||
@@ -726,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"))
|
||||
@@ -857,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();
|
||||
@@ -888,9 +903,6 @@ 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.");
|
||||
@@ -900,18 +912,14 @@ int main(int argc, char* argv[])
|
||||
// Override settings that shouldn't be picked up from defaults or INIs.
|
||||
GSRunner::SettingsOverride();
|
||||
|
||||
// apply new settings (e.g. pick up renderer change)
|
||||
VMManager::ApplySettings();
|
||||
GSDumpReplayer::SetIsDumpRunner(true);
|
||||
|
||||
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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -208,7 +208,7 @@
|
||||
</action>
|
||||
<action name="actionAnalyse">
|
||||
<property name="icon">
|
||||
<iconset theme="magnifier-line"/>
|
||||
<iconset theme="search-line"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Analyze</string>
|
||||
|
||||
@@ -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)
|
||||
@@ -130,12 +144,12 @@ void DockMenuBar::onLockStateChanged(bool layout_locked)
|
||||
if (layout_locked)
|
||||
{
|
||||
m_layout_locked_toggle->setText(tr("Layout Locked"));
|
||||
m_layout_locked_toggle->setIcon(QIcon::fromTheme(QString::fromUtf8("padlock-lock")));
|
||||
m_layout_locked_toggle->setIcon(QIcon::fromTheme(QString::fromUtf8("lock-fill")));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_layout_locked_toggle->setText(tr("Layout Unlocked"));
|
||||
m_layout_locked_toggle->setIcon(QIcon::fromTheme(QString::fromUtf8("padlock-unlock")));
|
||||
m_layout_locked_toggle->setIcon(QIcon::fromTheme(QString::fromUtf8("lock-unlock-fill")));
|
||||
}
|
||||
|
||||
m_ignore_lock_state_changed = false;
|
||||
@@ -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,7 +296,10 @@ 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);
|
||||
|
||||
@@ -312,27 +310,10 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "common/Console.h"
|
||||
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtCore/QTimer>
|
||||
#include <QtGui/QGuiApplication>
|
||||
#include <QtGui/QKeyEvent>
|
||||
#include <QtGui/QResizeEvent>
|
||||
@@ -29,20 +30,16 @@
|
||||
#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);
|
||||
m_resize_debounce_timer = new QTimer(this);
|
||||
m_resize_debounce_timer->setSingleShot(true);
|
||||
m_resize_debounce_timer->setTimerType(Qt::PreciseTimer);
|
||||
connect(m_resize_debounce_timer, &QTimer::timeout, this, &DisplaySurface::onResizeDebounceTimer);
|
||||
}
|
||||
|
||||
DisplayWidget::~DisplayWidget()
|
||||
DisplaySurface::~DisplaySurface()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (m_clip_mouse_enabled)
|
||||
@@ -50,19 +47,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 +67,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 +99,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 +127,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 +147,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 +183,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 +211,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 +228,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 +241,28 @@ bool DisplayWidget::event(QEvent* event)
|
||||
InputManager::InvokeEvents(InputManager::MakeHostKeyboardKey(key), static_cast<float>(pressed));
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
default:
|
||||
pxAssert(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void DisplaySurface::onResizeDebounceTimer()
|
||||
{
|
||||
emit windowResizedEvent(m_pending_window_width, m_pending_window_height, m_pending_window_scale);
|
||||
}
|
||||
|
||||
bool DisplaySurface::event(QEvent* event)
|
||||
{
|
||||
switch (event->type())
|
||||
{
|
||||
case QEvent::KeyPress:
|
||||
case QEvent::KeyRelease:
|
||||
{
|
||||
handleKeyInputEvent(event);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -268,7 +272,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,125 +356,86 @@ 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)
|
||||
{
|
||||
m_pending_window_width = scaled_width;
|
||||
m_pending_window_height = scaled_height;
|
||||
m_pending_window_scale = dpr;
|
||||
|
||||
m_last_window_width = scaled_width;
|
||||
m_last_window_height = scaled_height;
|
||||
m_last_window_scale = dpr;
|
||||
emit windowResizedEvent(scaled_width, scaled_height, dpr);
|
||||
// qt spams resize events, sometimes several time per ms.
|
||||
// since a vulkan resize swap chain event takes between 15 to 25ms this is,
|
||||
// need less to say, unwanted.
|
||||
m_resize_debounce_timer->start(100);
|
||||
}
|
||||
|
||||
updateCenterPos();
|
||||
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,48 @@
|
||||
|
||||
#pragma once
|
||||
#include "common/WindowInfo.h"
|
||||
#include <QtWidgets/QStackedWidget>
|
||||
#include <QtWidgets/QWidget>
|
||||
#include <QtCore/QTimer>
|
||||
#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 Q_SLOTS:
|
||||
void onResizeDebounceTimer();
|
||||
|
||||
private:
|
||||
bool isActuallyFullscreen() const;
|
||||
@@ -49,34 +57,17 @@ 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
|
||||
QTimer* m_resize_debounce_timer = nullptr;
|
||||
u32 m_pending_window_width = 0;
|
||||
u32 m_pending_window_height = 0;
|
||||
float m_pending_window_scale = 1.0f;
|
||||
|
||||
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,60 +31,15 @@ static constexpr int SIZE_HINT_HEIGHT_TITLES = SIZE_HINT_HEIGHT + COVER_ART_SPAC
|
||||
|
||||
static constexpr int MIN_COVER_CACHE_SIZE = 256;
|
||||
|
||||
static int DPRScale(const int size, const qreal dpr)
|
||||
{
|
||||
return static_cast<int>(static_cast<qreal>(size) * dpr);
|
||||
}
|
||||
|
||||
static int DPRUnscale(const int size, const qreal dpr)
|
||||
{
|
||||
return static_cast<int>(static_cast<qreal>(size) / dpr);
|
||||
}
|
||||
|
||||
static void resizeAndPadPixmap(QPixmap* pm, const int expected_width, const int expected_height, const 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, const int width, const int height,
|
||||
const float scale, const qreal dpr, const std::string& title)
|
||||
static QPixmap createPlaceholderImage(const QPixmap& placeholder_pixmap, int width, int height, float scale,
|
||||
qreal dpr, const std::string& title)
|
||||
{
|
||||
QPixmap pm(placeholder_pixmap.copy());
|
||||
pm.setDevicePixelRatio(dpr);
|
||||
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))
|
||||
{
|
||||
@@ -199,7 +154,7 @@ void GameListModel::loadOrGenerateCover(const GameList::Entry* ge)
|
||||
else
|
||||
{
|
||||
image.setDevicePixelRatio(m_dpr);
|
||||
resizeAndPadPixmap(&image, getCoverArtWidth(), getCoverArtHeight(), m_dpr);
|
||||
QtUtils::resizeAndScalePixmap(&image, getCoverArtWidth(), getCoverArtHeight(), m_dpr, QtUtils::ScalingMode::Fit, 100);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -233,7 +256,6 @@ 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->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
m_table_view->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||
m_table_view->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
@@ -241,8 +263,9 @@ void GameListWidget::initialize()
|
||||
m_table_view->setMouseTracking(true);
|
||||
m_table_view->setShowGrid(false);
|
||||
m_table_view->setCurrentIndex(QModelIndex());
|
||||
m_table_view->horizontalHeader()->setHighlightSections(false);
|
||||
m_table_view->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
m_table_view->horizontalHeader()->setHighlightSections(false);
|
||||
m_table_view->horizontalHeader()->setSectionsMovable(true);
|
||||
m_table_view->verticalHeader()->hide();
|
||||
m_table_view->setVerticalScrollMode(QAbstractItemView::ScrollMode::ScrollPerPixel);
|
||||
|
||||
@@ -251,18 +274,28 @@ void GameListWidget::initialize()
|
||||
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);
|
||||
|
||||
@@ -310,84 +343,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;
|
||||
@@ -398,7 +377,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());
|
||||
@@ -406,36 +385,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
|
||||
@@ -554,18 +557,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();
|
||||
});
|
||||
}
|
||||
@@ -708,7 +717,7 @@ void GameListWidget::resizeEvent(QResizeEvent* event)
|
||||
QWidget::resizeEvent(event);
|
||||
resizeTableViewColumnsToFit();
|
||||
m_model->updateCacheSize(width(), height());
|
||||
setCustomBackground();
|
||||
processBackgroundFrames();
|
||||
}
|
||||
|
||||
bool GameListWidget::event(QEvent* event)
|
||||
@@ -726,123 +735,141 @@ bool GameListWidget::event(QEvent* event)
|
||||
void GameListWidget::resizeTableViewColumnsToFit()
|
||||
{
|
||||
QtUtils::ResizeColumnsForTableView(m_table_view, {
|
||||
55, // type
|
||||
85, // code
|
||||
-1, // title
|
||||
-1, // file title
|
||||
75, // crc
|
||||
95, // time played
|
||||
90, // 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_Title],
|
||||
DEFAULT_COLUMN_WIDTHS[GameListModel::Column_FileTitle],
|
||||
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"
|
||||
|
||||
@@ -49,15 +50,15 @@ public:
|
||||
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;
|
||||
};
|
||||
|
||||
@@ -86,16 +86,6 @@ const char* MainWindow::DISC_IMAGE_FILTER = QT_TRANSLATE_NOOP("MainWindow", "All
|
||||
|
||||
MainWindow* g_main_window = nullptr;
|
||||
|
||||
#if defined(_WIN32) || defined(__APPLE__)
|
||||
static const bool s_use_central_widget = false;
|
||||
#else
|
||||
// Qt Wayland is broken. Any sort of stacked widget usage fails to update,
|
||||
// leading to broken window resizes, no display rendering, etc. So, we mess
|
||||
// with the central widget instead. Which we can't do on xorg, because it
|
||||
// breaks window resizing there...
|
||||
static bool s_use_central_widget = false;
|
||||
#endif
|
||||
|
||||
// UI thread VM validity.
|
||||
static bool s_vm_valid = false;
|
||||
static bool s_vm_paused = false;
|
||||
@@ -113,19 +103,6 @@ MainWindow::MainWindow()
|
||||
{
|
||||
pxAssert(!g_main_window);
|
||||
g_main_window = this;
|
||||
|
||||
// Native window rendering is broken in wayland.
|
||||
// Let's work around it by disabling it for every widget besides
|
||||
// DisplayWidget.
|
||||
// Additionally, alien widget rendering is much more performant, so we
|
||||
// should have a nice responsiveness boost in our UI :)
|
||||
// QTBUG-133919, reported upstream by govanify
|
||||
QGuiApplication::setAttribute(Qt::AA_NativeWindows, false);
|
||||
QGuiApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings, true);
|
||||
|
||||
#if !defined(_WIN32) && !defined(__APPLE__)
|
||||
s_use_central_widget = DisplayContainer::isRunningOnWayland();
|
||||
#endif
|
||||
}
|
||||
|
||||
MainWindow::~MainWindow()
|
||||
@@ -192,7 +169,7 @@ static void makeIconsMasks(QWidget* menu)
|
||||
|
||||
QWidget* MainWindow::getContentParent()
|
||||
{
|
||||
return s_use_central_widget ? static_cast<QWidget*>(this) : static_cast<QWidget*>(m_ui.mainContainer);
|
||||
return static_cast<QWidget*>(m_ui.mainContainer);
|
||||
}
|
||||
|
||||
void MainWindow::setupAdditionalUi()
|
||||
@@ -219,15 +196,7 @@ void MainWindow::setupAdditionalUi()
|
||||
m_game_list_widget = new GameListWidget(getContentParent());
|
||||
m_game_list_widget->initialize();
|
||||
m_ui.actionGridViewShowTitles->setChecked(m_game_list_widget->getShowGridCoverTitles());
|
||||
if (s_use_central_widget)
|
||||
{
|
||||
m_ui.mainContainer = nullptr; // setCentralWidget() will delete this
|
||||
setCentralWidget(m_game_list_widget);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ui.mainContainer->addWidget(m_game_list_widget);
|
||||
}
|
||||
m_ui.mainContainer->addWidget(m_game_list_widget);
|
||||
|
||||
m_status_progress_widget = new QProgressBar(m_ui.statusBar);
|
||||
m_status_progress_widget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
|
||||
@@ -532,7 +501,7 @@ void MainWindow::recreate()
|
||||
if (was_display_created)
|
||||
{
|
||||
g_emu_thread->setSurfaceless(true);
|
||||
while (m_display_widget || !g_emu_thread->isSurfaceless())
|
||||
while (m_display_surface || !g_emu_thread->isSurfaceless())
|
||||
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 1);
|
||||
|
||||
m_display_created = false;
|
||||
@@ -1033,11 +1002,10 @@ void MainWindow::updateWindowTitle()
|
||||
if (windowTitle() != main_title)
|
||||
setWindowTitle(main_title);
|
||||
|
||||
if (m_display_widget && !isRenderingToMain())
|
||||
if (m_display_container && !isRenderingToMain())
|
||||
{
|
||||
QWidget* container = m_display_container ? static_cast<QWidget*>(m_display_container) : static_cast<QWidget*>(m_display_widget);
|
||||
if (container->windowTitle() != display_title)
|
||||
container->setWindowTitle(display_title);
|
||||
if (m_display_container->windowTitle() != display_title)
|
||||
m_display_container->setWindowTitle(display_title);
|
||||
}
|
||||
|
||||
if (g_log_window)
|
||||
@@ -1052,7 +1020,7 @@ void MainWindow::updateWindowState(bool force_visible)
|
||||
|
||||
const bool hide_window = !isRenderingToMain() && shouldHideMainWindow();
|
||||
const bool disable_resize = Host::GetBoolSettingValue("UI", "DisableWindowResize", false);
|
||||
const bool has_window = s_vm_valid || m_display_widget;
|
||||
const bool has_window = s_vm_valid || m_display_surface;
|
||||
|
||||
// Need to test both valid and display widget because of startup (vm invalid while window is created).
|
||||
const bool visible = force_visible || !hide_window || !has_window;
|
||||
@@ -1065,8 +1033,8 @@ void MainWindow::updateWindowState(bool force_visible)
|
||||
QtUtils::SetWindowResizeable(this, resizeable);
|
||||
|
||||
// Update the display widget too if rendering separately.
|
||||
if (m_display_widget && !isRenderingToMain())
|
||||
QtUtils::SetWindowResizeable(getDisplayContainer(), resizeable);
|
||||
if (m_display_surface && !isRenderingToMain())
|
||||
QtUtils::SetWindowResizeable(m_display_container, resizeable);
|
||||
}
|
||||
|
||||
void MainWindow::setProgressBar(int current, int total)
|
||||
@@ -1093,26 +1061,20 @@ void MainWindow::clearProgressBar()
|
||||
|
||||
bool MainWindow::isShowingGameList() const
|
||||
{
|
||||
if (s_use_central_widget)
|
||||
return (centralWidget() == m_game_list_widget);
|
||||
else
|
||||
return (m_ui.mainContainer->currentIndex() == 0);
|
||||
return (m_ui.mainContainer->currentIndex() == 0);
|
||||
}
|
||||
|
||||
bool MainWindow::isRenderingFullscreen() const
|
||||
{
|
||||
if (!MTGS::IsOpen() || !m_display_widget)
|
||||
if (!MTGS::IsOpen() || !m_display_surface)
|
||||
return false;
|
||||
|
||||
return getDisplayContainer()->isFullScreen();
|
||||
return m_display_container->isFullScreen();
|
||||
}
|
||||
|
||||
bool MainWindow::isRenderingToMain() const
|
||||
{
|
||||
if (s_use_central_widget)
|
||||
return (m_display_widget && centralWidget() == m_display_widget);
|
||||
else
|
||||
return (m_display_widget && m_ui.mainContainer->indexOf(m_display_widget) == 1);
|
||||
return (m_display_surface && m_ui.mainContainer->indexOf(m_display_container) == 1);
|
||||
}
|
||||
|
||||
bool MainWindow::shouldHideMouseCursor() const
|
||||
@@ -1137,14 +1099,14 @@ bool MainWindow::shouldMouseLock() const
|
||||
if (!Host::GetBoolSettingValue("EmuCore", "EnableMouseLock", false))
|
||||
return false;
|
||||
|
||||
if (m_display_created == false || m_display_widget == nullptr && !isRenderingToMain())
|
||||
if (m_display_created == false || m_display_surface == nullptr)
|
||||
return false;
|
||||
|
||||
bool windowsHidden = (!g_debugger_window || g_debugger_window->isHidden()) &&
|
||||
(!m_controller_settings_window || m_controller_settings_window->isHidden()) &&
|
||||
(!m_settings_window || m_settings_window->isHidden());
|
||||
|
||||
auto* displayWindow = isRenderingToMain() ? window() : m_display_widget->window();
|
||||
auto* displayWindow = isRenderingToMain() ? window() : m_display_container->window();
|
||||
|
||||
if (displayWindow == nullptr)
|
||||
return false;
|
||||
@@ -1194,7 +1156,7 @@ void MainWindow::switchToGameListView()
|
||||
|
||||
// switch to surfaceless. we have to wait until the display widget is gone before we swap over.
|
||||
g_emu_thread->setSurfaceless(true);
|
||||
while (m_display_widget)
|
||||
while (m_display_surface)
|
||||
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 1);
|
||||
}
|
||||
}
|
||||
@@ -1211,8 +1173,8 @@ void MainWindow::switchToEmulationView()
|
||||
if (s_vm_paused && !m_was_paused_on_surface_loss)
|
||||
g_emu_thread->setVMPaused(false);
|
||||
|
||||
if (m_display_widget)
|
||||
m_display_widget->setFocus();
|
||||
if (m_display_surface)
|
||||
m_display_container->setFocus();
|
||||
}
|
||||
|
||||
void MainWindow::refreshGameList(bool invalidate_cache, bool popup_on_error)
|
||||
@@ -1349,7 +1311,7 @@ void MainWindow::requestExit(bool allow_confirm)
|
||||
|
||||
void MainWindow::checkForSettingChanges()
|
||||
{
|
||||
if (m_display_widget)
|
||||
if (m_display_surface)
|
||||
updateDisplayWidgetCursor();
|
||||
|
||||
updateWindowState();
|
||||
@@ -1359,10 +1321,10 @@ void MainWindow::checkForSettingChanges()
|
||||
|
||||
std::optional<WindowInfo> MainWindow::getWindowInfo()
|
||||
{
|
||||
if (!m_display_widget || isRenderingToMain())
|
||||
return QtUtils::GetWindowInfoForWidget(this);
|
||||
else if (QWidget* widget = getDisplayContainer())
|
||||
return QtUtils::GetWindowInfoForWidget(widget);
|
||||
if (!m_display_surface || isRenderingToMain())
|
||||
return QtUtils::GetWindowInfoForWindow(this);
|
||||
else if (m_display_surface)
|
||||
return QtUtils::GetWindowInfoForWindow(m_display_surface);
|
||||
else
|
||||
return std::nullopt;
|
||||
}
|
||||
@@ -1381,9 +1343,8 @@ void MainWindow::onGameListRefreshComplete()
|
||||
|
||||
void MainWindow::onGameListSelectionChanged()
|
||||
{
|
||||
auto lock = GameList::GetLock();
|
||||
const GameList::Entry* entry = m_game_list_widget->getSelectedEntry();
|
||||
if (!entry)
|
||||
const std::optional<GameList::Entry> entry = m_game_list_widget->getSelectedEntry();
|
||||
if (!entry.has_value())
|
||||
return;
|
||||
|
||||
m_ui.statusBar->showMessage(QString::fromStdString(entry->path));
|
||||
@@ -1391,9 +1352,8 @@ void MainWindow::onGameListSelectionChanged()
|
||||
|
||||
void MainWindow::onGameListEntryActivated()
|
||||
{
|
||||
auto lock = GameList::GetLock();
|
||||
const GameList::Entry* entry = m_game_list_widget->getSelectedEntry();
|
||||
if (!entry)
|
||||
const std::optional<GameList::Entry> entry = m_game_list_widget->getSelectedEntry();
|
||||
if (!entry.has_value())
|
||||
return;
|
||||
|
||||
if (s_vm_valid)
|
||||
@@ -1421,24 +1381,23 @@ void MainWindow::onGameListEntryActivated()
|
||||
}
|
||||
|
||||
// only resume if the option is enabled, and we have one for this game
|
||||
startGameListEntry(entry, resume.value() ? std::optional<s32>(-1) : std::optional<s32>(), std::nullopt);
|
||||
startGameListEntry(*entry, resume.value() ? std::optional<s32>(-1) : std::optional<s32>(), std::nullopt);
|
||||
}
|
||||
|
||||
void MainWindow::onGameListEntryContextMenuRequested(const QPoint& point)
|
||||
{
|
||||
auto lock = GameList::GetLock();
|
||||
const GameList::Entry* entry = m_game_list_widget->getSelectedEntry();
|
||||
const std::optional<GameList::Entry> entry = m_game_list_widget->getSelectedEntry();
|
||||
|
||||
QMenu menu;
|
||||
|
||||
if (entry)
|
||||
if (entry.has_value())
|
||||
{
|
||||
QAction* action = menu.addAction(tr("Properties..."));
|
||||
action->setEnabled(!entry->serial.empty() || entry->type == GameList::EntryType::ELF);
|
||||
if (action->isEnabled())
|
||||
{
|
||||
connect(action, &QAction::triggered, [entry]() {
|
||||
SettingsWindow::openGamePropertiesDialog(entry,
|
||||
SettingsWindow::openGamePropertiesDialog(&*entry,
|
||||
entry->title, entry->serial, entry->crc, entry->type == GameList::EntryType::ELF, nullptr);
|
||||
});
|
||||
}
|
||||
@@ -1450,7 +1409,7 @@ void MainWindow::onGameListEntryContextMenuRequested(const QPoint& point)
|
||||
});
|
||||
|
||||
action = menu.addAction(tr("Set Cover Image..."));
|
||||
connect(action, &QAction::triggered, [this, entry]() { setGameListEntryCoverImage(entry); });
|
||||
connect(action, &QAction::triggered, [this, entry]() { setGameListEntryCoverImage(*entry); });
|
||||
|
||||
#if !defined(__APPLE__)
|
||||
connect(menu.addAction(tr("Create Game Shortcut...")), &QAction::triggered, [this]() { MainWindow::onCreateGameShortcutTriggered(); });
|
||||
@@ -1462,37 +1421,37 @@ void MainWindow::onGameListEntryContextMenuRequested(const QPoint& point)
|
||||
const time_t entry_played_time = GameList::GetCachedPlayedTimeForSerial(entry->serial);
|
||||
// Best two options given zero play time are to grey this out or to not show it at all.
|
||||
if (entry_played_time)
|
||||
connect(menu.addAction(tr("Reset Play Time")), &QAction::triggered, [this, entry, entry_played_time]() { clearGameListEntryPlayTime(entry, entry_played_time); });
|
||||
connect(menu.addAction(tr("Reset Play Time")), &QAction::triggered, [this, entry, entry_played_time]() { clearGameListEntryPlayTime(*entry, entry_played_time); });
|
||||
|
||||
// Check Wiki Page functionality is based on a serial redirect.
|
||||
if (!entry->serial.empty())
|
||||
connect(menu.addAction(tr("Check Wiki Page")), &QAction::triggered, [this, entry]() { goToWikiPage(entry); });
|
||||
connect(menu.addAction(tr("Check Wiki Page")), &QAction::triggered, [this, entry]() { goToWikiPage(*entry); });
|
||||
|
||||
action = menu.addAction(tr("Open Snapshots Folder"));
|
||||
connect(action, &QAction::triggered, [this, entry]() { openSnapshotsFolderForGame(entry); });
|
||||
connect(action, &QAction::triggered, [this, entry]() { openSnapshotsFolderForGame(*entry); });
|
||||
menu.addSeparator();
|
||||
|
||||
if (!s_vm_valid)
|
||||
{
|
||||
action = menu.addAction(tr("Default Boot"));
|
||||
connect(action, &QAction::triggered, [this, entry]() { startGameListEntry(entry); });
|
||||
connect(action, &QAction::triggered, [this, entry]() { startGameListEntry(*entry); });
|
||||
|
||||
// Make bold to indicate it's the default choice when double-clicking
|
||||
if (!VMManager::HasSaveStateInSlot(entry->serial.c_str(), entry->crc, -1))
|
||||
QtUtils::MarkActionAsDefault(action);
|
||||
|
||||
action = menu.addAction(tr("Fast Boot"));
|
||||
connect(action, &QAction::triggered, [this, entry]() { startGameListEntry(entry, std::nullopt, true); });
|
||||
connect(action, &QAction::triggered, [this, entry]() { startGameListEntry(*entry, std::nullopt, true); });
|
||||
|
||||
action = menu.addAction(tr("Full Boot"));
|
||||
connect(action, &QAction::triggered, [this, entry]() { startGameListEntry(entry, std::nullopt, false); });
|
||||
connect(action, &QAction::triggered, [this, entry]() { startGameListEntry(*entry, std::nullopt, false); });
|
||||
|
||||
if (m_ui.menuDebug->menuAction()->isVisible())
|
||||
{
|
||||
action = menu.addAction(tr("Boot and Debug"));
|
||||
connect(action, &QAction::triggered, [this, entry]() {
|
||||
DebugInterface::setPauseOnEntry(true);
|
||||
startGameListEntry(entry);
|
||||
startGameListEntry(*entry);
|
||||
DebuggerWindow::getInstance()->show();
|
||||
});
|
||||
}
|
||||
@@ -1598,7 +1557,7 @@ void MainWindow::onSaveStateMenuAboutToShow()
|
||||
|
||||
void MainWindow::onStartFullscreenUITriggered()
|
||||
{
|
||||
if (m_display_widget)
|
||||
if (m_display_surface)
|
||||
g_emu_thread->stopFullscreenUI();
|
||||
else
|
||||
g_emu_thread->startFullscreenUI(Host::GetBaseBoolSettingValue("UI", "StartFullscreen", false));
|
||||
@@ -1764,7 +1723,10 @@ void MainWindow::onToolsCoverDownloaderTriggered()
|
||||
#if !defined(__APPLE__)
|
||||
void MainWindow::onCreateGameShortcutTriggered()
|
||||
{
|
||||
const GameList::Entry* entry = m_game_list_widget->getSelectedEntry();
|
||||
const std::optional<GameList::Entry> entry = m_game_list_widget->getSelectedEntry();
|
||||
if (!entry.has_value())
|
||||
return;
|
||||
|
||||
const QString title = QString::fromStdString(entry->GetTitle());
|
||||
const QString path = QString::fromStdString(entry->path);
|
||||
VMLock lock(pauseAndLockVM());
|
||||
@@ -2033,7 +1995,7 @@ void MainWindow::onVMPaused()
|
||||
updateStatusBarWidgetVisibility();
|
||||
m_last_fps_status = m_status_verbose_widget->text();
|
||||
m_status_verbose_widget->setText(tr("Paused"));
|
||||
if (m_display_widget)
|
||||
if (m_display_surface)
|
||||
updateDisplayWidgetCursor();
|
||||
}
|
||||
|
||||
@@ -2055,10 +2017,10 @@ void MainWindow::onVMResumed()
|
||||
updateStatusBarWidgetVisibility();
|
||||
m_status_verbose_widget->setText(m_last_fps_status);
|
||||
m_last_fps_status = QString();
|
||||
if (m_display_widget)
|
||||
if (m_display_surface)
|
||||
{
|
||||
updateDisplayWidgetCursor();
|
||||
m_display_widget->setFocus();
|
||||
m_display_container->setFocus();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2090,7 +2052,7 @@ void MainWindow::onVMStopped()
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_display_widget)
|
||||
if (m_display_surface)
|
||||
updateDisplayWidgetCursor();
|
||||
else
|
||||
switchToGameListView();
|
||||
@@ -2360,22 +2322,17 @@ std::optional<WindowInfo> MainWindow::acquireRenderWindow(bool recreate_window,
|
||||
DevCon.WriteLn("acquireRenderWindow() recreate=%s fullscreen=%s render_to_main=%s surfaceless=%s", recreate_window ? "true" : "false",
|
||||
fullscreen ? "true" : "false", render_to_main ? "true" : "false", surfaceless ? "true" : "false");
|
||||
|
||||
QWidget* container = m_display_container ? static_cast<QWidget*>(m_display_container) : static_cast<QWidget*>(m_display_widget);
|
||||
const bool is_fullscreen = isRenderingFullscreen();
|
||||
const bool is_rendering_to_main = isRenderingToMain();
|
||||
const bool changing_surfaceless = (!m_display_widget != surfaceless);
|
||||
const bool changing_surfaceless = (!m_display_surface != surfaceless);
|
||||
if (m_display_created && !recreate_window && fullscreen == is_fullscreen && is_rendering_to_main == render_to_main &&
|
||||
!changing_surfaceless)
|
||||
{
|
||||
return m_display_widget ? m_display_widget->getWindowInfo() : WindowInfo();
|
||||
return m_display_surface ? m_display_surface->getWindowInfo() : WindowInfo();
|
||||
}
|
||||
|
||||
// Skip recreating the surface if we're just transitioning between fullscreen and windowed with render-to-main off.
|
||||
// .. except on Wayland, where everything tends to break if you don't recreate.
|
||||
const bool has_container = (m_display_container != nullptr);
|
||||
const bool needs_container = DisplayContainer::isNeeded(fullscreen, render_to_main);
|
||||
if (m_display_created && !recreate_window && !is_rendering_to_main && !render_to_main && has_container == needs_container &&
|
||||
!needs_container && !changing_surfaceless)
|
||||
if (m_display_created && !recreate_window && !is_rendering_to_main && !render_to_main && !changing_surfaceless)
|
||||
{
|
||||
DevCon.WriteLn("Toggling to %s without recreating surface", (fullscreen ? "fullscreen" : "windowed"));
|
||||
|
||||
@@ -2385,23 +2342,23 @@ std::optional<WindowInfo> MainWindow::acquireRenderWindow(bool recreate_window,
|
||||
|
||||
if (fullscreen)
|
||||
{
|
||||
container->showFullScreen();
|
||||
m_display_container->showFullScreen();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_is_temporarily_windowed && g_emu_thread->shouldRenderToMain())
|
||||
container->setGeometry(geometry());
|
||||
m_display_container->setGeometry(geometry());
|
||||
else
|
||||
restoreDisplayWindowGeometryFromConfig();
|
||||
container->showNormal();
|
||||
m_display_container->showNormal();
|
||||
}
|
||||
|
||||
updateDisplayWidgetCursor();
|
||||
m_display_widget->setFocus();
|
||||
m_display_container->setFocus();
|
||||
updateWindowState();
|
||||
|
||||
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
||||
return m_display_widget->getWindowInfo();
|
||||
return m_display_surface->getWindowInfo();
|
||||
}
|
||||
|
||||
destroyDisplayWidget(surfaceless);
|
||||
@@ -2420,7 +2377,7 @@ std::optional<WindowInfo> MainWindow::acquireRenderWindow(bool recreate_window,
|
||||
|
||||
createDisplayWidget(fullscreen, render_to_main);
|
||||
|
||||
std::optional<WindowInfo> wi = m_display_widget->getWindowInfo();
|
||||
std::optional<WindowInfo> wi = m_display_surface->getWindowInfo();
|
||||
if (!wi.has_value())
|
||||
{
|
||||
QMessageBox::critical(this, tr("Error"), tr("Failed to get window info from widget"));
|
||||
@@ -2428,14 +2385,13 @@ std::optional<WindowInfo> MainWindow::acquireRenderWindow(bool recreate_window,
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
g_emu_thread->connectDisplaySignals(m_display_widget);
|
||||
g_emu_thread->connectDisplaySignals(m_display_surface);
|
||||
|
||||
updateWindowTitle();
|
||||
updateWindowState();
|
||||
|
||||
updateDisplayWidgetCursor();
|
||||
m_display_widget->setFocus();
|
||||
|
||||
m_display_container->setFocus();
|
||||
return wi;
|
||||
}
|
||||
|
||||
@@ -2449,64 +2405,46 @@ void MainWindow::createDisplayWidget(bool fullscreen, bool render_to_main)
|
||||
QGuiApplication::sync();
|
||||
}
|
||||
|
||||
QWidget* container;
|
||||
if (DisplayContainer::isNeeded(fullscreen, render_to_main))
|
||||
m_display_surface = new DisplaySurface();
|
||||
if (fullscreen || !render_to_main)
|
||||
{
|
||||
m_display_container = new DisplayContainer();
|
||||
m_display_widget = new DisplayWidget(m_display_container);
|
||||
m_display_container->setDisplayWidget(m_display_widget);
|
||||
container = m_display_container;
|
||||
m_display_container = m_display_surface->createWindowContainer();
|
||||
m_display_container->setWindowTitle(windowTitle());
|
||||
m_display_container->setWindowIcon(windowIcon());
|
||||
}
|
||||
else
|
||||
{
|
||||
m_display_widget = new DisplayWidget((!fullscreen && render_to_main) ? getContentParent() : nullptr);
|
||||
container = m_display_widget;
|
||||
}
|
||||
|
||||
if (fullscreen || !render_to_main)
|
||||
{
|
||||
container->setWindowTitle(windowTitle());
|
||||
container->setWindowIcon(windowIcon());
|
||||
m_display_container = m_display_surface->createWindowContainer(getContentParent());
|
||||
}
|
||||
|
||||
if (fullscreen)
|
||||
{
|
||||
// Don't risk doing this on Wayland, it really doesn't like window state changes,
|
||||
// and positioning has no effect anyway.
|
||||
if (!s_use_central_widget)
|
||||
{
|
||||
if (isVisible() && g_emu_thread->shouldRenderToMain())
|
||||
container->move(pos());
|
||||
else
|
||||
restoreDisplayWindowGeometryFromConfig();
|
||||
}
|
||||
|
||||
container->showFullScreen();
|
||||
// On Wayland, while move/restoreGeometry can't position the window, it can influence which screen they show up on
|
||||
if (isVisible() && g_emu_thread->shouldRenderToMain())
|
||||
m_display_container->move(pos());
|
||||
else
|
||||
restoreDisplayWindowGeometryFromConfig();
|
||||
m_display_container->showFullScreen();
|
||||
}
|
||||
else if (!render_to_main)
|
||||
{
|
||||
if (m_is_temporarily_windowed && g_emu_thread->shouldRenderToMain())
|
||||
container->setGeometry(geometry());
|
||||
m_display_container->setGeometry(geometry());
|
||||
else
|
||||
restoreDisplayWindowGeometryFromConfig();
|
||||
container->showNormal();
|
||||
}
|
||||
else if (s_use_central_widget)
|
||||
{
|
||||
m_game_list_widget->setVisible(false);
|
||||
takeCentralWidget();
|
||||
m_game_list_widget->setParent(this); // takeCentralWidget() removes parent
|
||||
setCentralWidget(m_display_widget);
|
||||
m_display_widget->setFocus();
|
||||
update();
|
||||
m_display_container->showNormal();
|
||||
}
|
||||
else
|
||||
{
|
||||
pxAssertRel(m_ui.mainContainer->count() == 1, "Has no display widget");
|
||||
m_ui.mainContainer->addWidget(container);
|
||||
m_ui.mainContainer->addWidget(m_display_container);
|
||||
m_ui.mainContainer->setCurrentIndex(1);
|
||||
}
|
||||
|
||||
// Attatch drag and drop signals
|
||||
connect(m_display_surface, &DisplaySurface::dragEnterEvent, this, &MainWindow::dragEnterEvent);
|
||||
connect(m_display_surface, &DisplaySurface::dropEvent, this, &MainWindow::dropEvent);
|
||||
|
||||
updateDisplayRelatedActions(true, render_to_main, fullscreen);
|
||||
|
||||
// We need the surface visible.
|
||||
@@ -2515,7 +2453,7 @@ void MainWindow::createDisplayWidget(bool fullscreen, bool render_to_main)
|
||||
|
||||
void MainWindow::displayResizeRequested(qint32 width, qint32 height)
|
||||
{
|
||||
if (!m_display_widget)
|
||||
if (!m_display_surface)
|
||||
return;
|
||||
|
||||
// unapply the pixel scaling factor for hidpi
|
||||
@@ -2523,15 +2461,15 @@ void MainWindow::displayResizeRequested(qint32 width, qint32 height)
|
||||
width = static_cast<qint32>(std::max(static_cast<int>(std::lroundf(static_cast<float>(width) / dpr)), 1));
|
||||
height = static_cast<qint32>(std::max(static_cast<int>(std::lroundf(static_cast<float>(height) / dpr)), 1));
|
||||
|
||||
if (m_display_container || !m_display_widget->parent())
|
||||
if (!m_display_container->parent())
|
||||
{
|
||||
// no parent - rendering to separate window. easy.
|
||||
QtUtils::ResizePotentiallyFixedSizeWindow(getDisplayContainer(), width, height);
|
||||
QtUtils::ResizePotentiallyFixedSizeWindow(m_display_container, width, height);
|
||||
return;
|
||||
}
|
||||
|
||||
// we are rendering to the main window. we have to add in the extra height from the toolbar/status bar.
|
||||
const s32 extra_height = this->height() - m_display_widget->height();
|
||||
const s32 extra_height = this->height() - m_display_container->height();
|
||||
QtUtils::ResizePotentiallyFixedSizeWindow(this, width, height + extra_height);
|
||||
}
|
||||
|
||||
@@ -2542,7 +2480,7 @@ void MainWindow::mouseModeRequested(bool relative_mode, bool hide_cursor)
|
||||
|
||||
m_relative_mouse_mode = relative_mode;
|
||||
m_hide_mouse_cursor = hide_cursor;
|
||||
if (m_display_widget && !s_vm_paused)
|
||||
if (m_display_surface && !s_vm_paused)
|
||||
updateDisplayWidgetCursor();
|
||||
}
|
||||
|
||||
@@ -2558,73 +2496,46 @@ void MainWindow::releaseRenderWindow()
|
||||
|
||||
void MainWindow::destroyDisplayWidget(bool show_game_list)
|
||||
{
|
||||
if (!m_display_widget)
|
||||
if (!m_display_surface)
|
||||
return;
|
||||
|
||||
if (!isRenderingFullscreen() && !isRenderingToMain())
|
||||
saveDisplayWindowGeometryToConfig();
|
||||
|
||||
if (m_display_container)
|
||||
m_display_container->removeDisplayWidget();
|
||||
|
||||
if (isRenderingToMain())
|
||||
{
|
||||
if (s_use_central_widget)
|
||||
pxAssertRel(m_ui.mainContainer->indexOf(m_display_container) == 1, "Display widget in stack");
|
||||
m_ui.mainContainer->removeWidget(m_display_container);
|
||||
if (show_game_list)
|
||||
{
|
||||
pxAssertRel(centralWidget() == m_display_widget, "Display widget is currently central");
|
||||
takeCentralWidget();
|
||||
if (show_game_list)
|
||||
{
|
||||
m_game_list_widget->setVisible(true);
|
||||
setCentralWidget(m_game_list_widget);
|
||||
m_game_list_widget->resizeTableViewColumnsToFit();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
pxAssertRel(m_ui.mainContainer->indexOf(m_display_widget) == 1, "Display widget in stack");
|
||||
m_ui.mainContainer->removeWidget(m_display_widget);
|
||||
if (show_game_list)
|
||||
{
|
||||
m_ui.mainContainer->setCurrentIndex(0);
|
||||
m_game_list_widget->resizeTableViewColumnsToFit();
|
||||
}
|
||||
m_ui.mainContainer->setCurrentIndex(0);
|
||||
m_game_list_widget->resizeTableViewColumnsToFit();
|
||||
}
|
||||
}
|
||||
|
||||
if (m_display_widget)
|
||||
{
|
||||
m_display_widget->destroy();
|
||||
m_display_widget = nullptr;
|
||||
}
|
||||
|
||||
if (m_display_container)
|
||||
{
|
||||
m_display_container->deleteLater();
|
||||
m_display_container = nullptr;
|
||||
}
|
||||
// displau surface is always in a container
|
||||
pxAssert(m_display_container != nullptr);
|
||||
m_display_container->deleteLater();
|
||||
m_display_container = nullptr;
|
||||
// m_display_surface will be destroyed by the container's dtor
|
||||
m_display_surface = nullptr;
|
||||
|
||||
updateDisplayRelatedActions(false, false, false);
|
||||
}
|
||||
|
||||
void MainWindow::updateDisplayWidgetCursor()
|
||||
{
|
||||
pxAssertRel(m_display_widget, "Should have a display widget");
|
||||
m_display_widget->updateRelativeMode(s_vm_valid && !s_vm_paused && m_relative_mouse_mode);
|
||||
m_display_widget->updateCursor(s_vm_valid && !s_vm_paused && shouldHideMouseCursor());
|
||||
pxAssertRel(m_display_surface, "Should have a display widget");
|
||||
m_display_surface->updateRelativeMode(s_vm_valid && !s_vm_paused && m_relative_mouse_mode);
|
||||
m_display_surface->updateCursor(s_vm_valid && !s_vm_paused && shouldHideMouseCursor());
|
||||
}
|
||||
|
||||
void MainWindow::focusDisplayWidget()
|
||||
{
|
||||
if (!m_display_widget || centralWidget() != m_display_widget)
|
||||
if (!m_display_surface || centralWidget() != m_display_container)
|
||||
return;
|
||||
|
||||
m_display_widget->setFocus();
|
||||
}
|
||||
|
||||
QWidget* MainWindow::getDisplayContainer() const
|
||||
{
|
||||
return (m_display_container ? static_cast<QWidget*>(m_display_container) : static_cast<QWidget*>(m_display_widget));
|
||||
m_display_container->setFocus();
|
||||
}
|
||||
|
||||
void MainWindow::setupMouseMoveHandler()
|
||||
@@ -2653,18 +2564,29 @@ void MainWindow::checkMousePosition(int x, int y)
|
||||
// physical mouse position
|
||||
const QPoint physicalPos(x, y);
|
||||
|
||||
const auto* displayWindow = getDisplayContainer()->window();
|
||||
const auto* displayWindow = m_display_surface;
|
||||
|
||||
// logical (DIP) frame rect
|
||||
QRectF logicalBounds = displayWindow->geometry();
|
||||
const QSize logicalSize = displayWindow->size();
|
||||
const QPoint logicalPosition = displayWindow->position() + displayWindow->parent()->position();
|
||||
|
||||
// The offset to the origin of the current screen is in device-independent pixels while the origin itself is native!
|
||||
// The logicalPosition is the sum of these two values, so we need to separate them and only scale the offset
|
||||
const QScreen* screen = displayWindow->screen();
|
||||
|
||||
// If we fail to get the screen associated with the window, avoid mouse locking as it's probably in an unexpected position.
|
||||
if (!screen)
|
||||
return;
|
||||
|
||||
const QPoint screenPosition = screen->geometry().topLeft();
|
||||
|
||||
// physical frame rect
|
||||
const qreal scale = displayWindow->devicePixelRatioF();
|
||||
QRectF physicalBounds(
|
||||
logicalBounds.x() * scale,
|
||||
logicalBounds.y() * scale,
|
||||
logicalBounds.width() * scale,
|
||||
logicalBounds.height() * scale);
|
||||
const qreal scale = displayWindow->devicePixelRatio();
|
||||
const QRectF physicalBounds(
|
||||
screenPosition.x() + (logicalPosition.x() - screenPosition.x()) * scale,
|
||||
screenPosition.y() + (logicalPosition.y() - screenPosition.y()) * scale,
|
||||
logicalSize.width() * scale,
|
||||
logicalSize.height() * scale);
|
||||
|
||||
if (physicalBounds.contains(physicalPos))
|
||||
return;
|
||||
@@ -2677,14 +2599,13 @@ void MainWindow::checkMousePosition(int x, int y)
|
||||
|
||||
void MainWindow::saveDisplayWindowGeometryToConfig()
|
||||
{
|
||||
QWidget* container = getDisplayContainer();
|
||||
if (container->windowState() & Qt::WindowFullScreen)
|
||||
if (m_display_container->windowState() & Qt::WindowFullScreen)
|
||||
{
|
||||
// if we somehow ended up here, don't save the fullscreen state to the config
|
||||
return;
|
||||
}
|
||||
|
||||
const QByteArray geometry = getDisplayContainer()->saveGeometry();
|
||||
const QByteArray geometry = m_display_container->saveGeometry();
|
||||
const QByteArray geometry_b64 = geometry.toBase64();
|
||||
const std::string old_geometry_b64 = Host::GetBaseStringSettingValue("UI", "DisplayWindowGeometry");
|
||||
if (old_geometry_b64 != geometry_b64.constData())
|
||||
@@ -2698,18 +2619,17 @@ void MainWindow::restoreDisplayWindowGeometryFromConfig()
|
||||
{
|
||||
const std::string geometry_b64 = Host::GetBaseStringSettingValue("UI", "DisplayWindowGeometry");
|
||||
const QByteArray geometry = QByteArray::fromBase64(QByteArray::fromStdString(geometry_b64));
|
||||
QWidget* container = getDisplayContainer();
|
||||
if (!geometry.isEmpty())
|
||||
{
|
||||
container->restoreGeometry(geometry);
|
||||
m_display_container->restoreGeometry(geometry);
|
||||
|
||||
// make sure we're not loading a dodgy config which had fullscreen set...
|
||||
container->setWindowState(container->windowState() & ~(Qt::WindowFullScreen | Qt::WindowActive));
|
||||
m_display_container->setWindowState(m_display_container->windowState() & ~(Qt::WindowFullScreen | Qt::WindowActive));
|
||||
}
|
||||
else
|
||||
{
|
||||
// default size
|
||||
container->resize(640, 480);
|
||||
m_display_container->resize(640, 480);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2720,7 +2640,7 @@ SettingsWindow* MainWindow::getSettingsWindow()
|
||||
m_settings_window = new SettingsWindow();
|
||||
connect(m_settings_window->getInterfaceSettingsWidget(), &InterfaceSettingsWidget::themeChanged, this, &MainWindow::onThemeChanged);
|
||||
connect(m_settings_window->getInterfaceSettingsWidget(), &InterfaceSettingsWidget::languageChanged, this, &MainWindow::onLanguageChanged);
|
||||
connect(m_settings_window->getInterfaceSettingsWidget(), &InterfaceSettingsWidget::backgroundChanged, m_game_list_widget, [this] { m_game_list_widget->setCustomBackground(true); });
|
||||
connect(m_settings_window->getInterfaceSettingsWidget(), &InterfaceSettingsWidget::backgroundChanged, m_game_list_widget, [this] { m_game_list_widget->setCustomBackground(); });
|
||||
connect(m_settings_window->getGameListSettingsWidget(), &GameListSettingsWidget::preferEnglishGameListChanged, this, [] {
|
||||
g_main_window->m_game_list_widget->refreshGridCovers();
|
||||
});
|
||||
@@ -2755,13 +2675,21 @@ void MainWindow::doGameSettings(const char* category)
|
||||
// prefer to use a game list entry, if we have one, that way the summary is populated
|
||||
if (!s_current_disc_path.isEmpty() || !s_current_elf_override.isEmpty())
|
||||
{
|
||||
auto lock = GameList::GetLock();
|
||||
const QString& path = (s_current_elf_override.isEmpty() ? s_current_disc_path : s_current_elf_override);
|
||||
const GameList::Entry* entry = GameList::GetEntryForPath(path.toUtf8().constData());
|
||||
if (entry)
|
||||
|
||||
std::optional<GameList::Entry> entry;
|
||||
|
||||
{
|
||||
auto lock = GameList::GetLock();
|
||||
const GameList::Entry* entry_ptr = GameList::GetEntryForPath(path.toUtf8().constData());
|
||||
if (entry_ptr)
|
||||
entry = *entry_ptr;
|
||||
}
|
||||
|
||||
if (entry.has_value())
|
||||
{
|
||||
SettingsWindow::openGamePropertiesDialog(
|
||||
entry, entry->title, entry->serial, entry->crc, !s_current_elf_override.isEmpty(), category);
|
||||
&*entry, entry->title, entry->serial, entry->crc, !s_current_elf_override.isEmpty(), category);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -2851,16 +2779,16 @@ QString MainWindow::getDiscDevicePath(const QString& title)
|
||||
return ret;
|
||||
}
|
||||
|
||||
void MainWindow::startGameListEntry(const GameList::Entry* entry, std::optional<s32> save_slot, std::optional<bool> fast_boot, bool load_backup)
|
||||
void MainWindow::startGameListEntry(const GameList::Entry& entry, std::optional<s32> save_slot, std::optional<bool> fast_boot, bool load_backup)
|
||||
{
|
||||
std::shared_ptr<VMBootParameters> params = std::make_shared<VMBootParameters>();
|
||||
params->fast_boot = fast_boot;
|
||||
|
||||
GameList::FillBootParametersForEntry(params.get(), entry);
|
||||
GameList::FillBootParametersForEntry(params.get(), &entry);
|
||||
|
||||
if (save_slot.has_value() && !entry->serial.empty())
|
||||
if (save_slot.has_value() && !entry.serial.empty())
|
||||
{
|
||||
std::string state_filename = VMManager::GetSaveStateFileName(entry->serial.c_str(), entry->crc, save_slot.value(), load_backup);
|
||||
std::string state_filename = VMManager::GetSaveStateFileName(entry.serial.c_str(), entry.crc, save_slot.value(), load_backup);
|
||||
if (!FileSystem::FileExists(state_filename.c_str()))
|
||||
{
|
||||
QMessageBox::critical(this, tr("Error"), tr("This save state does not exist."));
|
||||
@@ -2873,15 +2801,15 @@ void MainWindow::startGameListEntry(const GameList::Entry* entry, std::optional<
|
||||
g_emu_thread->startVM(std::move(params));
|
||||
}
|
||||
|
||||
void MainWindow::setGameListEntryCoverImage(const GameList::Entry* entry)
|
||||
void MainWindow::setGameListEntryCoverImage(const GameList::Entry& entry)
|
||||
{
|
||||
const QString filename = QDir::toNativeSeparators(
|
||||
QFileDialog::getOpenFileName(this, tr("Select Cover Image"), QString(), tr("All Cover Image Types (*.jpg *.jpeg *.png *.webp)")));
|
||||
if (filename.isEmpty())
|
||||
return;
|
||||
|
||||
const QString old_filename = QString::fromStdString(GameList::GetCoverImagePathForEntry(entry));
|
||||
const QString new_filename = QString::fromStdString(GameList::GetNewCoverImagePathForEntry(entry, filename.toUtf8().constData(), true));
|
||||
const QString old_filename = QString::fromStdString(GameList::GetCoverImagePathForEntry(&entry));
|
||||
const QString new_filename = QString::fromStdString(GameList::GetNewCoverImagePathForEntry(&entry, filename.toUtf8().constData(), true));
|
||||
if (new_filename.isEmpty())
|
||||
return;
|
||||
|
||||
@@ -2922,31 +2850,31 @@ void MainWindow::setGameListEntryCoverImage(const GameList::Entry* entry)
|
||||
m_game_list_widget->refreshGridCovers();
|
||||
}
|
||||
|
||||
void MainWindow::clearGameListEntryPlayTime(const GameList::Entry* entry, const time_t entry_played_time)
|
||||
void MainWindow::clearGameListEntryPlayTime(const GameList::Entry& entry, const time_t entry_played_time)
|
||||
{
|
||||
if (QMessageBox::question(this, tr("Confirm Reset"),
|
||||
tr("Are you sure you want to reset the play time for '%1' (%2)?\n\nYour current play time is %3.\n\nThis action cannot be undone.")
|
||||
.arg(entry->title.empty() ? tr("empty title") : QString::fromStdString(entry->title),
|
||||
entry->serial.empty() ? tr("no serial") : QString::fromStdString(entry->serial),
|
||||
.arg(entry.title.empty() ? tr("empty title") : QString::fromStdString(entry.title),
|
||||
entry.serial.empty() ? tr("no serial") : QString::fromStdString(entry.serial),
|
||||
QString::fromStdString(GameList::FormatTimespan(entry_played_time, true))),
|
||||
(QMessageBox::Yes | QMessageBox::No), QMessageBox::No) == QMessageBox::Yes)
|
||||
{
|
||||
GameList::ClearPlayedTimeForSerial(entry->serial);
|
||||
GameList::ClearPlayedTimeForSerial(entry.serial);
|
||||
m_game_list_widget->refresh(false, false);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::goToWikiPage(const GameList::Entry* entry)
|
||||
void MainWindow::goToWikiPage(const GameList::Entry& entry)
|
||||
{
|
||||
QtUtils::OpenURL(this, fmt::format("https://wiki.pcsx2.net/{}", entry->serial).c_str());
|
||||
QtUtils::OpenURL(this, fmt::format("https://wiki.pcsx2.net/{}", entry.serial).c_str());
|
||||
}
|
||||
|
||||
void MainWindow::openSnapshotsFolderForGame(const GameList::Entry* entry)
|
||||
void MainWindow::openSnapshotsFolderForGame(const GameList::Entry& entry)
|
||||
{
|
||||
// Go to top-level snapshots directory if not organizing by game.
|
||||
if (EmuConfig.GS.OrganizeSnapshotsByGame && entry && !entry->title.empty())
|
||||
if (EmuConfig.GS.OrganizeSnapshotsByGame && !entry.title.empty())
|
||||
{
|
||||
std::string game_name = entry->title;
|
||||
std::string game_name = entry.title;
|
||||
Path::SanitizeFileName(&game_name);
|
||||
|
||||
const std::string game_dir = Path::Combine(EmuFolders::Snapshots, game_name);
|
||||
@@ -3021,11 +2949,11 @@ void MainWindow::loadSaveStateSlot(s32 slot, bool load_backup)
|
||||
else
|
||||
{
|
||||
// we're not currently running, therefore we must've right clicked in the game list
|
||||
const GameList::Entry* entry = m_game_list_widget->getSelectedEntry();
|
||||
if (!entry)
|
||||
const std::optional<GameList::Entry> entry = m_game_list_widget->getSelectedEntry();
|
||||
if (!entry.has_value())
|
||||
return;
|
||||
|
||||
startGameListEntry(entry, slot, std::nullopt, load_backup);
|
||||
startGameListEntry(*entry, slot, std::nullopt, load_backup);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3251,7 +3179,7 @@ MainWindow::VMLock MainWindow::pauseAndLockVM()
|
||||
// period when there's no window, and Qt might shut us down. So avoid it there.
|
||||
// On MacOS, it forces a workspace switch, which is kinda jarring.
|
||||
#ifndef __APPLE__
|
||||
const bool was_fullscreen = g_emu_thread->isFullscreen() && !s_use_central_widget;
|
||||
const bool was_fullscreen = g_emu_thread->isFullscreen();
|
||||
#else
|
||||
const bool was_fullscreen = false;
|
||||
#endif
|
||||
@@ -3270,25 +3198,20 @@ MainWindow::VMLock MainWindow::pauseAndLockVM()
|
||||
|
||||
g_emu_thread->setFullscreen(false, false);
|
||||
|
||||
// Container could change... thanks Wayland.
|
||||
QWidget* container;
|
||||
while (QtHost::IsVMValid() && (g_emu_thread->isFullscreen() ||
|
||||
!(container = getDisplayContainer()) || container->isFullScreen()))
|
||||
// Process events untill both EmuThread and Qt have finished exiting fullscreen
|
||||
while (QtHost::IsVMValid() && (g_emu_thread->isFullscreen() || m_display_container->isFullScreen()))
|
||||
{
|
||||
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
||||
}
|
||||
}
|
||||
|
||||
// Now we'll either have a borderless window, or a regular window (if we were exclusive fullscreen).
|
||||
QWidget* dialog_parent = getDisplayContainer();
|
||||
|
||||
// Ensure main window is visible.
|
||||
if (!g_main_window->isVisible())
|
||||
g_main_window->show();
|
||||
g_main_window->raise();
|
||||
g_main_window->activateWindow();
|
||||
|
||||
return VMLock(dialog_parent, was_paused, was_fullscreen);
|
||||
return VMLock(m_display_container, was_paused, was_fullscreen);
|
||||
}
|
||||
|
||||
void MainWindow::rescanFile(const std::string& path)
|
||||
@@ -3298,23 +3221,30 @@ void MainWindow::rescanFile(const std::string& path)
|
||||
|
||||
MainWindow::VMLock::VMLock(QWidget* dialog_parent, bool was_paused, bool was_fullscreen)
|
||||
: m_dialog_parent(dialog_parent)
|
||||
, m_has_lock(true)
|
||||
, m_was_paused(was_paused)
|
||||
, m_was_fullscreen(was_fullscreen)
|
||||
{
|
||||
QtHost::LockVMWithDialog();
|
||||
}
|
||||
|
||||
MainWindow::VMLock::VMLock(VMLock&& lock)
|
||||
: m_dialog_parent(lock.m_dialog_parent)
|
||||
, m_has_lock(lock.m_has_lock)
|
||||
, m_was_paused(lock.m_was_paused)
|
||||
, m_was_fullscreen(lock.m_was_fullscreen)
|
||||
{
|
||||
lock.m_dialog_parent = nullptr;
|
||||
lock.m_has_lock = false;
|
||||
lock.m_was_paused = true;
|
||||
lock.m_was_fullscreen = false;
|
||||
}
|
||||
|
||||
MainWindow::VMLock::~VMLock()
|
||||
{
|
||||
if (m_has_lock)
|
||||
QtHost::UnlockVMWithDialog();
|
||||
|
||||
if (m_was_fullscreen)
|
||||
{
|
||||
g_main_window->m_is_temporarily_windowed = false;
|
||||
@@ -3327,10 +3257,15 @@ MainWindow::VMLock::~VMLock()
|
||||
|
||||
MainWindow::VMLock& MainWindow::VMLock::operator=(VMLock&& lock)
|
||||
{
|
||||
if (m_has_lock)
|
||||
QtHost::UnlockVMWithDialog();
|
||||
|
||||
m_has_lock = lock.m_has_lock;
|
||||
m_dialog_parent = lock.m_dialog_parent;
|
||||
m_was_paused = lock.m_was_paused;
|
||||
m_was_fullscreen = lock.m_was_fullscreen;
|
||||
|
||||
lock.m_has_lock = false;
|
||||
lock.m_dialog_parent = nullptr;
|
||||
lock.m_was_paused = true;
|
||||
lock.m_was_fullscreen = false;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -257,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);
|
||||
@@ -274,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, const time_t entry_played_time);
|
||||
void goToWikiPage(const GameList::Entry* entry);
|
||||
void openSnapshotsFolderForGame(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);
|
||||
@@ -291,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;
|
||||
|
||||
@@ -855,7 +855,7 @@
|
||||
</action>
|
||||
<action name="actionDebugger">
|
||||
<property name="icon">
|
||||
<iconset theme="heart-circle-line"/>
|
||||
<iconset theme="bug-line"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Open Debugger</string>
|
||||
|
||||
@@ -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
|
||||
@@ -317,7 +318,9 @@ void EmuThread::loadState(const QString& filename)
|
||||
if (!VMManager::HasValidVM())
|
||||
return;
|
||||
|
||||
VMManager::LoadState(filename.toUtf8().constData());
|
||||
Error error;
|
||||
if (!VMManager::LoadState(filename.toUtf8().constData(), &error))
|
||||
Host::ReportErrorAsync(TRANSLATE_SV("QtHost", "Failed to Load State"), error.GetDescription());
|
||||
}
|
||||
|
||||
void EmuThread::loadStateFromSlot(qint32 slot, bool load_backup)
|
||||
@@ -331,7 +334,9 @@ void EmuThread::loadStateFromSlot(qint32 slot, bool load_backup)
|
||||
if (!VMManager::HasValidVM())
|
||||
return;
|
||||
|
||||
VMManager::LoadStateFromSlot(slot, load_backup);
|
||||
Error error;
|
||||
if (!VMManager::LoadStateFromSlot(slot, load_backup, &error))
|
||||
Host::ReportErrorAsync(TRANSLATE_SV("QtHost", "Failed to Load State"), error.GetDescription());
|
||||
}
|
||||
|
||||
void EmuThread::saveState(const QString& filename)
|
||||
@@ -495,6 +500,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 +774,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 +969,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;
|
||||
@@ -1713,6 +1726,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 +2191,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 +2206,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 +2267,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.") :
|
||||
@@ -2398,8 +2421,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()
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
@@ -366,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();
|
||||
|
||||
@@ -114,22 +114,22 @@
|
||||
<property name="spacing">
|
||||
<number>7</number>
|
||||
</property>
|
||||
<item row="8" column="0">
|
||||
<item row="9" column="0">
|
||||
<widget class="QCheckBox" name="showFrameTimes">
|
||||
<property name="text">
|
||||
<string>Show Frame Times</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="2">
|
||||
<item row="7" column="2">
|
||||
<widget class="QLabel" name="warningLabel">
|
||||
<property name="text">
|
||||
<string>Warnings For User</string>
|
||||
<string>Messages</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="0">
|
||||
<widget class="QCheckBox" name="showCPU">
|
||||
<item row="6" column="0">
|
||||
<widget class="QCheckBox" name="showUsageCPU">
|
||||
<property name="text">
|
||||
<string>Show CPU Usage</string>
|
||||
</property>
|
||||
@@ -142,14 +142,21 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="2">
|
||||
<item row="5" column="2">
|
||||
<widget class="QCheckBox" name="showInputRec">
|
||||
<property name="text">
|
||||
<string>Show Input Recording Status</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<item row="6" column="2">
|
||||
<widget class="QCheckBox" name="showTextureReplacements">
|
||||
<property name="text">
|
||||
<string>Show Texture Replacement Status</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QCheckBox" name="showResolution">
|
||||
<property name="text">
|
||||
<string>Show Resolution</string>
|
||||
@@ -163,22 +170,22 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<item row="3" column="0">
|
||||
<widget class="QCheckBox" name="showVPS">
|
||||
<property name="text">
|
||||
<string>Show VPS</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="0">
|
||||
<widget class="QCheckBox" name="showGPU">
|
||||
<item row="7" column="0">
|
||||
<widget class="QCheckBox" name="showUsageGPU">
|
||||
<property name="text">
|
||||
<string>Show GPU Usage</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="showSpeed">
|
||||
<widget class="QCheckBox" name="showSpeedPercentages">
|
||||
<property name="text">
|
||||
<string>Show Speed Percentages</string>
|
||||
</property>
|
||||
@@ -187,46 +194,46 @@
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="Column0_PerformanceAndStats">
|
||||
<property name="text">
|
||||
<string>Performance & Stats</string>
|
||||
<string>Performance Stats</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<item row="1" column="2">
|
||||
<widget class="QCheckBox" name="showSettings">
|
||||
<property name="text">
|
||||
<string>Show Settings</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<item row="5" column="0">
|
||||
<widget class="QCheckBox" name="showGSStats">
|
||||
<property name="text">
|
||||
<string>Show Statistics</string>
|
||||
<string>Show GS Statistics</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QCheckBox" name="showIndicators">
|
||||
<item row="8" column="0">
|
||||
<widget class="QCheckBox" name="showStatusIndicators">
|
||||
<property name="text">
|
||||
<string>Show Indicators</string>
|
||||
<string>Show Status Indicators</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="2">
|
||||
<item row="4" column="2">
|
||||
<widget class="QCheckBox" name="showVideoCapture">
|
||||
<property name="text">
|
||||
<string>Show Video Capture Status</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="2">
|
||||
<item row="3" column="2">
|
||||
<widget class="QCheckBox" name="showInputs">
|
||||
<property name="text">
|
||||
<string>Show Inputs</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="2">
|
||||
<item row="8" column="2">
|
||||
<widget class="QCheckBox" name="warnAboutUnsafeSettings">
|
||||
<property name="text">
|
||||
<string>Warn About Unsafe Settings</string>
|
||||
@@ -254,7 +261,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="2">
|
||||
<item row="2" column="2">
|
||||
<widget class="QCheckBox" name="showPatches">
|
||||
<property name="text">
|
||||
<string>Show Patches</string>
|
||||
@@ -375,22 +382,23 @@
|
||||
<tabstop>scale</tabstop>
|
||||
<tabstop>messagesPos</tabstop>
|
||||
<tabstop>performancePos</tabstop>
|
||||
<tabstop>showSpeed</tabstop>
|
||||
<tabstop>showSpeedPercentages</tabstop>
|
||||
<tabstop>showFPS</tabstop>
|
||||
<tabstop>showVPS</tabstop>
|
||||
<tabstop>showResolution</tabstop>
|
||||
<tabstop>showGSStats</tabstop>
|
||||
<tabstop>showUsageCPU</tabstop>
|
||||
<tabstop>showUsageGPU</tabstop>
|
||||
<tabstop>showStatusIndicators</tabstop>
|
||||
<tabstop>showFrameTimes</tabstop>
|
||||
<tabstop>showCPU</tabstop>
|
||||
<tabstop>showGPU</tabstop>
|
||||
<tabstop>showHardwareInfo</tabstop>
|
||||
<tabstop>showVersion</tabstop>
|
||||
<tabstop>showIndicators</tabstop>
|
||||
<tabstop>showSettings</tabstop>
|
||||
<tabstop>showPatches</tabstop>
|
||||
<tabstop>showInputs</tabstop>
|
||||
<tabstop>showVideoCapture</tabstop>
|
||||
<tabstop>showInputRec</tabstop>
|
||||
<tabstop>showTextureReplacements</tabstop>
|
||||
<tabstop>warnAboutUnsafeSettings</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
|
||||
@@ -225,22 +225,23 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* settings_dialog,
|
||||
SettingWidgetBinder::BindWidgetToFloatSetting(sif, m_osd.scale, "EmuCore/GS", "OsdScale", 100.0f);
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_osd.messagesPos, "EmuCore/GS", "OsdMessagesPos", static_cast<int>(OsdOverlayPos::TopLeft));
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_osd.performancePos, "EmuCore/GS", "OsdPerformancePos", static_cast<int>(OsdOverlayPos::TopRight));
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_osd.showSpeed, "EmuCore/GS", "OsdShowSpeed", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_osd.showSpeedPercentages, "EmuCore/GS", "OsdShowSpeed", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_osd.showFPS, "EmuCore/GS", "OsdShowFPS", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_osd.showVPS, "EmuCore/GS", "OsdShowVPS", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_osd.showCPU, "EmuCore/GS", "OsdShowCPU", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_osd.showGPU, "EmuCore/GS", "OsdShowGPU", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_osd.showResolution, "EmuCore/GS", "OsdShowResolution", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_osd.showGSStats, "EmuCore/GS", "OsdShowGSStats", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_osd.showIndicators, "EmuCore/GS", "OsdShowIndicators", true);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_osd.showUsageCPU, "EmuCore/GS", "OsdShowCPU", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_osd.showUsageGPU, "EmuCore/GS", "OsdShowGPU", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_osd.showStatusIndicators, "EmuCore/GS", "OsdShowIndicators", true);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_osd.showFrameTimes, "EmuCore/GS", "OsdShowFrameTimes", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_osd.showHardwareInfo, "EmuCore/GS", "OsdShowHardwareInfo", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_osd.showVersion, "EmuCore/GS", "OsdShowVersion", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_osd.showSettings, "EmuCore/GS", "OsdShowSettings", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_osd.showPatches, "EmuCore/GS", "OsdshowPatches", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_osd.showInputs, "EmuCore/GS", "OsdShowInputs", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_osd.showFrameTimes, "EmuCore/GS", "OsdShowFrameTimes", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_osd.showVersion, "EmuCore/GS", "OsdShowVersion", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_osd.showHardwareInfo, "EmuCore/GS", "OsdShowHardwareInfo", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_osd.showVideoCapture, "EmuCore/GS", "OsdShowVideoCapture", true);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_osd.showInputRec, "EmuCore/GS", "OsdShowInputRec", true);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_osd.showTextureReplacements, "EmuCore/GS", "OsdShowTextureReplacements", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_osd.warnAboutUnsafeSettings, "EmuCore", "OsdWarnAboutUnsafeSettings", true);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
@@ -715,57 +716,62 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* settings_dialog,
|
||||
dialog()->registerWidgetHelp(m_osd.scale, tr("OSD Scale"), tr("100%"), tr("Scales the size of the onscreen OSD from 50% to 500%."));
|
||||
|
||||
dialog()->registerWidgetHelp(m_osd.messagesPos, tr("OSD Messages Position"), tr("Left (Default)"),
|
||||
tr("Shows on-screen-display messages when events occur such as save states being "
|
||||
tr("Position of on-screen-display messages when events occur such as save states being "
|
||||
"created/loaded, screenshots being taken, etc."));
|
||||
|
||||
dialog()->registerWidgetHelp(m_osd.performancePos, tr("OSD Statistics Position"), tr("Right (Default)"),
|
||||
tr("Shows a variety of on-screen performance data points as selected by the user."));
|
||||
dialog()->registerWidgetHelp(m_osd.performancePos, tr("OSD Performance Position"), tr("Right (Default)"),
|
||||
tr("Position of a variety of on-screen performance data points as selected by the user."));
|
||||
|
||||
dialog()->registerWidgetHelp(m_osd.showSpeedPercentages, tr("Show Speed Percentages"), tr("Unchecked"),
|
||||
tr("Shows the current emulation speed of the system as a percentage."));
|
||||
|
||||
dialog()->registerWidgetHelp(m_osd.showFPS, tr("Show FPS"), tr("Unchecked"),
|
||||
tr("Shows the internal frame rate of the game in the top-right corner of the display."));
|
||||
tr("Shows the number of internal video frames displayed per second by the system."));
|
||||
|
||||
dialog()->registerWidgetHelp(m_osd.showVPS, tr("Show VPS"), tr("Unchecked"),
|
||||
tr("Shows the vsync rate of the emulator in the top-right corner of the display."));
|
||||
|
||||
dialog()->registerWidgetHelp(m_osd.showSpeed, tr("Show Speed Percentages"), tr("Unchecked"),
|
||||
tr("Shows the current emulation speed of the system in the top-right corner of the display as a percentage."));
|
||||
tr("Shows the number of V-syncs performed per second by the system."));
|
||||
|
||||
dialog()->registerWidgetHelp(m_osd.showResolution, tr("Show Resolution"), tr("Unchecked"),
|
||||
tr("Shows the resolution of the game in the top-right corner of the display."));
|
||||
tr("Shows the internal resolution of the game."));
|
||||
|
||||
dialog()->registerWidgetHelp(m_osd.showCPU, tr("Show CPU Usage"), tr("Unchecked"), tr("Shows host's CPU utilization."));
|
||||
dialog()->registerWidgetHelp(m_osd.showGSStats, tr("Show GS Statistics"), tr("Unchecked"),
|
||||
tr("Shows statistics about the emulated GS such as primitives and draw calls."));
|
||||
|
||||
dialog()->registerWidgetHelp(m_osd.showGPU, tr("Show GPU Usage"), tr("Unchecked"), tr("Shows host's GPU utilization."));
|
||||
dialog()->registerWidgetHelp(m_osd.showUsageCPU, tr("Show CPU Usage"),
|
||||
tr("Unchecked"), tr("Shows the host's CPU utilization based on threads."));
|
||||
|
||||
dialog()->registerWidgetHelp(m_osd.showGSStats, tr("Show Statistics"), tr("Unchecked"),
|
||||
tr("Shows counters for internal graphical utilization, useful for debugging."));
|
||||
dialog()->registerWidgetHelp(m_osd.showUsageGPU, tr("Show GPU Usage"),
|
||||
tr("Unchecked"), tr("Shows the host's GPU utilization."));
|
||||
|
||||
dialog()->registerWidgetHelp(m_osd.showIndicators, tr("Show Indicators"), tr("Checked"),
|
||||
tr("Shows OSD icon indicators for emulation states such as Pausing, Turbo, Fast-Forward, and Slow-Motion."));
|
||||
|
||||
dialog()->registerWidgetHelp(m_osd.showSettings, tr("Show Settings"), tr("Unchecked"),
|
||||
tr("Displays various settings and the current values of those settings, useful for debugging."));
|
||||
|
||||
dialog()->registerWidgetHelp(m_osd.showPatches, tr("Show Patches"), tr("Unchecked"),
|
||||
tr("Shows the amount of currently active patches/cheats on the bottom-right corner of the display."));
|
||||
|
||||
dialog()->registerWidgetHelp(m_osd.showInputs, tr("Show Inputs"), tr("Unchecked"),
|
||||
tr("Shows the current controller state of the system in the bottom-left corner of the display."));
|
||||
dialog()->registerWidgetHelp(m_osd.showStatusIndicators, tr("Show Status Indicators"), tr("Checked"),
|
||||
tr("Shows icon indicators for emulation states such as Pausing, Turbo, Fast-Forward, and Slow-Motion."));
|
||||
|
||||
dialog()->registerWidgetHelp(m_osd.showFrameTimes, tr("Show Frame Times"), tr("Unchecked"),
|
||||
tr("Displays a graph showing the average frametimes."));
|
||||
|
||||
dialog()->registerWidgetHelp(m_osd.showHardwareInfo, tr("Show Hardware Info"), tr("Unchecked"),
|
||||
tr("Shows the current system CPU and GPU information."));
|
||||
|
||||
dialog()->registerWidgetHelp(m_osd.showVersion, tr("Show PCSX2 Version"), tr("Unchecked"),
|
||||
tr("Shows the current PCSX2 version on the top-right corner of the display."));
|
||||
tr("Shows the current PCSX2 version."));
|
||||
|
||||
dialog()->registerWidgetHelp(m_osd.showSettings, tr("Show Settings"), tr("Unchecked"),
|
||||
tr("Displays various settings and the current values of those settings in the bottom-right corner of the display."));
|
||||
|
||||
dialog()->registerWidgetHelp(m_osd.showPatches, tr("Show Patches"), tr("Unchecked"),
|
||||
tr("Shows the amount of currently active patches/cheats in the bottom-right corner of the display."));
|
||||
|
||||
dialog()->registerWidgetHelp(m_osd.showInputs, tr("Show Inputs"), tr("Unchecked"),
|
||||
tr("Shows the current controller state of the system in the bottom-left corner of the display."));
|
||||
|
||||
dialog()->registerWidgetHelp(m_osd.showVideoCapture, tr("Show Video Capture Status"), tr("Checked"),
|
||||
tr("Shows the currently active video capture status."));
|
||||
tr("Shows the status of the currently active video capture in the top-right corner of the display.."));
|
||||
|
||||
dialog()->registerWidgetHelp(m_osd.showInputRec, tr("Show Input Recording Status"), tr("Checked"),
|
||||
tr("Shows the currently active input recording status."));
|
||||
tr("Shows the status of the currently active input recording in the top-right corner of the display.."));
|
||||
|
||||
dialog()->registerWidgetHelp(m_osd.showHardwareInfo, tr("Show Hardware Info"), tr("Unchecked"),
|
||||
tr("Shows the current system hardware information on the OSD."));
|
||||
dialog()->registerWidgetHelp(m_osd.showTextureReplacements, tr("Show Texture Replacement Status"), tr("Checked"),
|
||||
tr("Shows the status of the number of dumped and loaded texture replacements in the top-right corner of the display."));
|
||||
|
||||
dialog()->registerWidgetHelp(m_osd.warnAboutUnsafeSettings, tr("Warn About Unsafe Settings"), tr("Checked"),
|
||||
tr("Displays warnings when settings are enabled which may break games."));
|
||||
@@ -943,16 +949,16 @@ void GraphicsSettingsWidget::onPerformancePosChanged()
|
||||
{
|
||||
const bool enabled = m_osd.performancePos->currentIndex() != (dialog()->isPerGameSettings() ? 1 : 0);
|
||||
|
||||
m_osd.showVPS->setEnabled(enabled);
|
||||
m_osd.showSpeed->setEnabled(enabled);
|
||||
m_osd.showSpeedPercentages->setEnabled(enabled);
|
||||
m_osd.showFPS->setEnabled(enabled);
|
||||
m_osd.showCPU->setEnabled(enabled);
|
||||
m_osd.showGPU->setEnabled(enabled);
|
||||
m_osd.showVPS->setEnabled(enabled);
|
||||
m_osd.showResolution->setEnabled(enabled);
|
||||
m_osd.showGSStats->setEnabled(enabled);
|
||||
m_osd.showHardwareInfo->setEnabled(enabled);
|
||||
m_osd.showIndicators->setEnabled(enabled);
|
||||
m_osd.showUsageCPU->setEnabled(enabled);
|
||||
m_osd.showUsageGPU->setEnabled(enabled);
|
||||
m_osd.showStatusIndicators->setEnabled(enabled);
|
||||
m_osd.showFrameTimes->setEnabled(enabled);
|
||||
m_osd.showHardwareInfo->setEnabled(enabled);
|
||||
m_osd.showVersion->setEnabled(enabled);
|
||||
}
|
||||
|
||||
@@ -970,7 +976,6 @@ void GraphicsSettingsWidget::onTextureReplacementChanged()
|
||||
m_texture.precacheTextureReplacements->setEnabled(enabled);
|
||||
}
|
||||
|
||||
|
||||
void GraphicsSettingsWidget::onCaptureContainerChanged()
|
||||
{
|
||||
const std::string container(
|
||||
|
||||
@@ -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(InterfaceSettingsWidget,
|
||||
"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)
|
||||
{
|
||||
@@ -123,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());
|
||||
@@ -200,7 +209,7 @@ InterfaceSettingsWidget::InterfaceSettingsWidget(SettingsWindow* settings_dialog
|
||||
tr("Shows the game you are currently playing as part of your profile in Discord."));
|
||||
dialog()->registerWidgetHelp(
|
||||
m_ui.mouseLock, tr("Enable Mouse Lock"), tr("Unchecked"),
|
||||
tr("Locks the mouse cursor to the windows when PCSX2 is in focus and all other windows are closed.<br><b>Unavailable on Linux Wayland.</b><br><b>Requires accessibility permissions on macOS.</b><br><b>Limited support for mixed-resolution with non-100% DPI configurations.</b>"));
|
||||
tr("Locks the mouse cursor to the windows when PCSX2 is in focus and all other windows are closed.<br><b>Unavailable on Linux Wayland.</b><br><b>Requires accessibility permissions on macOS.</b>"));
|
||||
dialog()->registerWidgetHelp(
|
||||
m_ui.doubleClickTogglesFullscreen, tr("Double-Click Toggles Fullscreen"), tr("Checked"),
|
||||
tr("Allows switching in and out of fullscreen mode by double-clicking the game window."));
|
||||
@@ -212,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"),
|
||||
@@ -221,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();
|
||||
}
|
||||
@@ -249,19 +259,14 @@ 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())
|
||||
|
||||
@@ -204,7 +204,7 @@ void SettingsWindow::setupUi(const GameList::Entry* game)
|
||||
tr("<strong>Advanced Settings</strong><hr>These are advanced options to determine the configuration of the simulated "
|
||||
"console.<br><br>Mouse over an option for additional information, and Shift+Wheel to scroll this panel."));
|
||||
addWidget(m_debug_settings = new DebugSettingsWidget(this, m_ui.settingsContainer), tr("Debug"),
|
||||
QStringLiteral("debugger-line"),
|
||||
QStringLiteral("bug-line"),
|
||||
tr("<strong>Debug Settings</strong><hr>These are options which can be used to log internal information about the application. "
|
||||
"<strong>Do not modify unless you know what you are doing</strong>, it will cause significant slowdown, and can waste large "
|
||||
"amounts of disk space."));
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 24 24"><path d="M13 19.9C15.2822 19.4367 17 17.419 17 15V12C17 11.299 16.8564 10.6219 16.5846 10H7.41538C7.14358 10.6219 7 11.299 7 12V15C7 17.419 8.71776 19.4367 11 19.9V14H13V19.9ZM5.5358 17.6907C5.19061 16.8623 5 15.9534 5 15H2V13H5V12C5 11.3573 5.08661 10.7348 5.2488 10.1436L3.0359 8.86602L4.0359 7.13397L6.05636 8.30049C6.11995 8.19854 6.18609 8.09835 6.25469 8H17.7453C17.8139 8.09835 17.88 8.19854 17.9436 8.30049L19.9641 7.13397L20.9641 8.86602L18.7512 10.1436C18.9134 10.7348 19 11.3573 19 12V13H22V15H19C19 15.9534 18.8094 16.8623 18.4642 17.6907L20.9641 19.134L19.9641 20.866L17.4383 19.4077C16.1549 20.9893 14.1955 22 12 22C9.80453 22 7.84512 20.9893 6.56171 19.4077L4.0359 20.866L3.0359 19.134L5.5358 17.6907ZM8 6C8 3.79086 9.79086 2 12 2C14.2091 2 16 3.79086 16 6H8Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 815 B |
@@ -1,28 +0,0 @@
|
||||
<svg viewBox="0 0 150 150">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
stroke: #000;
|
||||
stroke-miterlimit: 10;
|
||||
stroke-width: 2px;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g id="Camada_3">
|
||||
<g id="By_Maxihplay">
|
||||
<g id="baixo">
|
||||
<path class="cls-1" d="m20.57,127.29v-7.95h12.21v-30.79l-10.63,5.87-2.93-7.29,15.21-8.03h7.51v40.25h10.52v7.95h-31.89Z"/>
|
||||
<path class="cls-1" d="m95.6,103.13c0,3.94-.43,7.45-1.28,10.56-.86,3.1-2.11,5.73-3.76,7.86-1.65,2.14-3.66,3.77-6.03,4.89-2.37,1.12-5.08,1.69-8.14,1.69-2.64,0-5.07-.47-7.3-1.39-2.22-.93-4.14-2.39-5.76-4.38-1.61-1.99-2.87-4.54-3.78-7.64-.9-3.1-1.36-6.82-1.36-11.14,0-3.93.43-7.45,1.3-10.56.87-3.1,2.12-5.72,3.76-7.86,1.64-2.14,3.65-3.77,6.05-4.89,2.4-1.12,5.11-1.69,8.14-1.69,2.64,0,5.07.47,7.3,1.39,2.22.93,4.14,2.38,5.74,4.36,1.6,1.98,2.85,4.52,3.76,7.62.9,3.1,1.36,6.83,1.36,11.18Zm-28.37.15v1.37c0,.43.02.86.07,1.3l17.96-12.98c-.86-2.42-1.98-4.21-3.39-5.37s-3.06-1.74-4.97-1.74c-1.37,0-2.64.35-3.81,1.05s-2.19,1.77-3.06,3.21c-.87,1.44-1.55,3.25-2.05,5.43-.5,2.18-.75,4.75-.75,7.74Zm19.36.15c0-.42-.01-.84-.04-1.26-.02-.43-.04-.84-.04-1.23l-17.85,12.9c.78,2.37,1.88,4.13,3.3,5.28,1.42,1.15,3.07,1.72,4.95,1.72,1.37,0,2.64-.35,3.83-1.06s2.21-1.78,3.08-3.21c.87-1.43,1.55-3.24,2.03-5.43.49-2.19.73-4.76.73-7.72Z"/>
|
||||
<path class="cls-1" d="m103.12,127.29v-7.95h12.21v-30.79l-10.63,5.87-2.93-7.29,15.21-8.03h7.51v40.25h10.52v7.95h-31.89Z"/>
|
||||
</g>
|
||||
<g id="cima">
|
||||
<path class="cls-1" d="m52.38,46.43c0,3.94-.43,7.45-1.28,10.56-.86,3.1-2.11,5.73-3.76,7.86-1.65,2.14-3.66,3.77-6.03,4.89-2.37,1.12-5.08,1.69-8.14,1.69-2.64,0-5.07-.47-7.3-1.39-2.22-.93-4.14-2.39-5.76-4.38-1.61-1.99-2.87-4.54-3.78-7.64-.9-3.1-1.36-6.82-1.36-11.14,0-3.93.43-7.45,1.3-10.56.87-3.1,2.12-5.72,3.76-7.86,1.64-2.14,3.65-3.77,6.05-4.89,2.4-1.12,5.11-1.69,8.14-1.69,2.64,0,5.07.47,7.3,1.39,2.22.93,4.14,2.38,5.74,4.36,1.6,1.98,2.85,4.52,3.76,7.62.9,3.1,1.36,6.83,1.36,11.18Zm-28.37.15v1.37c0,.43.02.86.07,1.3l17.96-12.98c-.86-2.42-1.98-4.21-3.39-5.37s-3.06-1.74-4.97-1.74c-1.37,0-2.64.35-3.81,1.05s-2.19,1.77-3.06,3.21c-.87,1.44-1.55,3.25-2.05,5.43-.5,2.18-.75,4.75-.75,7.74Zm19.36.15c0-.42-.01-.84-.04-1.26-.02-.43-.04-.84-.04-1.23l-17.85,12.9c.78,2.37,1.88,4.13,3.3,5.28,1.42,1.15,3.07,1.72,4.95,1.72,1.37,0,2.64-.35,3.83-1.06s2.21-1.78,3.08-3.21c.87-1.43,1.55-3.24,2.03-5.43.49-2.19.73-4.76.73-7.72Z"/>
|
||||
<path class="cls-1" d="m59.89,70.58v-7.95h12.21v-30.79l-10.63,5.87-2.93-7.29,15.21-8.03h7.51v40.25h10.52v7.95h-31.89Z"/>
|
||||
<path class="cls-1" d="m134.93,46.43c0,3.94-.43,7.45-1.28,10.56-.86,3.1-2.11,5.73-3.76,7.86-1.65,2.14-3.66,3.77-6.03,4.89-2.37,1.12-5.08,1.69-8.14,1.69-2.64,0-5.07-.47-7.3-1.39-2.22-.93-4.14-2.39-5.76-4.38-1.61-1.99-2.87-4.54-3.78-7.64-.9-3.1-1.36-6.82-1.36-11.14,0-3.93.43-7.45,1.3-10.56.87-3.1,2.12-5.72,3.76-7.86,1.64-2.14,3.65-3.77,6.05-4.89,2.4-1.12,5.11-1.69,8.14-1.69,2.64,0,5.07.47,7.3,1.39,2.22.93,4.14,2.38,5.74,4.36,1.6,1.98,2.85,4.52,3.76,7.62.9,3.1,1.36,6.83,1.36,11.18Zm-28.37.15v1.37c0,.43.02.86.07,1.3l17.96-12.98c-.86-2.42-1.98-4.21-3.39-5.37-1.41-1.16-3.06-1.74-4.97-1.74-1.37,0-2.64.35-3.81,1.05s-2.19,1.77-3.06,3.21c-.87,1.44-1.55,3.25-2.05,5.43-.5,2.18-.75,4.75-.75,7.74Zm19.36.15c0-.42-.01-.84-.04-1.26-.02-.43-.04-.84-.04-1.23l-17.85,12.9c.78,2.37,1.88,4.13,3.3,5.28,1.42,1.15,3.07,1.72,4.95,1.72,1.37,0,2.64-.35,3.83-1.06s2.21-1.78,3.08-3.21c.87-1.43,1.55-3.24,2.03-5.43.49-2.19.73-4.76.73-7.72Z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="Camada_2">
|
||||
<path class="cls-2" d="m-3.8-.03h154.06v152.06H-3.8V-.03Z" fill="none"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.5 KiB |
@@ -1,10 +0,0 @@
|
||||
<svg viewBox="0 0 512 512">
|
||||
<circle
|
||||
style="fill:none;stroke:#000000;stroke-width:39.1094;stroke-linecap:round;stroke-dasharray:none"
|
||||
cx="255.99998"
|
||||
cy="255.99998"
|
||||
r="236.44533"/>
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:30;stroke-linecap:round"
|
||||
d="M 14.169739,256 H 138.21901 L 210.20476,131.31702 277.76735,383.464 358.15788,244.22352 h 139.38547"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 396 B |
@@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 24 24"><path d="M19 10H20C20.5523 10 21 10.4477 21 11V21C21 21.5523 20.5523 22 20 22H4C3.44772 22 3 21.5523 3 21V11C3 10.4477 3.44772 10 4 10H5V9C5 5.13401 8.13401 2 12 2C15.866 2 19 5.13401 19 9V10ZM17 10V9C17 6.23858 14.7614 4 12 4C9.23858 4 7 6.23858 7 9V10H17ZM11 14V18H13V14H11Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 317 B |
@@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 24 24"><path d="M7 10H20C20.5523 10 21 10.4477 21 11V21C21 21.5523 20.5523 22 20 22H4C3.44772 22 3 21.5523 3 21V11C3 10.4477 3.44772 10 4 10H5V9C5 5.13401 8.13401 2 12 2C14.7405 2 17.1131 3.5748 18.2624 5.86882L16.4731 6.76344C15.6522 5.12486 13.9575 4 12 4C9.23858 4 7 6.23858 7 9V10ZM10 15V17H14V15H10Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 338 B |
@@ -1 +0,0 @@
|
||||
<svg viewBox="0 0 512 512"><path d="M505 442.7L405.3 343c-4.5-4.5-10.6-7-17-7H372c27.6-35.3 44-79.7 44-128C416 93.1 322.9 0 208 0S0 93.1 0 208s93.1 208 208 208c48.3 0 92.7-16.4 128-44v16.3c0 6.4 2.5 12.5 7 17l99.7 99.7c9.4 9.4 24.6 9.4 33.9 0l28.3-28.3c9.4-9.4 9.4-24.6.1-34zM208 336c-70.7 0-128-57.2-128-128 0-70.7 57.2-128 128-128 70.7 0 128 57.2 128 128 0 70.7-57.2 128-128 128z"/></svg>
|
||||
|
Before Width: | Height: | Size: 391 B |
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="800px" height="800px" fill="none" version="1.1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m8.1816 10.703s4e-5 -2.5676 0-4.1081c-4e-5 -1.8489 1.527-3.5946 3.8182-3.5946 2.2912 0 3.8181 1.7457 3.8181 3.5946v4.1081" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/>
|
||||
<path d="m4.5 11.393c-4e-5 1.7387-1e-4 5.3708 2e-5 7.8056 1.3e-4 2.6923 4.1637 3.3012 7.5 3.3012 3.3363 0 7.4999-0.6089 7.4999-3.3012v-7.8056c0-0.5523-0.4477-0.9975-1-0.9975h-13c-0.55227 0-0.99998 0.4452-0.99999 0.9975zm6 4.6096c0 0.476 0.2069 0.9037 0.5357 1.198v1.5521c0 0.5522 0.4477 1 1 1h0.1429c0.5523 0 1-0.4478 1-1v-1.5521c0.3288-0.2943 0.5357-0.722 0.5357-1.198 0-0.8876-0.7195-1.6071-1.6071-1.6071-0.8877 0-1.6072 0.7195-1.6072 1.6071z" clip-rule="evenodd" fill="#000" fill-rule="evenodd"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 876 B |
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="800px" height="800px" fill="none" version="1.1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m4.5 11.393c-4e-5 1.7387-1e-4 5.3708 2e-5 7.8056 1.3e-4 2.6923 4.1637 3.3012 7.5 3.3012 3.3363 0 7.4999-0.6089 7.4999-3.3012v-7.8056c0-0.5523-0.4477-0.9975-1-0.9975h-13c-0.55227 0-0.99998 0.4452-0.99999 0.9975zm6 4.6096c0 0.476 0.2069 0.9037 0.5357 1.198v1.5521c0 0.5522 0.4477 1 1 1h0.1429c0.5523 0 1-0.4478 1-1v-1.5521c0.3288-0.2943 0.5357-0.722 0.5357-1.198 0-0.8876-0.7195-1.6071-1.6071-1.6071-0.8877 0-1.6072 0.7195-1.6072 1.6071z" clip-rule="evenodd" fill="#000" fill-rule="evenodd"/>
|
||||
<path d="m8.1816 10.703s4e-5 -2.5676 0-4.1081c-4e-5 -1.8489 1.527-3.5946 3.8182-3.5946 2.2912 0 3.8181 1.7457 3.8181 3.5946" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 869 B |
@@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 24 24"><path d="M18.031 16.6168L22.3137 20.8995L20.8995 22.3137L16.6168 18.031C15.0769 19.263 13.124 20 11 20C6.032 20 2 15.968 2 11C2 6.032 6.032 2 11 2C15.968 2 20 6.032 20 11C20 13.124 19.263 15.0769 18.031 16.6168ZM16.0247 15.8748C17.2475 14.6146 18 12.8956 18 11C18 7.1325 14.8675 4 11 4C7.1325 4 4 7.1325 4 11C4 14.8675 7.1325 18 11 18C12.8956 18 14.6146 17.2475 15.8748 16.0247L16.0247 15.8748Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 435 B |
@@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 24 24" fill="#fff"><path d="M13 19.9C15.2822 19.4367 17 17.419 17 15V12C17 11.299 16.8564 10.6219 16.5846 10H7.41538C7.14358 10.6219 7 11.299 7 12V15C7 17.419 8.71776 19.4367 11 19.9V14H13V19.9ZM5.5358 17.6907C5.19061 16.8623 5 15.9534 5 15H2V13H5V12C5 11.3573 5.08661 10.7348 5.2488 10.1436L3.0359 8.86602L4.0359 7.13397L6.05636 8.30049C6.11995 8.19854 6.18609 8.09835 6.25469 8H17.7453C17.8139 8.09835 17.88 8.19854 17.9436 8.30049L19.9641 7.13397L20.9641 8.86602L18.7512 10.1436C18.9134 10.7348 19 11.3573 19 12V13H22V15H19C19 15.9534 18.8094 16.8623 18.4642 17.6907L20.9641 19.134L19.9641 20.866L17.4383 19.4077C16.1549 20.9893 14.1955 22 12 22C9.80453 22 7.84512 20.9893 6.56171 19.4077L4.0359 20.866L3.0359 19.134L5.5358 17.6907ZM8 6C8 3.79086 9.79086 2 12 2C14.2091 2 16 3.79086 16 6H8Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 827 B |
@@ -1,31 +0,0 @@
|
||||
<svg viewBox="0 0 150 150">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #fff;
|
||||
stroke: #fff;
|
||||
stroke-miterlimit: 10;
|
||||
stroke-width: 2px;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g id="Camada_3">
|
||||
<g id="codigos">
|
||||
<g id="By_Maxihplay">
|
||||
<g id="baixo">
|
||||
<path class="cls-1" d="m20.57,127.29v-7.95h12.21v-30.79l-10.63,5.87-2.93-7.29,15.21-8.03h7.51v40.25h10.52v7.95h-31.89Z"/>
|
||||
<path class="cls-1" d="m95.6,103.13c0,3.94-.43,7.45-1.28,10.56-.86,3.1-2.11,5.73-3.76,7.86-1.65,2.14-3.66,3.77-6.03,4.89-2.37,1.12-5.08,1.69-8.14,1.69-2.64,0-5.07-.47-7.3-1.39-2.22-.93-4.14-2.39-5.76-4.38-1.61-1.99-2.87-4.54-3.78-7.64-.9-3.1-1.36-6.82-1.36-11.14,0-3.93.43-7.45,1.3-10.56.87-3.1,2.12-5.72,3.76-7.86,1.64-2.14,3.65-3.77,6.05-4.89,2.4-1.12,5.11-1.69,8.14-1.69,2.64,0,5.07.47,7.3,1.39,2.22.93,4.14,2.38,5.74,4.36,1.6,1.98,2.85,4.52,3.76,7.62.9,3.1,1.36,6.83,1.36,11.18Zm-28.37.15v1.37c0,.43.02.86.07,1.3l17.96-12.98c-.86-2.42-1.98-4.21-3.39-5.37s-3.06-1.74-4.97-1.74c-1.37,0-2.64.35-3.81,1.05s-2.19,1.77-3.06,3.21c-.87,1.44-1.55,3.25-2.05,5.43-.5,2.18-.75,4.75-.75,7.74Zm19.36.15c0-.42-.01-.84-.04-1.26-.02-.43-.04-.84-.04-1.23l-17.85,12.9c.78,2.37,1.88,4.13,3.3,5.28,1.42,1.15,3.07,1.72,4.95,1.72,1.37,0,2.64-.35,3.83-1.06s2.21-1.78,3.08-3.21c.87-1.43,1.55-3.24,2.03-5.43.49-2.19.73-4.76.73-7.72Z"/>
|
||||
<path class="cls-1" d="m103.12,127.29v-7.95h12.21v-30.79l-10.63,5.87-2.93-7.29,15.21-8.03h7.51v40.25h10.52v7.95h-31.89Z"/>
|
||||
</g>
|
||||
<g id="cima">
|
||||
<path class="cls-1" d="m52.38,46.43c0,3.94-.43,7.45-1.28,10.56-.86,3.1-2.11,5.73-3.76,7.86-1.65,2.14-3.66,3.77-6.03,4.89-2.37,1.12-5.08,1.69-8.14,1.69-2.64,0-5.07-.47-7.3-1.39-2.22-.93-4.14-2.39-5.76-4.38-1.61-1.99-2.87-4.54-3.78-7.64-.9-3.1-1.36-6.82-1.36-11.14,0-3.93.43-7.45,1.3-10.56.87-3.1,2.12-5.72,3.76-7.86,1.64-2.14,3.65-3.77,6.05-4.89,2.4-1.12,5.11-1.69,8.14-1.69,2.64,0,5.07.47,7.3,1.39,2.22.93,4.14,2.38,5.74,4.36,1.6,1.98,2.85,4.52,3.76,7.62.9,3.1,1.36,6.83,1.36,11.18Zm-28.37.15v1.37c0,.43.02.86.07,1.3l17.96-12.98c-.86-2.42-1.98-4.21-3.39-5.37s-3.06-1.74-4.97-1.74c-1.37,0-2.64.35-3.81,1.05s-2.19,1.77-3.06,3.21c-.87,1.44-1.55,3.25-2.05,5.43-.5,2.18-.75,4.75-.75,7.74Zm19.36.15c0-.42-.01-.84-.04-1.26-.02-.43-.04-.84-.04-1.23l-17.85,12.9c.78,2.37,1.88,4.13,3.3,5.28,1.42,1.15,3.07,1.72,4.95,1.72,1.37,0,2.64-.35,3.83-1.06s2.21-1.78,3.08-3.21c.87-1.43,1.55-3.24,2.03-5.43.49-2.19.73-4.76.73-7.72Z"/>
|
||||
<path class="cls-1" d="m59.89,70.58v-7.95h12.21v-30.79l-10.63,5.87-2.93-7.29,15.21-8.03h7.51v40.25h10.52v7.95h-31.89Z"/>
|
||||
<path class="cls-1" d="m134.93,46.43c0,3.94-.43,7.45-1.28,10.56-.86,3.1-2.11,5.73-3.76,7.86-1.65,2.14-3.66,3.77-6.03,4.89-2.37,1.12-5.08,1.69-8.14,1.69-2.64,0-5.07-.47-7.3-1.39-2.22-.93-4.14-2.39-5.76-4.38-1.61-1.99-2.87-4.54-3.78-7.64-.9-3.1-1.36-6.82-1.36-11.14,0-3.93.43-7.45,1.3-10.56.87-3.1,2.12-5.72,3.76-7.86,1.64-2.14,3.65-3.77,6.05-4.89,2.4-1.12,5.11-1.69,8.14-1.69,2.64,0,5.07.47,7.3,1.39,2.22.93,4.14,2.38,5.74,4.36,1.6,1.98,2.85,4.52,3.76,7.62.9,3.1,1.36,6.83,1.36,11.18Zm-28.37.15v1.37c0,.43.02.86.07,1.3l17.96-12.98c-.86-2.42-1.98-4.21-3.39-5.37-1.41-1.16-3.06-1.74-4.97-1.74-1.37,0-2.64.35-3.81,1.05s-2.19,1.77-3.06,3.21c-.87,1.44-1.55,3.25-2.05,5.43-.5,2.18-.75,4.75-.75,7.74Zm19.36.15c0-.42-.01-.84-.04-1.26-.02-.43-.04-.84-.04-1.23l-17.85,12.9c.78,2.37,1.88,4.13,3.3,5.28,1.42,1.15,3.07,1.72,4.95,1.72,1.37,0,2.64-.35,3.83-1.06,1.19-.71,2.21-1.78,3.08-3.21.87-1.43,1.55-3.24,2.03-5.43.49-2.19.73-4.76.73-7.72Z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="Camada_2">
|
||||
<path class="cls-2" d="m-3.8-.03h154.06v152.06H-3.8V-.03Z" fill="none"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.6 KiB |
@@ -1,10 +0,0 @@
|
||||
<svg viewBox="0 0 512 512">
|
||||
<circle
|
||||
style="fill:none;stroke:#ffffff;stroke-width:39.1094;stroke-linecap:round;stroke-dasharray:none"
|
||||
cx="255.99998"
|
||||
cy="255.99998"
|
||||
r="236.44533" />
|
||||
<path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:30;stroke-linecap:round"
|
||||
d="M 14.169739,256 H 138.21901 L 210.20476,131.31702 277.76735,383.464 358.15788,244.22352 h 139.38547"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 397 B |
@@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 24 24" fill="#fff"><path d="M19 10H20C20.5523 10 21 10.4477 21 11V21C21 21.5523 20.5523 22 20 22H4C3.44772 22 3 21.5523 3 21V11C3 10.4477 3.44772 10 4 10H5V9C5 5.13401 8.13401 2 12 2C15.866 2 19 5.13401 19 9V10ZM17 10V9C17 6.23858 14.7614 4 12 4C9.23858 4 7 6.23858 7 9V10H17ZM11 14V18H13V14H11Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 329 B |
@@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 24 24" fill="#fff"><path d="M7 10H20C20.5523 10 21 10.4477 21 11V21C21 21.5523 20.5523 22 20 22H4C3.44772 22 3 21.5523 3 21V11C3 10.4477 3.44772 10 4 10H5V9C5 5.13401 8.13401 2 12 2C14.7405 2 17.1131 3.5748 18.2624 5.86882L16.4731 6.76344C15.6522 5.12486 13.9575 4 12 4C9.23858 4 7 6.23858 7 9V10ZM10 15V17H14V15H10Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 350 B |
@@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 24 24" fill="#fff"><path d="M18.031 16.6168L22.3137 20.8995L20.8995 22.3137L16.6168 18.031C15.0769 19.263 13.124 20 11 20C6.032 20 2 15.968 2 11C2 6.032 6.032 2 11 2C15.968 2 20 6.032 20 11C20 13.124 19.263 15.0769 18.031 16.6168ZM16.0247 15.8748C17.2475 14.6146 18 12.8956 18 11C18 7.1325 14.8675 4 11 4C7.1325 4 4 7.1325 4 11C4 14.8675 7.1325 18 11 18C12.8956 18 14.6146 17.2475 15.8748 16.0247L16.0247 15.8748Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 447 B |
@@ -9,6 +9,7 @@
|
||||
<file>icons/black/svg/booklet.svg</file>
|
||||
<file>icons/black/svg/book.svg</file>
|
||||
<file>icons/black/svg/brush-line.svg</file>
|
||||
<file>icons/black/svg/bug-line.svg</file>
|
||||
<file>icons/black/svg/buzz-controller-line.svg</file>
|
||||
<file>icons/black/svg/camera-video.svg</file>
|
||||
<file>icons/black/svg/cheats-line.svg</file>
|
||||
@@ -17,7 +18,6 @@
|
||||
<file>icons/black/svg/close-line.svg</file>
|
||||
<file>icons/black/svg/controller-line.svg</file>
|
||||
<file>icons/black/svg/controller-strike-line.svg</file>
|
||||
<file>icons/black/svg/debugger-line.svg</file>
|
||||
<file>icons/black/svg/debug-step-into-line.svg</file>
|
||||
<file>icons/black/svg/debug-step-out-line.svg</file>
|
||||
<file>icons/black/svg/debug-step-over-line.svg</file>
|
||||
@@ -54,7 +54,6 @@
|
||||
<file>icons/black/svg/guitar-line.svg</file>
|
||||
<file>icons/black/svg/guncon2-line.svg</file>
|
||||
<file>icons/black/svg/headset-line.svg</file>
|
||||
<file>icons/black/svg/heart-circle-line.svg</file>
|
||||
<file>icons/black/svg/image-fill.svg</file>
|
||||
<file>icons/black/svg/interface-line.svg</file>
|
||||
<file>icons/black/svg/jogcon-line.svg</file>
|
||||
@@ -63,16 +62,15 @@
|
||||
<file>icons/black/svg/keyboardmania-line.svg</file>
|
||||
<file>icons/black/svg/lightbulb-line.svg</file>
|
||||
<file>icons/black/svg/list-check.svg</file>
|
||||
<file>icons/black/svg/lock-fill.svg</file>
|
||||
<file>icons/black/svg/lock-unlock-fill.svg</file>
|
||||
<file>icons/black/svg/login-box-line.svg</file>
|
||||
<file>icons/black/svg/magnifier-line.svg</file>
|
||||
<file>icons/black/svg/memcard-line.svg</file>
|
||||
<file>icons/black/svg/mic-line.svg</file>
|
||||
<file>icons/black/svg/minus-line.svg</file>
|
||||
<file>icons/black/svg/mouse-line.svg</file>
|
||||
<file>icons/black/svg/msd-line.svg</file>
|
||||
<file>icons/black/svg/negcon-line.svg</file>
|
||||
<file>icons/black/svg/padlock-lock.svg</file>
|
||||
<file>icons/black/svg/padlock-unlock.svg</file>
|
||||
<file>icons/black/svg/pause-line.svg</file>
|
||||
<file>icons/black/svg/pencil-line.svg</file>
|
||||
<file>icons/black/svg/pin-filled.svg</file>
|
||||
@@ -87,6 +85,7 @@
|
||||
<file>icons/black/svg/save-3-line.svg</file>
|
||||
<file>icons/black/svg/screenshot-2-line.svg</file>
|
||||
<file>icons/black/svg/seamic-line.svg</file>
|
||||
<file>icons/black/svg/search-line.svg</file>
|
||||
<file>icons/black/svg/settings-3-line.svg</file>
|
||||
<file>icons/black/svg/shut-down-line.svg</file>
|
||||
<file>icons/black/svg/singstar-line.svg</file>
|
||||
@@ -114,6 +113,7 @@
|
||||
<file>icons/white/svg/booklet.svg</file>
|
||||
<file>icons/white/svg/book.svg</file>
|
||||
<file>icons/white/svg/brush-line.svg</file>
|
||||
<file>icons/white/svg/bug-line.svg</file>
|
||||
<file>icons/white/svg/buzz-controller-line.svg</file>
|
||||
<file>icons/white/svg/camera-video.svg</file>
|
||||
<file>icons/white/svg/cheats-line.svg</file>
|
||||
@@ -122,7 +122,6 @@
|
||||
<file>icons/white/svg/close-line.svg</file>
|
||||
<file>icons/white/svg/controller-line.svg</file>
|
||||
<file>icons/white/svg/controller-strike-line.svg</file>
|
||||
<file>icons/white/svg/debugger-line.svg</file>
|
||||
<file>icons/white/svg/debug-step-into-line.svg</file>
|
||||
<file>icons/white/svg/debug-step-out-line.svg</file>
|
||||
<file>icons/white/svg/debug-step-over-line.svg</file>
|
||||
@@ -159,7 +158,6 @@
|
||||
<file>icons/white/svg/guitar-line.svg</file>
|
||||
<file>icons/white/svg/guncon2-line.svg</file>
|
||||
<file>icons/white/svg/headset-line.svg</file>
|
||||
<file>icons/white/svg/heart-circle-line.svg</file>
|
||||
<file>icons/white/svg/image-fill.svg</file>
|
||||
<file>icons/white/svg/interface-line.svg</file>
|
||||
<file>icons/white/svg/jogcon-line.svg</file>
|
||||
@@ -168,6 +166,8 @@
|
||||
<file>icons/white/svg/keyboardmania-line.svg</file>
|
||||
<file>icons/white/svg/lightbulb-line.svg</file>
|
||||
<file>icons/white/svg/list-check.svg</file>
|
||||
<file>icons/white/svg/lock-fill.svg</file>
|
||||
<file>icons/white/svg/lock-unlock-fill.svg</file>
|
||||
<file>icons/white/svg/login-box-line.svg</file>
|
||||
<file>icons/white/svg/magnifier-line.svg</file>
|
||||
<file>icons/white/svg/memcard-line.svg</file>
|
||||
@@ -192,6 +192,7 @@
|
||||
<file>icons/white/svg/save-3-line.svg</file>
|
||||
<file>icons/white/svg/screenshot-2-line.svg</file>
|
||||
<file>icons/white/svg/seamic-line.svg</file>
|
||||
<file>icons/white/svg/search-line.svg</file>
|
||||
<file>icons/white/svg/settings-3-line.svg</file>
|
||||
<file>icons/white/svg/shut-down-line.svg</file>
|
||||
<file>icons/white/svg/singstar-line.svg</file>
|
||||
|
||||
@@ -484,6 +484,13 @@ bool Achievements::CreateClient(rc_client_t** client, std::unique_ptr<HTTPDownlo
|
||||
|
||||
rc_client_set_userdata(new_client, http->get());
|
||||
|
||||
const std::string custom_host = Host::GetBaseStringSettingValue("Achievements", "Host", "");
|
||||
if (!custom_host.empty())
|
||||
{
|
||||
Console.WriteLn("Achievements: Using custom host %s", custom_host.c_str());
|
||||
rc_client_set_host(new_client, custom_host.c_str());
|
||||
}
|
||||
|
||||
*client = new_client;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
|
||||
cdvdStruct cdvd;
|
||||
|
||||
s64 PSXCLK = 36864000;
|
||||
u32 PSXCLK = 36864000;
|
||||
|
||||
static constexpr s32 GMT9_OFFSET_SECONDS = 9 * 60 * 60; // 32400
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
static const u32 BIAS = 2; // Bus is half of the actual ps2 speed
|
||||
static const u32 PS2CLK = 294912000; //hz /* 294.912 mhz */
|
||||
extern s64 PSXCLK; /* 36.864 Mhz */
|
||||
extern u32 PSXCLK; /* 36.864 Mhz */
|
||||
|
||||
|
||||
#include "Memory.h"
|
||||
@@ -21,4 +21,4 @@ extern s64 PSXCLK; /* 36.864 Mhz */
|
||||
#include <string>
|
||||
|
||||
extern std::string ShiftJIS_ConvertString( const char* src );
|
||||
extern std::string ShiftJIS_ConvertString( const char* src, int maxlen );
|
||||
extern std::string ShiftJIS_ConvertString( const char* src, int maxlen );
|
||||
|
||||
@@ -456,6 +456,8 @@ enum class GSNativeScaling : u8
|
||||
Off,
|
||||
Normal,
|
||||
Aggressive,
|
||||
NormalUpscaled,
|
||||
AggressiveUpscaled,
|
||||
MaxCount
|
||||
};
|
||||
|
||||
@@ -711,7 +713,7 @@ struct Pcsx2Config
|
||||
|
||||
union
|
||||
{
|
||||
u64 bitset[2];
|
||||
u64 bitsets[2];
|
||||
|
||||
struct
|
||||
{
|
||||
@@ -734,19 +736,20 @@ struct Pcsx2Config
|
||||
OsdShowSpeed : 1,
|
||||
OsdShowFPS : 1,
|
||||
OsdShowVPS : 1,
|
||||
OsdShowCPU : 1,
|
||||
OsdShowGPU : 1,
|
||||
OsdShowResolution : 1,
|
||||
OsdShowGSStats : 1,
|
||||
OsdShowCPU : 1,
|
||||
OsdShowGPU : 1,
|
||||
OsdShowIndicators : 1,
|
||||
OsdShowFrameTimes : 1,
|
||||
OsdShowHardwareInfo : 1,
|
||||
OsdShowVersion : 1,
|
||||
OsdShowSettings : 1,
|
||||
OsdshowPatches : 1,
|
||||
OsdShowInputs : 1,
|
||||
OsdShowFrameTimes : 1,
|
||||
OsdShowVersion : 1,
|
||||
OsdShowVideoCapture : 1,
|
||||
OsdShowInputRec : 1,
|
||||
OsdShowHardwareInfo : 1,
|
||||
OsdShowTextureReplacements : 1,
|
||||
HWSpinGPUForReadbacks : 1,
|
||||
HWSpinCPUForReadbacks : 1,
|
||||
GPUPaletteConversion : 1,
|
||||
@@ -776,6 +779,8 @@ struct Pcsx2Config
|
||||
SaveAlpha : 1,
|
||||
SaveInfo : 1,
|
||||
SaveTransferImages : 1,
|
||||
SaveDrawStats : 1,
|
||||
SaveFrameStats : 1,
|
||||
DumpReplaceableTextures : 1,
|
||||
DumpReplaceableMipmaps : 1,
|
||||
DumpTexturesWithFMVActive : 1,
|
||||
|
||||
@@ -252,7 +252,7 @@ static void vSyncInfoCalc(vSyncTimingInfo* info, double framesPerSecond, u32 sca
|
||||
const u64 accumilatedHRenderError = (hRender % 10000) + (hBlank % 10000);
|
||||
const u64 accumilatedHFractional = accumilatedHRenderError % 10000;
|
||||
info->hRender += (u32)(accumilatedHRenderError / 10000);
|
||||
info->hSyncError = (accumilatedHFractional * (scansPerFrame / (IsInterlacedVideoMode() ? 2 : 1))) / 10000;
|
||||
info->hSyncError = (u32)((accumilatedHFractional * (scansPerFrame / (IsInterlacedVideoMode() ? 2 : 1))) / 10000);
|
||||
|
||||
// Note: In NTSC modes there is some small rounding error in the vsync too,
|
||||
// however it would take thousands of frames for it to amount to anything and
|
||||
|
||||
@@ -105,7 +105,7 @@ bool ATA::IO_Write()
|
||||
{
|
||||
IO_SparseCacheUpdateLocation(imagePos + written);
|
||||
// Align to sparse block size.
|
||||
u32 writeSize = hddSparseBlockSize - ((imagePos + written) % hddSparseBlockSize);
|
||||
u32 writeSize = static_cast<u32>(hddSparseBlockSize - ((imagePos + written) % hddSparseBlockSize));
|
||||
// Limit to size of write.
|
||||
writeSize = std::min(writeSize, entry.length - written);
|
||||
|
||||
|
||||
@@ -160,7 +160,7 @@ namespace InternalServers
|
||||
//Counts
|
||||
ret->questions = dns.questions;
|
||||
|
||||
DNS_State* state = new DNS_State(reqs.size(), reqs, ret, payload->sourcePort);
|
||||
DNS_State* state = new DNS_State(static_cast<int>(reqs.size()), reqs, ret, payload->sourcePort);
|
||||
outstandingQueries++;
|
||||
|
||||
for (size_t i = 0; i < reqs.size(); i++)
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "common/ScopedGuard.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "common/RedtapeWindows.h"
|
||||
#include <winsock2.h>
|
||||
#include <iphlpapi.h>
|
||||
#elif defined(__POSIX__)
|
||||
@@ -20,6 +21,7 @@
|
||||
#include "sockets.h"
|
||||
#include "AdapterUtils.h"
|
||||
#include "DEV9.h"
|
||||
#include "Host.h"
|
||||
|
||||
#include "Sessions/ICMP_Session/ICMP_Session.h"
|
||||
#include "Sessions/TCP_Session/TCP_Session.h"
|
||||
@@ -45,7 +47,7 @@ std::vector<AdapterEntry> SocketAdapter::GetAdapters()
|
||||
std::vector<AdapterEntry> nic;
|
||||
AdapterEntry autoEntry;
|
||||
autoEntry.type = Pcsx2Config::DEV9Options::NetApi::Sockets;
|
||||
autoEntry.name = "Auto";
|
||||
autoEntry.name = TRANSLATE_STR("DEV9", "Auto");
|
||||
autoEntry.guid = "Auto";
|
||||
nic.push_back(autoEntry);
|
||||
|
||||
|
||||
@@ -8,22 +8,19 @@
|
||||
|
||||
std::vector<std::unique_ptr<BiosThread>> getEEThreads()
|
||||
{
|
||||
std::vector<std::unique_ptr<BiosThread>> threads;
|
||||
|
||||
if (CurrentBiosInformation.eeThreadListAddr <= 0)
|
||||
return threads;
|
||||
if (!VMManager::HasValidVM() || CurrentBiosInformation.eeThreadListAddr == 0)
|
||||
return {};
|
||||
|
||||
const u32 start = CurrentBiosInformation.eeThreadListAddr & 0x3fffff;
|
||||
|
||||
std::vector<std::unique_ptr<BiosThread>> threads;
|
||||
for (int tid = 0; tid < 256; tid++)
|
||||
{
|
||||
EEInternalThread* internal = static_cast<EEInternalThread*>(
|
||||
PSM(start + tid * sizeof(EEInternalThread)));
|
||||
|
||||
EEInternalThread* internal = static_cast<EEInternalThread*>(PSM(start + tid * sizeof(EEInternalThread)));
|
||||
if (internal->status != (int)ThreadStatus::THS_BAD)
|
||||
{
|
||||
auto thread = std::make_unique<EEThread>(tid, *internal);
|
||||
threads.push_back(std::move(thread));
|
||||
}
|
||||
if (internal && internal->status != (int)ThreadStatus::THS_BAD)
|
||||
threads.emplace_back(std::make_unique<EEThread>(tid, *internal));
|
||||
}
|
||||
|
||||
return threads;
|
||||
@@ -63,8 +60,7 @@ std::vector<std::unique_ptr<BiosThread>> getIOPThreads()
|
||||
|
||||
data.PC = iopMemRead32(data.SavedSP + 0x8c);
|
||||
|
||||
auto thread = std::make_unique<IOPThread>(data);
|
||||
threads.push_back(std::move(thread));
|
||||
threads.emplace_back(std::make_unique<IOPThread>(data));
|
||||
|
||||
item = iopMemRead32(item + 0x24);
|
||||
}
|
||||
@@ -93,7 +89,7 @@ std::vector<IopMod> getIOPModules()
|
||||
if (i > 1000)
|
||||
return {};
|
||||
|
||||
IopMod mod;
|
||||
IopMod& mod = modlist.emplace_back();
|
||||
|
||||
u32 nstr = iopMemRead32(maddr + 4);
|
||||
if (nstr)
|
||||
@@ -113,8 +109,6 @@ std::vector<IopMod> getIOPModules()
|
||||
mod.data_size = iopMemRead32(maddr + 0x20);
|
||||
mod.bss_size = iopMemRead32(maddr + 0x24);
|
||||
|
||||
modlist.push_back(mod);
|
||||
|
||||
maddr = iopMemRead32(maddr);
|
||||
}
|
||||
|
||||
|
||||
@@ -186,7 +186,7 @@ namespace MIPSAnalyst
|
||||
bool suspectedNoReturn = false;
|
||||
|
||||
u32 addr;
|
||||
for (addr = startAddr; addr <= endAddr; addr += 4) {
|
||||
for (addr = startAddr; addr < endAddr; addr += 4) {
|
||||
// Use pre-existing symbol map info if available. May be more reliable.
|
||||
ccc::FunctionHandle existing_symbol_handle = database.functions.first_handle_from_starting_address(addr);
|
||||
const ccc::Function* existing_symbol = database.functions.symbol_from_handle(existing_symbol_handle);
|
||||
|
||||
@@ -108,7 +108,7 @@ namespace MipsStackWalk
|
||||
|
||||
if (entry == INVALIDTARGET)
|
||||
{
|
||||
stop = std::max<s64>(0, (s64)start - LONGEST_FUNCTION);
|
||||
stop = static_cast<u32>(std::max<s64>(0, (s64)start - LONGEST_FUNCTION));
|
||||
}
|
||||
|
||||
for (u32 pc = start; cpu->isValidAddress(pc) && pc >= stop; pc -= 4)
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
<!-- PDF METADATA STARTS ---
|
||||
title: "PCSX2 - Debugger Documentation"
|
||||
date: "2021"
|
||||
footer-left: "[Document Source](https://github.com/PCSX2/pcsx2/blob/{LATEST-GIT-TAG}/pcsx2/Docs/Debugger.md)"
|
||||
urlcolor: "cyan"
|
||||
... PDF METADATA ENDS -->
|
||||
|
||||
# Debugger Key Bindings
|
||||
|
||||
## Disassembly View
|
||||
- `G` - goto
|
||||
- `E` - edit breakpoint
|
||||
- `D` - enable/disable breakpoint
|
||||
- `B` - add breakpoint
|
||||
- `M` - assemble opcode
|
||||
- `Right Arrow` - follow branch/position memory view to accessed address
|
||||
- `Left Arrow` - go back one branch level/goto pc
|
||||
- `Up Arrow` - move cursor up one line
|
||||
- `Down Arrow` - 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
|
||||
|
||||
- `G` - goto
|
||||
- `Ctrl+B` - add breakpoint
|
||||
- `Left Arrow` - move cursor back one byte/nibble
|
||||
- `Right Arrow` - move cursor ahead one byte/nibble
|
||||
- `Up Arrow` - move cursor up one line
|
||||
- `Down Arrow` - 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+Mouse Wheel` - zoom memory view
|
||||
- `Esc` - return to previous goto address
|
||||
- `Ctrl+V` - paste a hex string into memory
|
||||
- `Enter/Return` - edit selected float
|
||||
|
||||
## Breakpoint List
|
||||
|
||||
- `Up Arrow` - select previous item
|
||||
- `Down Arrow` - select next item
|
||||
- `Delete` - remove selected breakpoint
|
||||
- `Enter/Return` - edit selected breakpoint
|
||||
- `Space` - toggle enable state of selected breakpoint
|
||||
@@ -180,7 +180,7 @@ The clamp modes are also numerically based.
|
||||
* skipDrawStart [Value between `0` to `10000`] {0-10000} Default: Off (`0`)
|
||||
* skipDrawEnd [Value between `0` to `10000`] {0-10000} Default: Off (`0`)
|
||||
* halfPixelOffset [`0` or `1` or `2` or `3` or `4` or `5`] {Off, Normal Vertex, Special (Texture), Special (Texture Aggressive), Align to Native, Align to Native with Texture Offsets} Default: Off (`0`)
|
||||
* nativeScaling [`0` or `1` or `2`] {Normal, Aggressive or Off} Default: Normal (`0`)
|
||||
* nativeScaling [`0` or `1` or `2` or `3` or `4`] {Off, Normal, Aggressive, Normal (Maintain Upscale) or Aggressive (Maintain Upscale) } Default: Normal (`0`)
|
||||
* nativePaletteDraw [`0` or `1`] {Off, On} Default: Off (`0`)
|
||||
* roundSprite [`0` or `1` or `2`] {Off, Half or Full} Default: Off (`0`)
|
||||
|
||||
|
||||
@@ -242,7 +242,7 @@
|
||||
"nativeScaling": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 2
|
||||
"maximum": 4
|
||||
},
|
||||
"roundSprite": {
|
||||
"type": "integer",
|
||||
|
||||
@@ -664,40 +664,36 @@ void GSgetStats(SmallStringBase& info)
|
||||
|
||||
if (pps >= 170000000)
|
||||
{
|
||||
pps /= 1073741824; // Gpps
|
||||
pps /= _1gb; // Gpps
|
||||
prefix = 'G';
|
||||
}
|
||||
else if (pps >= 35000000)
|
||||
{
|
||||
pps /= 1048576; // Mpps
|
||||
pps /= _1mb; // Mpps
|
||||
prefix = 'M';
|
||||
}
|
||||
else if (pps >= 1024)
|
||||
else if (pps >= _1kb)
|
||||
{
|
||||
pps /= 1024;
|
||||
prefix = 'K';
|
||||
}
|
||||
else
|
||||
{
|
||||
prefix = '\0';
|
||||
pps /= _1kb; // kpps
|
||||
prefix = 'k';
|
||||
}
|
||||
|
||||
info.format("{} SW | {} SP | {} P | {} D | {:.2f} S | {:.2f} U | {:.2f} {}pps",
|
||||
info.format("{} SW | {} SYNP | {} PRIM | {} DRW | {:.2f} SWIZ | {:.2f} UNSWIZ | {:.2f} {}pps",
|
||||
api_name,
|
||||
(int)pm.Get(GSPerfMon::SyncPoint),
|
||||
(int)pm.Get(GSPerfMon::Prim),
|
||||
(int)pm.Get(GSPerfMon::Draw),
|
||||
pm.Get(GSPerfMon::Swizzle) / 1024,
|
||||
pm.Get(GSPerfMon::Unswizzle) / 1024,
|
||||
pm.Get(GSPerfMon::Swizzle) / _1kb,
|
||||
pm.Get(GSPerfMon::Unswizzle) / _1kb,
|
||||
pps, prefix);
|
||||
}
|
||||
else if (GSCurrentRenderer == GSRendererType::Null)
|
||||
{
|
||||
fmt::format_to(std::back_inserter(info), "{} Null", api_name);
|
||||
info.format("{} Null", api_name);
|
||||
}
|
||||
else
|
||||
{
|
||||
info.format("{} HW | {} P | {} D | {} DC | {} B | {} RP | {} RB | {} TC | {} TU",
|
||||
info.format("{} HW | {} PRIM | {} DRW | {} DRWC | {} BAR | {} RP | {} RB | {} TC | {} TU",
|
||||
api_name,
|
||||
(int)pm.Get(GSPerfMon::Prim),
|
||||
(int)pm.Get(GSPerfMon::Draw),
|
||||
@@ -713,30 +709,45 @@ void GSgetStats(SmallStringBase& info)
|
||||
void GSgetMemoryStats(SmallStringBase& info)
|
||||
{
|
||||
if (!g_texture_cache)
|
||||
{
|
||||
info.assign("");
|
||||
return;
|
||||
}
|
||||
|
||||
const u64 targets = g_texture_cache->GetTargetMemoryUsage();
|
||||
const u64 sources = g_texture_cache->GetSourceMemoryUsage();
|
||||
const u64 hashcache = g_texture_cache->GetHashCacheMemoryUsage();
|
||||
const u64 pool = g_gs_device->GetPoolMemoryUsage();
|
||||
const u64 total = targets + sources + hashcache + pool;
|
||||
// Get megabyte values. Round negligible values to 0.1 MB to avoid swamping.
|
||||
const auto get_MB = [](const double bytes) {
|
||||
return (bytes <= 0.0 ? bytes : std::max(0.1, bytes / static_cast<double>(_1mb)));
|
||||
};
|
||||
|
||||
const auto format_precision = [](const double megabytes) -> std::string {
|
||||
return (megabytes < 10.0 ?
|
||||
fmt::format("{:.1f}", megabytes) :
|
||||
fmt::format("{:.0f}", std::round(megabytes)));
|
||||
};
|
||||
|
||||
const double targets_MB = get_MB(static_cast<double>(g_texture_cache->GetTargetMemoryUsage()));
|
||||
const double sources_MB = get_MB(static_cast<double>(g_texture_cache->GetSourceMemoryUsage()));
|
||||
const double pool_MB = get_MB(static_cast<double>(g_gs_device->GetPoolMemoryUsage()));
|
||||
|
||||
if (GSConfig.TexturePreloading == TexturePreloadingLevel::Full)
|
||||
{
|
||||
fmt::format_to(std::back_inserter(info), "VRAM: {} MB | T: {} MB | S: {} MB | H: {} MB | P: {} MB",
|
||||
(int)std::ceil(total / 1048576.0f),
|
||||
(int)std::ceil(targets / 1048576.0f),
|
||||
(int)std::ceil(sources / 1048576.0f),
|
||||
(int)std::ceil(hashcache / 1048576.0f),
|
||||
(int)std::ceil(pool / 1048576.0f));
|
||||
const double hashcache_MB = get_MB(static_cast<double>(g_texture_cache->GetHashCacheMemoryUsage()));
|
||||
const double total_MB = targets_MB + sources_MB + hashcache_MB + pool_MB;
|
||||
info.format("VRAM: {} MB | TGT: {} MB | SRC: {} MB | HC: {} MB | PL: {} MB",
|
||||
format_precision(total_MB),
|
||||
format_precision(targets_MB),
|
||||
format_precision(sources_MB),
|
||||
format_precision(hashcache_MB),
|
||||
format_precision(pool_MB));
|
||||
}
|
||||
else
|
||||
{
|
||||
fmt::format_to(std::back_inserter(info), "VRAM: {} MB | T: {} MB | S: {} MB | P: {} MB",
|
||||
(int)std::ceil(total / 1048576.0f),
|
||||
(int)std::ceil(targets / 1048576.0f),
|
||||
(int)std::ceil(sources / 1048576.0f),
|
||||
(int)std::ceil(pool / 1048576.0f));
|
||||
const double total_MB = targets_MB + sources_MB + pool_MB;
|
||||
info.format("VRAM: {} MB | TGT: {} MB | SRC: {} MB | PL: {} MB",
|
||||
format_precision(total_MB),
|
||||
format_precision(targets_MB),
|
||||
format_precision(sources_MB),
|
||||
format_precision(pool_MB));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1123,6 +1134,7 @@ static void HotkeyToggleOSD()
|
||||
GSConfig.OsdShowInputs ^= EmuConfig.GS.OsdShowInputs;
|
||||
GSConfig.OsdShowInputRec ^= EmuConfig.GS.OsdShowInputRec;
|
||||
GSConfig.OsdShowVideoCapture ^= EmuConfig.GS.OsdShowVideoCapture;
|
||||
GSConfig.OsdShowTextureReplacements ^= EmuConfig.GS.OsdShowTextureReplacements;
|
||||
|
||||
GSConfig.OsdMessagesPos =
|
||||
GSConfig.OsdMessagesPos == OsdOverlayPos::None ? EmuConfig.GS.OsdMessagesPos : OsdOverlayPos::None;
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
|
||||
#include "GSPerfMon.h"
|
||||
#include "GS.h"
|
||||
#include "GSUtil.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <inttypes.h>
|
||||
|
||||
GSPerfMon g_perfmon;
|
||||
|
||||
@@ -41,3 +43,28 @@ void GSPerfMon::Update()
|
||||
|
||||
memset(m_counters, 0, sizeof(m_counters));
|
||||
}
|
||||
|
||||
GSPerfMon GSPerfMon::operator-(const GSPerfMon& other)
|
||||
{
|
||||
GSPerfMon diff;
|
||||
for (std::size_t i = 0; i < std::size(diff.m_counters); i++)
|
||||
{
|
||||
diff.m_counters[i] = m_counters[i] - other.m_counters[i];
|
||||
}
|
||||
return diff;
|
||||
}
|
||||
|
||||
void GSPerfMon::Dump(const std::string& filename, bool hw)
|
||||
{
|
||||
FILE* fp = fopen(filename.c_str(), "w");
|
||||
if (!fp)
|
||||
return;
|
||||
|
||||
std::size_t last = hw ? CounterLastHW : CounterLastSW;
|
||||
for (std::size_t i = 0; i < last; i++)
|
||||
{
|
||||
fprintf(fp, "%s: %" PRIu64 "\n", GSUtil::GetPerfMonCounterName(static_cast<counter_t>(i), hw), static_cast<u64>(m_counters[i]));
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "common/Pcsx2Defs.h"
|
||||
|
||||
#include <ctime>
|
||||
#include <string>
|
||||
|
||||
class GSPerfMon
|
||||
{
|
||||
@@ -27,12 +28,15 @@ public:
|
||||
// Reused counters for HW.
|
||||
TextureCopies = Fillrate,
|
||||
TextureUploads = SyncPoint,
|
||||
|
||||
CounterLastHW = CounterLast,
|
||||
CounterLastSW = SyncPoint + 1
|
||||
};
|
||||
|
||||
protected:
|
||||
double m_counters[CounterLast] = {};
|
||||
double m_stats[CounterLast] = {};
|
||||
u64 m_frame = 0;
|
||||
int m_frame = 0;
|
||||
clock_t m_lastframe = 0;
|
||||
int m_count = 0;
|
||||
int m_disp_fb_sprite_blits = 0;
|
||||
@@ -42,8 +46,8 @@ public:
|
||||
|
||||
void Reset();
|
||||
|
||||
void SetFrame(u64 frame) { m_frame = frame; }
|
||||
u64 GetFrame() { return m_frame; }
|
||||
void SetFrame(int frame) { m_frame = frame; }
|
||||
int GetFrame() { return m_frame; }
|
||||
void EndFrame(bool frame_only);
|
||||
|
||||
void Put(counter_t c, double val) { m_counters[c] += val; }
|
||||
@@ -58,6 +62,10 @@ public:
|
||||
m_disp_fb_sprite_blits = 0;
|
||||
return blits;
|
||||
}
|
||||
|
||||
GSPerfMon operator-(const GSPerfMon& other);
|
||||
|
||||
void Dump(const std::string& filename, bool hw);
|
||||
};
|
||||
|
||||
extern GSPerfMon g_perfmon;
|
||||
extern GSPerfMon g_perfmon;
|
||||
|
||||