From 93f9ae8a31877e6b9417f09abf7370d674112dd9 Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Mon, 13 Dec 2021 22:12:54 +1000 Subject: [PATCH] Qt: Add work in progress interface --- 3rdparty/qt/.gitignore | 3 + 3rdparty/qt/README.md | 11 + PCSX2_qt.sln | 656 ++++++++ bin/resources/cover-placeholder.png | Bin 0 -> 83189 bytes cmake/SearchForStuff.cmake | 4 + common/vsprops/QtCompile.props | 197 +++ common/vsprops/QtCompile.targets | 10 + common/vsprops/QtCompile.xml | 14 + pcsx2-qt/AboutDialog.cpp | 76 + pcsx2-qt/AboutDialog.h | 37 + pcsx2-qt/AboutDialog.ui | 168 ++ pcsx2-qt/CMakeLists.txt | 102 ++ pcsx2-qt/DisplayWidget.cpp | 334 ++++ pcsx2-qt/DisplayWidget.h | 77 + pcsx2-qt/EmuThread.cpp | 755 +++++++++ pcsx2-qt/EmuThread.h | 144 ++ pcsx2-qt/GameList/GameListModel.cpp | 490 ++++++ pcsx2-qt/GameList/GameListModel.h | 97 ++ pcsx2-qt/GameList/GameListRefreshThread.cpp | 130 ++ pcsx2-qt/GameList/GameListRefreshThread.h | 75 + pcsx2-qt/GameList/GameListWidget.cpp | 457 ++++++ pcsx2-qt/GameList/GameListWidget.h | 111 ++ pcsx2-qt/Main.cpp | 264 ++++ pcsx2-qt/MainWindow.cpp | 1372 +++++++++++++++++ pcsx2-qt/MainWindow.h | 177 +++ pcsx2-qt/MainWindow.ui | 648 ++++++++ pcsx2-qt/PrecompiledHeader.cpp | 16 + pcsx2-qt/PrecompiledHeader.h | 21 + pcsx2-qt/QtHost.cpp | 506 ++++++ pcsx2-qt/QtHost.h | 54 + pcsx2-qt/QtKeyCodes.cpp | 486 ++++++ pcsx2-qt/QtUtils.cpp | 703 +++++++++ pcsx2-qt/QtUtils.h | 85 + pcsx2-qt/SettingWidgetBinder.h | 417 +++++ .../Settings/AdvancedSystemSettingsWidget.cpp | 50 + .../Settings/AdvancedSystemSettingsWidget.h | 34 + .../Settings/AdvancedSystemSettingsWidget.ui | 197 +++ pcsx2-qt/Settings/BIOSSettingsWidget.cpp | 179 +++ pcsx2-qt/Settings/BIOSSettingsWidget.h | 75 + pcsx2-qt/Settings/BIOSSettingsWidget.ui | 161 ++ pcsx2-qt/Settings/ControllerBindingWidget.ui | 97 ++ .../ControllerBindingWidget_DualShock2.ui | 1296 ++++++++++++++++ .../Settings/ControllerBindingWidgets.cpp | 158 ++ pcsx2-qt/Settings/ControllerBindingWidgets.h | 86 ++ .../ControllerGlobalSettingsWidget.cpp | 64 + .../Settings/ControllerGlobalSettingsWidget.h | 42 + .../ControllerGlobalSettingsWidget.ui | 173 +++ .../Settings/ControllerSettingsDialog.cpp | 119 ++ pcsx2-qt/Settings/ControllerSettingsDialog.h | 74 + pcsx2-qt/Settings/ControllerSettingsDialog.ui | 122 ++ pcsx2-qt/Settings/EmulationSettingsWidget.cpp | 165 ++ pcsx2-qt/Settings/EmulationSettingsWidget.h | 42 + pcsx2-qt/Settings/EmulationSettingsWidget.ui | 178 +++ pcsx2-qt/Settings/GameFixSettingsWidget.cpp | 50 + pcsx2-qt/Settings/GameFixSettingsWidget.h | 34 + pcsx2-qt/Settings/GameFixSettingsWidget.ui | 166 ++ pcsx2-qt/Settings/GameListSettingsWidget.cpp | 244 +++ pcsx2-qt/Settings/GameListSettingsWidget.h | 59 + pcsx2-qt/Settings/GameListSettingsWidget.ui | 213 +++ pcsx2-qt/Settings/GraphicsSettingsWidget.cpp | 381 +++++ pcsx2-qt/Settings/GraphicsSettingsWidget.h | 49 + pcsx2-qt/Settings/GraphicsSettingsWidget.ui | 997 ++++++++++++ pcsx2-qt/Settings/HotkeySettingsWidget.cpp | 84 + pcsx2-qt/Settings/HotkeySettingsWidget.h | 48 + pcsx2-qt/Settings/InputBindingDialog.cpp | 230 +++ pcsx2-qt/Settings/InputBindingDialog.h | 68 + pcsx2-qt/Settings/InputBindingDialog.ui | 76 + pcsx2-qt/Settings/InputBindingWidget.cpp | 371 +++++ pcsx2-qt/Settings/InputBindingWidget.h | 102 ++ pcsx2-qt/Settings/InterfaceSettingsWidget.cpp | 101 ++ pcsx2-qt/Settings/InterfaceSettingsWidget.h | 37 + pcsx2-qt/Settings/InterfaceSettingsWidget.ui | 240 +++ pcsx2-qt/Settings/OSDSettingsWidget.ui | 121 ++ pcsx2-qt/Settings/SettingsDialog.cpp | 156 ++ pcsx2-qt/Settings/SettingsDialog.h | 101 ++ pcsx2-qt/Settings/SettingsDialog.ui | 227 +++ pcsx2-qt/Settings/SystemSettingsWidget.cpp | 88 ++ pcsx2-qt/Settings/SystemSettingsWidget.h | 40 + pcsx2-qt/Settings/SystemSettingsWidget.ui | 301 ++++ pcsx2-qt/pcsx2-qt.vcxproj | 292 ++++ pcsx2-qt/pcsx2-qt.vcxproj.filters | 301 ++++ pcsx2-qt/resources/generate.sh | 13 + pcsx2-qt/resources/icons/AppIcon.png | Bin 0 -> 675 bytes pcsx2-qt/resources/icons/AppIcon@2x.png | Bin 0 -> 1554 bytes pcsx2-qt/resources/icons/AppIconLarge.png | Bin 0 -> 1554 bytes pcsx2-qt/resources/icons/AppIconLarge@2x.png | Bin 0 -> 2380 bytes pcsx2-qt/resources/icons/QT.png | Bin 0 -> 2505 bytes .../resources/icons/address-book-new-22.png | Bin 0 -> 681 bytes .../icons/address-book-new-22@2x.png | Bin 0 -> 2130 bytes .../icons/applications-system-24.png | Bin 0 -> 1151 bytes .../icons/applications-system-24@2x.png | Bin 0 -> 2346 bytes .../icons/black/16/artboard-2-line.png | Bin 0 -> 165 bytes .../icons/black/16/book-open-line.png | Bin 0 -> 147 bytes .../resources/icons/black/16/brush-line.png | Bin 0 -> 210 bytes .../resources/icons/black/16/close-line.png | Bin 0 -> 127 bytes .../icons/black/16/dashboard-line.png | Bin 0 -> 162 bytes .../resources/icons/black/16/disc-line.png | Bin 0 -> 230 bytes .../icons/black/16/door-open-line.png | Bin 0 -> 159 bytes .../icons/black/16/download-2-line.png | Bin 0 -> 146 bytes .../resources/icons/black/16/dvd-line.png | Bin 0 -> 222 bytes .../icons/black/16/file-add-line.png | Bin 0 -> 171 bytes .../resources/icons/black/16/file-line.png | Bin 0 -> 153 bytes .../icons/black/16/file-list-line.png | Bin 0 -> 159 bytes .../icons/black/16/file-reduce-line.png | Bin 0 -> 157 bytes .../icons/black/16/file-search-line.png | Bin 0 -> 199 bytes .../icons/black/16/file-settings-line.png | Bin 0 -> 170 bytes .../resources/icons/black/16/flask-line.png | Bin 0 -> 191 bytes .../icons/black/16/folder-add-line.png | Bin 0 -> 165 bytes .../icons/black/16/folder-open-line.png | Bin 0 -> 184 bytes .../icons/black/16/folder-reduce-line.png | Bin 0 -> 153 bytes .../icons/black/16/folder-settings-line.png | Bin 0 -> 195 bytes .../icons/black/16/fullscreen-line.png | Bin 0 -> 137 bytes .../icons/black/16/function-line.png | Bin 0 -> 142 bytes .../resources/icons/black/16/gamepad-line.png | Bin 0 -> 192 bytes .../icons/black/16/hard-drive-2-line.png | Bin 0 -> 135 bytes .../icons/black/16/keyboard-line.png | Bin 0 -> 135 bytes .../icons/black/16/layout-grid-line.png | Bin 0 -> 145 bytes .../resources/icons/black/16/list-check.png | Bin 0 -> 136 bytes .../resources/icons/black/16/pause-line.png | Bin 0 -> 107 bytes .../resources/icons/black/16/play-line.png | Bin 0 -> 146 bytes .../resources/icons/black/16/refresh-line.png | Bin 0 -> 208 bytes .../resources/icons/black/16/restart-line.png | Bin 0 -> 211 bytes .../resources/icons/black/16/save-3-line.png | Bin 0 -> 153 bytes .../icons/black/16/screenshot-2-line.png | Bin 0 -> 197 bytes .../resources/icons/black/16/sd-card-line.png | Bin 0 -> 144 bytes .../icons/black/16/settings-3-line.png | Bin 0 -> 229 bytes .../icons/black/16/shut-down-line.png | Bin 0 -> 203 bytes .../resources/icons/black/16/tv-2-line.png | Bin 0 -> 136 bytes .../icons/black/16/volume-up-line.png | Bin 0 -> 215 bytes .../icons/black/16/window-2-line.png | Bin 0 -> 141 bytes .../icons/black/24/artboard-2-line.png | Bin 0 -> 112 bytes .../icons/black/24/book-open-line.png | Bin 0 -> 156 bytes .../resources/icons/black/24/brush-line.png | Bin 0 -> 281 bytes .../resources/icons/black/24/close-line.png | Bin 0 -> 154 bytes .../icons/black/24/dashboard-line.png | Bin 0 -> 122 bytes .../resources/icons/black/24/disc-line.png | Bin 0 -> 293 bytes .../icons/black/24/door-open-line.png | Bin 0 -> 155 bytes .../icons/black/24/download-2-line.png | Bin 0 -> 141 bytes .../resources/icons/black/24/dvd-line.png | Bin 0 -> 287 bytes .../icons/black/24/file-add-line.png | Bin 0 -> 140 bytes .../resources/icons/black/24/file-line.png | Bin 0 -> 137 bytes .../icons/black/24/file-list-line.png | Bin 0 -> 119 bytes .../icons/black/24/file-reduce-line.png | Bin 0 -> 138 bytes .../icons/black/24/file-search-line.png | Bin 0 -> 209 bytes .../icons/black/24/file-settings-line.png | Bin 0 -> 198 bytes .../resources/icons/black/24/flask-line.png | Bin 0 -> 206 bytes .../icons/black/24/folder-add-line.png | Bin 0 -> 143 bytes .../icons/black/24/folder-open-line.png | Bin 0 -> 184 bytes .../icons/black/24/folder-reduce-line.png | Bin 0 -> 135 bytes .../icons/black/24/folder-settings-line.png | Bin 0 -> 199 bytes .../icons/black/24/fullscreen-line.png | Bin 0 -> 104 bytes .../icons/black/24/function-line.png | Bin 0 -> 105 bytes .../resources/icons/black/24/gamepad-line.png | Bin 0 -> 200 bytes .../icons/black/24/hard-drive-2-line.png | Bin 0 -> 119 bytes .../icons/black/24/keyboard-line.png | Bin 0 -> 100 bytes .../icons/black/24/layout-grid-line.png | Bin 0 -> 114 bytes .../resources/icons/black/24/list-check.png | Bin 0 -> 108 bytes .../resources/icons/black/24/pause-line.png | Bin 0 -> 94 bytes .../resources/icons/black/24/play-line.png | Bin 0 -> 171 bytes .../resources/icons/black/24/refresh-line.png | Bin 0 -> 276 bytes .../resources/icons/black/24/restart-line.png | Bin 0 -> 265 bytes .../resources/icons/black/24/save-3-line.png | Bin 0 -> 137 bytes .../icons/black/24/screenshot-2-line.png | Bin 0 -> 181 bytes .../resources/icons/black/24/sd-card-line.png | Bin 0 -> 134 bytes .../icons/black/24/settings-3-line.png | Bin 0 -> 289 bytes .../icons/black/24/shut-down-line.png | Bin 0 -> 258 bytes .../icons/black/24/volume-up-line.png | Bin 0 -> 265 bytes .../icons/black/24/window-2-line.png | Bin 0 -> 123 bytes .../icons/black/32/artboard-2-line.png | Bin 0 -> 193 bytes .../icons/black/32/book-open-line.png | Bin 0 -> 202 bytes .../resources/icons/black/32/brush-line.png | Bin 0 -> 337 bytes .../resources/icons/black/32/close-line.png | Bin 0 -> 167 bytes .../icons/black/32/dashboard-line.png | Bin 0 -> 185 bytes .../resources/icons/black/32/disc-line.png | Bin 0 -> 383 bytes .../icons/black/32/door-open-line.png | Bin 0 -> 215 bytes .../icons/black/32/download-2-line.png | Bin 0 -> 179 bytes .../resources/icons/black/32/dvd-line.png | Bin 0 -> 378 bytes .../icons/black/32/file-add-line.png | Bin 0 -> 223 bytes .../resources/icons/black/32/file-line.png | Bin 0 -> 190 bytes .../icons/black/32/file-list-line.png | Bin 0 -> 181 bytes .../icons/black/32/file-reduce-line.png | Bin 0 -> 195 bytes .../icons/black/32/file-search-line.png | Bin 0 -> 290 bytes .../icons/black/32/file-settings-line.png | Bin 0 -> 272 bytes .../resources/icons/black/32/flask-line.png | Bin 0 -> 291 bytes .../icons/black/32/folder-add-line.png | Bin 0 -> 198 bytes .../icons/black/32/folder-open-line.png | Bin 0 -> 258 bytes .../icons/black/32/folder-reduce-line.png | Bin 0 -> 186 bytes .../icons/black/32/folder-settings-line.png | Bin 0 -> 282 bytes .../icons/black/32/fullscreen-line.png | Bin 0 -> 146 bytes .../icons/black/32/function-line.png | Bin 0 -> 161 bytes .../resources/icons/black/32/gamepad-line.png | Bin 0 -> 321 bytes .../icons/black/32/hard-drive-2-line.png | Bin 0 -> 167 bytes .../icons/black/32/keyboard-line.png | Bin 0 -> 159 bytes .../icons/black/32/layout-grid-line.png | Bin 0 -> 170 bytes .../resources/icons/black/32/list-check.png | Bin 0 -> 151 bytes .../resources/icons/black/32/pause-line.png | Bin 0 -> 127 bytes .../resources/icons/black/32/play-line.png | Bin 0 -> 193 bytes .../resources/icons/black/32/refresh-line.png | Bin 0 -> 355 bytes .../resources/icons/black/32/restart-line.png | Bin 0 -> 344 bytes .../resources/icons/black/32/save-3-line.png | Bin 0 -> 187 bytes .../icons/black/32/screenshot-2-line.png | Bin 0 -> 289 bytes .../resources/icons/black/32/sd-card-line.png | Bin 0 -> 180 bytes .../icons/black/32/settings-3-line.png | Bin 0 -> 379 bytes .../icons/black/32/shut-down-line.png | Bin 0 -> 336 bytes .../resources/icons/black/32/tv-2-line.png | Bin 0 -> 166 bytes .../icons/black/32/volume-up-line.png | Bin 0 -> 341 bytes .../icons/black/32/window-2-line.png | Bin 0 -> 163 bytes .../icons/black/48/artboard-2-line.png | Bin 0 -> 138 bytes .../icons/black/48/book-open-line.png | Bin 0 -> 217 bytes .../resources/icons/black/48/brush-line.png | Bin 0 -> 467 bytes .../resources/icons/black/48/close-line.png | Bin 0 -> 206 bytes .../icons/black/48/dashboard-line.png | Bin 0 -> 152 bytes .../resources/icons/black/48/disc-line.png | Bin 0 -> 527 bytes .../icons/black/48/door-open-line.png | Bin 0 -> 214 bytes .../icons/black/48/download-2-line.png | Bin 0 -> 186 bytes .../resources/icons/black/48/dvd-line.png | Bin 0 -> 580 bytes .../icons/black/48/file-add-line.png | Bin 0 -> 179 bytes .../resources/icons/black/48/file-line.png | Bin 0 -> 192 bytes .../icons/black/48/file-list-line.png | Bin 0 -> 158 bytes .../icons/black/48/file-reduce-line.png | Bin 0 -> 176 bytes .../icons/black/48/file-search-line.png | Bin 0 -> 332 bytes .../icons/black/48/file-settings-line.png | Bin 0 -> 306 bytes .../resources/icons/black/48/flask-line.png | Bin 0 -> 324 bytes .../icons/black/48/folder-add-line.png | Bin 0 -> 176 bytes .../icons/black/48/folder-open-line.png | Bin 0 -> 258 bytes .../icons/black/48/folder-reduce-line.png | Bin 0 -> 175 bytes .../icons/black/48/folder-settings-line.png | Bin 0 -> 304 bytes .../icons/black/48/fullscreen-line.png | Bin 0 -> 133 bytes .../icons/black/48/function-line.png | Bin 0 -> 133 bytes .../resources/icons/black/48/gamepad-line.png | Bin 0 -> 341 bytes .../icons/black/48/hard-drive-2-line.png | Bin 0 -> 156 bytes .../icons/black/48/keyboard-line.png | Bin 0 -> 130 bytes .../icons/black/48/layout-grid-line.png | Bin 0 -> 152 bytes .../resources/icons/black/48/list-check.png | Bin 0 -> 127 bytes .../resources/icons/black/48/pause-line.png | Bin 0 -> 120 bytes .../resources/icons/black/48/play-line.png | Bin 0 -> 224 bytes .../resources/icons/black/48/refresh-line.png | Bin 0 -> 488 bytes .../resources/icons/black/48/restart-line.png | Bin 0 -> 474 bytes .../resources/icons/black/48/save-3-line.png | Bin 0 -> 185 bytes .../icons/black/48/screenshot-2-line.png | Bin 0 -> 261 bytes .../resources/icons/black/48/sd-card-line.png | Bin 0 -> 189 bytes .../icons/black/48/settings-3-line.png | Bin 0 -> 573 bytes .../icons/black/48/shut-down-line.png | Bin 0 -> 459 bytes .../icons/black/48/volume-up-line.png | Bin 0 -> 444 bytes .../icons/black/48/window-2-line.png | Bin 0 -> 154 bytes .../icons/black/64/artboard-2-line.png | Bin 0 -> 222 bytes .../icons/black/64/book-open-line.png | Bin 0 -> 291 bytes .../resources/icons/black/64/brush-line.png | Bin 0 -> 646 bytes .../resources/icons/black/64/close-line.png | Bin 0 -> 262 bytes .../icons/black/64/dashboard-line.png | Bin 0 -> 213 bytes .../resources/icons/black/64/disc-line.png | Bin 0 -> 720 bytes .../icons/black/64/door-open-line.png | Bin 0 -> 273 bytes .../icons/black/64/download-2-line.png | Bin 0 -> 256 bytes .../resources/icons/black/64/dvd-line.png | Bin 0 -> 733 bytes .../icons/black/64/file-add-line.png | Bin 0 -> 284 bytes .../resources/icons/black/64/file-line.png | Bin 0 -> 256 bytes .../icons/black/64/file-list-line.png | Bin 0 -> 227 bytes .../icons/black/64/file-reduce-line.png | Bin 0 -> 254 bytes .../icons/black/64/file-search-line.png | Bin 0 -> 461 bytes .../icons/black/64/file-settings-line.png | Bin 0 -> 408 bytes .../resources/icons/black/64/flask-line.png | Bin 0 -> 412 bytes .../icons/black/64/folder-add-line.png | Bin 0 -> 260 bytes .../icons/black/64/folder-open-line.png | Bin 0 -> 356 bytes .../icons/black/64/folder-reduce-line.png | Bin 0 -> 245 bytes .../icons/black/64/folder-settings-line.png | Bin 0 -> 423 bytes .../icons/black/64/fullscreen-line.png | Bin 0 -> 169 bytes .../icons/black/64/function-line.png | Bin 0 -> 211 bytes .../resources/icons/black/64/gamepad-line.png | Bin 0 -> 449 bytes .../icons/black/64/hard-drive-2-line.png | Bin 0 -> 213 bytes .../icons/black/64/keyboard-line.png | Bin 0 -> 175 bytes .../icons/black/64/layout-grid-line.png | Bin 0 -> 218 bytes .../resources/icons/black/64/list-check.png | Bin 0 -> 172 bytes .../resources/icons/black/64/pause-line.png | Bin 0 -> 172 bytes .../resources/icons/black/64/play-line.png | Bin 0 -> 285 bytes .../resources/icons/black/64/refresh-line.png | Bin 0 -> 661 bytes .../resources/icons/black/64/restart-line.png | Bin 0 -> 658 bytes .../resources/icons/black/64/save-3-line.png | Bin 0 -> 244 bytes .../icons/black/64/screenshot-2-line.png | Bin 0 -> 399 bytes .../resources/icons/black/64/sd-card-line.png | Bin 0 -> 240 bytes .../icons/black/64/settings-3-line.png | Bin 0 -> 755 bytes .../icons/black/64/shut-down-line.png | Bin 0 -> 640 bytes .../resources/icons/black/64/tv-2-line.png | Bin 0 -> 210 bytes .../icons/black/64/volume-up-line.png | Bin 0 -> 634 bytes .../icons/black/64/window-2-line.png | Bin 0 -> 208 bytes pcsx2-qt/resources/icons/black/index.theme | 15 + pcsx2-qt/resources/icons/discord.png | Bin 0 -> 1053 bytes pcsx2-qt/resources/icons/flag-eu.png | Bin 0 -> 282 bytes pcsx2-qt/resources/icons/flag-eu.svg | 31 + pcsx2-qt/resources/icons/flag-eu@2x.png | Bin 0 -> 619 bytes pcsx2-qt/resources/icons/flag-jp.png | Bin 0 -> 274 bytes pcsx2-qt/resources/icons/flag-jp.svg | 6 + pcsx2-qt/resources/icons/flag-jp@2x.png | Bin 0 -> 421 bytes pcsx2-qt/resources/icons/flag-other.png | Bin 0 -> 682 bytes pcsx2-qt/resources/icons/flag-other@2x.png | Bin 0 -> 954 bytes pcsx2-qt/resources/icons/flag-us.png | Bin 0 -> 614 bytes pcsx2-qt/resources/icons/flag-us.svg | 28 + pcsx2-qt/resources/icons/flag-us@2x.png | Bin 0 -> 1248 bytes pcsx2-qt/resources/icons/logo.png | Bin 0 -> 2108 bytes pcsx2-qt/resources/icons/media-optical-24.png | Bin 0 -> 1124 bytes .../resources/icons/media-optical-24@2x.png | Bin 0 -> 2470 bytes .../resources/icons/media-optical-gear-24.png | Bin 0 -> 1344 bytes .../icons/media-optical-gear-24@2x.png | Bin 0 -> 2489 bytes pcsx2-qt/resources/icons/media-optical.png | Bin 0 -> 1731 bytes pcsx2-qt/resources/icons/media-optical@2x.png | Bin 0 -> 3250 bytes pcsx2-qt/resources/icons/star-0.png | Bin 0 -> 439 bytes pcsx2-qt/resources/icons/star-1.png | Bin 0 -> 743 bytes pcsx2-qt/resources/icons/star-2.png | Bin 0 -> 897 bytes pcsx2-qt/resources/icons/star-3.png | Bin 0 -> 912 bytes pcsx2-qt/resources/icons/star-4.png | Bin 0 -> 733 bytes pcsx2-qt/resources/icons/star-5.png | Bin 0 -> 422 bytes .../icons/white/16/artboard-2-line.png | Bin 0 -> 163 bytes .../icons/white/16/book-open-line.png | Bin 0 -> 148 bytes .../resources/icons/white/16/brush-line.png | Bin 0 -> 219 bytes .../resources/icons/white/16/close-line.png | Bin 0 -> 130 bytes .../icons/white/16/dashboard-line.png | Bin 0 -> 162 bytes .../resources/icons/white/16/disc-line.png | Bin 0 -> 236 bytes .../icons/white/16/door-open-line.png | Bin 0 -> 163 bytes .../icons/white/16/download-2-line.png | Bin 0 -> 148 bytes .../resources/icons/white/16/dvd-line.png | Bin 0 -> 226 bytes .../icons/white/16/file-add-line.png | Bin 0 -> 174 bytes .../resources/icons/white/16/file-line.png | Bin 0 -> 156 bytes .../icons/white/16/file-list-line.png | Bin 0 -> 161 bytes .../icons/white/16/file-reduce-line.png | Bin 0 -> 157 bytes .../icons/white/16/file-search-line.png | Bin 0 -> 199 bytes .../icons/white/16/file-settings-line.png | Bin 0 -> 173 bytes .../resources/icons/white/16/flask-line.png | Bin 0 -> 199 bytes .../icons/white/16/folder-add-line.png | Bin 0 -> 163 bytes .../icons/white/16/folder-open-line.png | Bin 0 -> 185 bytes .../icons/white/16/folder-reduce-line.png | Bin 0 -> 153 bytes .../icons/white/16/folder-settings-line.png | Bin 0 -> 193 bytes .../icons/white/16/fullscreen-line.png | Bin 0 -> 137 bytes .../icons/white/16/function-line.png | Bin 0 -> 143 bytes .../resources/icons/white/16/gamepad-line.png | Bin 0 -> 195 bytes .../icons/white/16/hard-drive-2-line.png | Bin 0 -> 137 bytes .../icons/white/16/keyboard-line.png | Bin 0 -> 139 bytes .../icons/white/16/layout-grid-line.png | Bin 0 -> 145 bytes .../resources/icons/white/16/list-check.png | Bin 0 -> 139 bytes .../resources/icons/white/16/pause-line.png | Bin 0 -> 110 bytes .../resources/icons/white/16/play-line.png | Bin 0 -> 149 bytes .../resources/icons/white/16/refresh-line.png | Bin 0 -> 218 bytes .../resources/icons/white/16/restart-line.png | Bin 0 -> 217 bytes .../resources/icons/white/16/save-3-line.png | Bin 0 -> 153 bytes .../icons/white/16/screenshot-2-line.png | Bin 0 -> 202 bytes .../resources/icons/white/16/sd-card-line.png | Bin 0 -> 150 bytes .../icons/white/16/settings-3-line.png | Bin 0 -> 238 bytes .../icons/white/16/shut-down-line.png | Bin 0 -> 213 bytes .../resources/icons/white/16/tv-2-line.png | Bin 0 -> 137 bytes .../icons/white/16/volume-up-line.png | Bin 0 -> 222 bytes .../icons/white/16/window-2-line.png | Bin 0 -> 142 bytes .../icons/white/24/artboard-2-line.png | Bin 0 -> 114 bytes .../icons/white/24/book-open-line.png | Bin 0 -> 156 bytes .../resources/icons/white/24/brush-line.png | Bin 0 -> 276 bytes .../resources/icons/white/24/close-line.png | Bin 0 -> 154 bytes .../icons/white/24/dashboard-line.png | Bin 0 -> 126 bytes .../resources/icons/white/24/disc-line.png | Bin 0 -> 289 bytes .../icons/white/24/door-open-line.png | Bin 0 -> 158 bytes .../icons/white/24/download-2-line.png | Bin 0 -> 140 bytes .../resources/icons/white/24/dvd-line.png | Bin 0 -> 282 bytes .../icons/white/24/file-add-line.png | Bin 0 -> 140 bytes .../resources/icons/white/24/file-line.png | Bin 0 -> 137 bytes .../icons/white/24/file-list-line.png | Bin 0 -> 119 bytes .../icons/white/24/file-reduce-line.png | Bin 0 -> 138 bytes .../icons/white/24/file-search-line.png | Bin 0 -> 209 bytes .../icons/white/24/file-settings-line.png | Bin 0 -> 195 bytes .../resources/icons/white/24/flask-line.png | Bin 0 -> 209 bytes .../icons/white/24/folder-add-line.png | Bin 0 -> 142 bytes .../icons/white/24/folder-open-line.png | Bin 0 -> 193 bytes .../icons/white/24/folder-reduce-line.png | Bin 0 -> 135 bytes .../icons/white/24/folder-settings-line.png | Bin 0 -> 194 bytes .../icons/white/24/fullscreen-line.png | Bin 0 -> 111 bytes .../icons/white/24/function-line.png | Bin 0 -> 109 bytes .../resources/icons/white/24/gamepad-line.png | Bin 0 -> 198 bytes .../icons/white/24/hard-drive-2-line.png | Bin 0 -> 119 bytes .../icons/white/24/keyboard-line.png | Bin 0 -> 108 bytes .../icons/white/24/layout-grid-line.png | Bin 0 -> 114 bytes .../resources/icons/white/24/list-check.png | Bin 0 -> 110 bytes .../resources/icons/white/24/pause-line.png | Bin 0 -> 101 bytes .../resources/icons/white/24/play-line.png | Bin 0 -> 173 bytes .../resources/icons/white/24/refresh-line.png | Bin 0 -> 273 bytes .../resources/icons/white/24/restart-line.png | Bin 0 -> 265 bytes .../resources/icons/white/24/save-3-line.png | Bin 0 -> 138 bytes .../icons/white/24/screenshot-2-line.png | Bin 0 -> 180 bytes .../resources/icons/white/24/sd-card-line.png | Bin 0 -> 135 bytes .../icons/white/24/settings-3-line.png | Bin 0 -> 285 bytes .../icons/white/24/shut-down-line.png | Bin 0 -> 252 bytes .../resources/icons/white/24/tv-2-line.png | Bin 0 -> 117 bytes .../icons/white/24/volume-up-line.png | Bin 0 -> 271 bytes .../icons/white/24/window-2-line.png | Bin 0 -> 122 bytes .../icons/white/32/artboard-2-line.png | Bin 0 -> 196 bytes .../icons/white/32/book-open-line.png | Bin 0 -> 202 bytes .../resources/icons/white/32/brush-line.png | Bin 0 -> 339 bytes .../resources/icons/white/32/close-line.png | Bin 0 -> 170 bytes .../icons/white/32/dashboard-line.png | Bin 0 -> 186 bytes .../resources/icons/white/32/disc-line.png | Bin 0 -> 385 bytes .../icons/white/32/door-open-line.png | Bin 0 -> 215 bytes .../icons/white/32/download-2-line.png | Bin 0 -> 180 bytes .../resources/icons/white/32/dvd-line.png | Bin 0 -> 382 bytes .../icons/white/32/file-add-line.png | Bin 0 -> 222 bytes .../resources/icons/white/32/file-line.png | Bin 0 -> 192 bytes .../icons/white/32/file-list-line.png | Bin 0 -> 182 bytes .../icons/white/32/file-reduce-line.png | Bin 0 -> 195 bytes .../icons/white/32/file-search-line.png | Bin 0 -> 288 bytes .../icons/white/32/file-settings-line.png | Bin 0 -> 267 bytes .../resources/icons/white/32/flask-line.png | Bin 0 -> 289 bytes .../icons/white/32/folder-add-line.png | Bin 0 -> 198 bytes .../icons/white/32/folder-open-line.png | Bin 0 -> 255 bytes .../icons/white/32/folder-reduce-line.png | Bin 0 -> 184 bytes .../icons/white/32/folder-settings-line.png | Bin 0 -> 277 bytes .../icons/white/32/fullscreen-line.png | Bin 0 -> 146 bytes .../icons/white/32/function-line.png | Bin 0 -> 163 bytes .../resources/icons/white/32/gamepad-line.png | Bin 0 -> 322 bytes .../icons/white/32/hard-drive-2-line.png | Bin 0 -> 167 bytes .../icons/white/32/keyboard-line.png | Bin 0 -> 162 bytes .../icons/white/32/layout-grid-line.png | Bin 0 -> 170 bytes .../resources/icons/white/32/list-check.png | Bin 0 -> 156 bytes .../resources/icons/white/32/pause-line.png | Bin 0 -> 129 bytes .../resources/icons/white/32/play-line.png | Bin 0 -> 194 bytes .../resources/icons/white/32/refresh-line.png | Bin 0 -> 355 bytes .../resources/icons/white/32/restart-line.png | Bin 0 -> 350 bytes .../resources/icons/white/32/save-3-line.png | Bin 0 -> 184 bytes .../icons/white/32/screenshot-2-line.png | Bin 0 -> 289 bytes .../resources/icons/white/32/sd-card-line.png | Bin 0 -> 183 bytes .../icons/white/32/settings-3-line.png | Bin 0 -> 380 bytes .../icons/white/32/shut-down-line.png | Bin 0 -> 328 bytes .../resources/icons/white/32/tv-2-line.png | Bin 0 -> 168 bytes .../icons/white/32/volume-up-line.png | Bin 0 -> 338 bytes .../icons/white/32/window-2-line.png | Bin 0 -> 163 bytes .../icons/white/48/artboard-2-line.png | Bin 0 -> 121 bytes .../icons/white/48/book-open-line.png | Bin 0 -> 214 bytes .../resources/icons/white/48/brush-line.png | Bin 0 -> 470 bytes .../resources/icons/white/48/close-line.png | Bin 0 -> 206 bytes .../icons/white/48/dashboard-line.png | Bin 0 -> 135 bytes .../resources/icons/white/48/disc-line.png | Bin 0 -> 534 bytes .../icons/white/48/door-open-line.png | Bin 0 -> 210 bytes .../icons/white/48/download-2-line.png | Bin 0 -> 186 bytes .../resources/icons/white/48/dvd-line.png | Bin 0 -> 580 bytes .../icons/white/48/file-add-line.png | Bin 0 -> 178 bytes .../resources/icons/white/48/file-line.png | Bin 0 -> 192 bytes .../icons/white/48/file-list-line.png | Bin 0 -> 157 bytes .../icons/white/48/file-reduce-line.png | Bin 0 -> 175 bytes .../icons/white/48/file-search-line.png | Bin 0 -> 326 bytes .../icons/white/48/file-settings-line.png | Bin 0 -> 302 bytes .../resources/icons/white/48/flask-line.png | Bin 0 -> 325 bytes .../icons/white/48/folder-add-line.png | Bin 0 -> 175 bytes .../icons/white/48/folder-open-line.png | Bin 0 -> 254 bytes .../icons/white/48/folder-reduce-line.png | Bin 0 -> 172 bytes .../icons/white/48/folder-settings-line.png | Bin 0 -> 297 bytes .../icons/white/48/fullscreen-line.png | Bin 0 -> 116 bytes .../icons/white/48/function-line.png | Bin 0 -> 116 bytes .../resources/icons/white/48/gamepad-line.png | Bin 0 -> 337 bytes .../icons/white/48/hard-drive-2-line.png | Bin 0 -> 155 bytes .../icons/white/48/keyboard-line.png | Bin 0 -> 113 bytes .../icons/white/48/layout-grid-line.png | Bin 0 -> 150 bytes .../resources/icons/white/48/list-check.png | Bin 0 -> 110 bytes .../resources/icons/white/48/pause-line.png | Bin 0 -> 103 bytes .../resources/icons/white/48/play-line.png | Bin 0 -> 226 bytes .../resources/icons/white/48/refresh-line.png | Bin 0 -> 484 bytes .../resources/icons/white/48/restart-line.png | Bin 0 -> 475 bytes .../resources/icons/white/48/save-3-line.png | Bin 0 -> 182 bytes .../icons/white/48/screenshot-2-line.png | Bin 0 -> 253 bytes .../resources/icons/white/48/sd-card-line.png | Bin 0 -> 190 bytes .../icons/white/48/settings-3-line.png | Bin 0 -> 573 bytes .../icons/white/48/shut-down-line.png | Bin 0 -> 456 bytes .../resources/icons/white/48/tv-2-line.png | Bin 0 -> 147 bytes .../icons/white/48/volume-up-line.png | Bin 0 -> 450 bytes .../icons/white/48/window-2-line.png | Bin 0 -> 152 bytes .../icons/white/64/artboard-2-line.png | Bin 0 -> 226 bytes .../icons/white/64/book-open-line.png | Bin 0 -> 288 bytes .../resources/icons/white/64/brush-line.png | Bin 0 -> 646 bytes .../resources/icons/white/64/close-line.png | Bin 0 -> 262 bytes .../icons/white/64/dashboard-line.png | Bin 0 -> 214 bytes .../resources/icons/white/64/disc-line.png | Bin 0 -> 720 bytes .../icons/white/64/door-open-line.png | Bin 0 -> 273 bytes .../icons/white/64/download-2-line.png | Bin 0 -> 255 bytes .../resources/icons/white/64/dvd-line.png | Bin 0 -> 733 bytes .../icons/white/64/file-add-line.png | Bin 0 -> 284 bytes .../resources/icons/white/64/file-line.png | Bin 0 -> 259 bytes .../icons/white/64/file-list-line.png | Bin 0 -> 228 bytes .../icons/white/64/file-reduce-line.png | Bin 0 -> 254 bytes .../icons/white/64/file-search-line.png | Bin 0 -> 457 bytes .../icons/white/64/file-settings-line.png | Bin 0 -> 405 bytes .../resources/icons/white/64/flask-line.png | Bin 0 -> 410 bytes .../icons/white/64/folder-add-line.png | Bin 0 -> 257 bytes .../icons/white/64/folder-open-line.png | Bin 0 -> 354 bytes .../icons/white/64/folder-reduce-line.png | Bin 0 -> 244 bytes .../icons/white/64/folder-settings-line.png | Bin 0 -> 416 bytes .../icons/white/64/fullscreen-line.png | Bin 0 -> 171 bytes .../icons/white/64/function-line.png | Bin 0 -> 211 bytes .../resources/icons/white/64/gamepad-line.png | Bin 0 -> 449 bytes .../icons/white/64/hard-drive-2-line.png | Bin 0 -> 212 bytes .../icons/white/64/keyboard-line.png | Bin 0 -> 181 bytes .../icons/white/64/layout-grid-line.png | Bin 0 -> 218 bytes .../resources/icons/white/64/list-check.png | Bin 0 -> 175 bytes .../resources/icons/white/64/pause-line.png | Bin 0 -> 172 bytes .../resources/icons/white/64/play-line.png | Bin 0 -> 289 bytes .../resources/icons/white/64/refresh-line.png | Bin 0 -> 661 bytes .../resources/icons/white/64/restart-line.png | Bin 0 -> 658 bytes .../resources/icons/white/64/save-3-line.png | Bin 0 -> 246 bytes .../icons/white/64/screenshot-2-line.png | Bin 0 -> 399 bytes .../resources/icons/white/64/sd-card-line.png | Bin 0 -> 242 bytes .../icons/white/64/settings-3-line.png | Bin 0 -> 755 bytes .../icons/white/64/shut-down-line.png | Bin 0 -> 640 bytes .../resources/icons/white/64/tv-2-line.png | Bin 0 -> 211 bytes .../icons/white/64/volume-up-line.png | Bin 0 -> 634 bytes .../icons/white/64/window-2-line.png | Bin 0 -> 206 bytes pcsx2-qt/resources/icons/white/index.theme | 15 + pcsx2-qt/resources/images/dualshock-2.png | Bin 0 -> 16319 bytes pcsx2-qt/resources/images/dualshock-2@2x.png | Bin 0 -> 37601 bytes pcsx2-qt/resources/resources.qrc | 430 ++++++ 509 files changed, 17724 insertions(+) create mode 100644 3rdparty/qt/.gitignore create mode 100644 3rdparty/qt/README.md create mode 100644 PCSX2_qt.sln create mode 100644 bin/resources/cover-placeholder.png create mode 100644 common/vsprops/QtCompile.props create mode 100644 common/vsprops/QtCompile.targets create mode 100644 common/vsprops/QtCompile.xml create mode 100644 pcsx2-qt/AboutDialog.cpp create mode 100644 pcsx2-qt/AboutDialog.h create mode 100644 pcsx2-qt/AboutDialog.ui create mode 100644 pcsx2-qt/CMakeLists.txt create mode 100644 pcsx2-qt/DisplayWidget.cpp create mode 100644 pcsx2-qt/DisplayWidget.h create mode 100644 pcsx2-qt/EmuThread.cpp create mode 100644 pcsx2-qt/EmuThread.h create mode 100644 pcsx2-qt/GameList/GameListModel.cpp create mode 100644 pcsx2-qt/GameList/GameListModel.h create mode 100644 pcsx2-qt/GameList/GameListRefreshThread.cpp create mode 100644 pcsx2-qt/GameList/GameListRefreshThread.h create mode 100644 pcsx2-qt/GameList/GameListWidget.cpp create mode 100644 pcsx2-qt/GameList/GameListWidget.h create mode 100644 pcsx2-qt/Main.cpp create mode 100644 pcsx2-qt/MainWindow.cpp create mode 100644 pcsx2-qt/MainWindow.h create mode 100644 pcsx2-qt/MainWindow.ui create mode 100644 pcsx2-qt/PrecompiledHeader.cpp create mode 100644 pcsx2-qt/PrecompiledHeader.h create mode 100644 pcsx2-qt/QtHost.cpp create mode 100644 pcsx2-qt/QtHost.h create mode 100644 pcsx2-qt/QtKeyCodes.cpp create mode 100644 pcsx2-qt/QtUtils.cpp create mode 100644 pcsx2-qt/QtUtils.h create mode 100644 pcsx2-qt/SettingWidgetBinder.h create mode 100644 pcsx2-qt/Settings/AdvancedSystemSettingsWidget.cpp create mode 100644 pcsx2-qt/Settings/AdvancedSystemSettingsWidget.h create mode 100644 pcsx2-qt/Settings/AdvancedSystemSettingsWidget.ui create mode 100644 pcsx2-qt/Settings/BIOSSettingsWidget.cpp create mode 100644 pcsx2-qt/Settings/BIOSSettingsWidget.h create mode 100644 pcsx2-qt/Settings/BIOSSettingsWidget.ui create mode 100644 pcsx2-qt/Settings/ControllerBindingWidget.ui create mode 100644 pcsx2-qt/Settings/ControllerBindingWidget_DualShock2.ui create mode 100644 pcsx2-qt/Settings/ControllerBindingWidgets.cpp create mode 100644 pcsx2-qt/Settings/ControllerBindingWidgets.h create mode 100644 pcsx2-qt/Settings/ControllerGlobalSettingsWidget.cpp create mode 100644 pcsx2-qt/Settings/ControllerGlobalSettingsWidget.h create mode 100644 pcsx2-qt/Settings/ControllerGlobalSettingsWidget.ui create mode 100644 pcsx2-qt/Settings/ControllerSettingsDialog.cpp create mode 100644 pcsx2-qt/Settings/ControllerSettingsDialog.h create mode 100644 pcsx2-qt/Settings/ControllerSettingsDialog.ui create mode 100644 pcsx2-qt/Settings/EmulationSettingsWidget.cpp create mode 100644 pcsx2-qt/Settings/EmulationSettingsWidget.h create mode 100644 pcsx2-qt/Settings/EmulationSettingsWidget.ui create mode 100644 pcsx2-qt/Settings/GameFixSettingsWidget.cpp create mode 100644 pcsx2-qt/Settings/GameFixSettingsWidget.h create mode 100644 pcsx2-qt/Settings/GameFixSettingsWidget.ui create mode 100644 pcsx2-qt/Settings/GameListSettingsWidget.cpp create mode 100644 pcsx2-qt/Settings/GameListSettingsWidget.h create mode 100644 pcsx2-qt/Settings/GameListSettingsWidget.ui create mode 100644 pcsx2-qt/Settings/GraphicsSettingsWidget.cpp create mode 100644 pcsx2-qt/Settings/GraphicsSettingsWidget.h create mode 100644 pcsx2-qt/Settings/GraphicsSettingsWidget.ui create mode 100644 pcsx2-qt/Settings/HotkeySettingsWidget.cpp create mode 100644 pcsx2-qt/Settings/HotkeySettingsWidget.h create mode 100644 pcsx2-qt/Settings/InputBindingDialog.cpp create mode 100644 pcsx2-qt/Settings/InputBindingDialog.h create mode 100644 pcsx2-qt/Settings/InputBindingDialog.ui create mode 100644 pcsx2-qt/Settings/InputBindingWidget.cpp create mode 100644 pcsx2-qt/Settings/InputBindingWidget.h create mode 100644 pcsx2-qt/Settings/InterfaceSettingsWidget.cpp create mode 100644 pcsx2-qt/Settings/InterfaceSettingsWidget.h create mode 100644 pcsx2-qt/Settings/InterfaceSettingsWidget.ui create mode 100644 pcsx2-qt/Settings/OSDSettingsWidget.ui create mode 100644 pcsx2-qt/Settings/SettingsDialog.cpp create mode 100644 pcsx2-qt/Settings/SettingsDialog.h create mode 100644 pcsx2-qt/Settings/SettingsDialog.ui create mode 100644 pcsx2-qt/Settings/SystemSettingsWidget.cpp create mode 100644 pcsx2-qt/Settings/SystemSettingsWidget.h create mode 100644 pcsx2-qt/Settings/SystemSettingsWidget.ui create mode 100644 pcsx2-qt/pcsx2-qt.vcxproj create mode 100644 pcsx2-qt/pcsx2-qt.vcxproj.filters create mode 100644 pcsx2-qt/resources/generate.sh create mode 100644 pcsx2-qt/resources/icons/AppIcon.png create mode 100644 pcsx2-qt/resources/icons/AppIcon@2x.png create mode 100644 pcsx2-qt/resources/icons/AppIconLarge.png create mode 100644 pcsx2-qt/resources/icons/AppIconLarge@2x.png create mode 100644 pcsx2-qt/resources/icons/QT.png create mode 100644 pcsx2-qt/resources/icons/address-book-new-22.png create mode 100644 pcsx2-qt/resources/icons/address-book-new-22@2x.png create mode 100644 pcsx2-qt/resources/icons/applications-system-24.png create mode 100644 pcsx2-qt/resources/icons/applications-system-24@2x.png create mode 100644 pcsx2-qt/resources/icons/black/16/artboard-2-line.png create mode 100644 pcsx2-qt/resources/icons/black/16/book-open-line.png create mode 100644 pcsx2-qt/resources/icons/black/16/brush-line.png create mode 100644 pcsx2-qt/resources/icons/black/16/close-line.png create mode 100644 pcsx2-qt/resources/icons/black/16/dashboard-line.png create mode 100644 pcsx2-qt/resources/icons/black/16/disc-line.png create mode 100644 pcsx2-qt/resources/icons/black/16/door-open-line.png create mode 100644 pcsx2-qt/resources/icons/black/16/download-2-line.png create mode 100644 pcsx2-qt/resources/icons/black/16/dvd-line.png create mode 100644 pcsx2-qt/resources/icons/black/16/file-add-line.png create mode 100644 pcsx2-qt/resources/icons/black/16/file-line.png create mode 100644 pcsx2-qt/resources/icons/black/16/file-list-line.png create mode 100644 pcsx2-qt/resources/icons/black/16/file-reduce-line.png create mode 100644 pcsx2-qt/resources/icons/black/16/file-search-line.png create mode 100644 pcsx2-qt/resources/icons/black/16/file-settings-line.png create mode 100644 pcsx2-qt/resources/icons/black/16/flask-line.png create mode 100644 pcsx2-qt/resources/icons/black/16/folder-add-line.png create mode 100644 pcsx2-qt/resources/icons/black/16/folder-open-line.png create mode 100644 pcsx2-qt/resources/icons/black/16/folder-reduce-line.png create mode 100644 pcsx2-qt/resources/icons/black/16/folder-settings-line.png create mode 100644 pcsx2-qt/resources/icons/black/16/fullscreen-line.png create mode 100644 pcsx2-qt/resources/icons/black/16/function-line.png create mode 100644 pcsx2-qt/resources/icons/black/16/gamepad-line.png create mode 100644 pcsx2-qt/resources/icons/black/16/hard-drive-2-line.png create mode 100644 pcsx2-qt/resources/icons/black/16/keyboard-line.png create mode 100644 pcsx2-qt/resources/icons/black/16/layout-grid-line.png create mode 100644 pcsx2-qt/resources/icons/black/16/list-check.png create mode 100644 pcsx2-qt/resources/icons/black/16/pause-line.png create mode 100644 pcsx2-qt/resources/icons/black/16/play-line.png create mode 100644 pcsx2-qt/resources/icons/black/16/refresh-line.png create mode 100644 pcsx2-qt/resources/icons/black/16/restart-line.png create mode 100644 pcsx2-qt/resources/icons/black/16/save-3-line.png create mode 100644 pcsx2-qt/resources/icons/black/16/screenshot-2-line.png create mode 100644 pcsx2-qt/resources/icons/black/16/sd-card-line.png create mode 100644 pcsx2-qt/resources/icons/black/16/settings-3-line.png create mode 100644 pcsx2-qt/resources/icons/black/16/shut-down-line.png create mode 100644 pcsx2-qt/resources/icons/black/16/tv-2-line.png create mode 100644 pcsx2-qt/resources/icons/black/16/volume-up-line.png create mode 100644 pcsx2-qt/resources/icons/black/16/window-2-line.png create mode 100644 pcsx2-qt/resources/icons/black/24/artboard-2-line.png create mode 100644 pcsx2-qt/resources/icons/black/24/book-open-line.png create mode 100644 pcsx2-qt/resources/icons/black/24/brush-line.png create mode 100644 pcsx2-qt/resources/icons/black/24/close-line.png create mode 100644 pcsx2-qt/resources/icons/black/24/dashboard-line.png create mode 100644 pcsx2-qt/resources/icons/black/24/disc-line.png create mode 100644 pcsx2-qt/resources/icons/black/24/door-open-line.png create mode 100644 pcsx2-qt/resources/icons/black/24/download-2-line.png create mode 100644 pcsx2-qt/resources/icons/black/24/dvd-line.png create mode 100644 pcsx2-qt/resources/icons/black/24/file-add-line.png create mode 100644 pcsx2-qt/resources/icons/black/24/file-line.png create mode 100644 pcsx2-qt/resources/icons/black/24/file-list-line.png create mode 100644 pcsx2-qt/resources/icons/black/24/file-reduce-line.png create mode 100644 pcsx2-qt/resources/icons/black/24/file-search-line.png create mode 100644 pcsx2-qt/resources/icons/black/24/file-settings-line.png create mode 100644 pcsx2-qt/resources/icons/black/24/flask-line.png create mode 100644 pcsx2-qt/resources/icons/black/24/folder-add-line.png create mode 100644 pcsx2-qt/resources/icons/black/24/folder-open-line.png create mode 100644 pcsx2-qt/resources/icons/black/24/folder-reduce-line.png create mode 100644 pcsx2-qt/resources/icons/black/24/folder-settings-line.png create mode 100644 pcsx2-qt/resources/icons/black/24/fullscreen-line.png create mode 100644 pcsx2-qt/resources/icons/black/24/function-line.png create mode 100644 pcsx2-qt/resources/icons/black/24/gamepad-line.png create mode 100644 pcsx2-qt/resources/icons/black/24/hard-drive-2-line.png create mode 100644 pcsx2-qt/resources/icons/black/24/keyboard-line.png create mode 100644 pcsx2-qt/resources/icons/black/24/layout-grid-line.png create mode 100644 pcsx2-qt/resources/icons/black/24/list-check.png create mode 100644 pcsx2-qt/resources/icons/black/24/pause-line.png create mode 100644 pcsx2-qt/resources/icons/black/24/play-line.png create mode 100644 pcsx2-qt/resources/icons/black/24/refresh-line.png create mode 100644 pcsx2-qt/resources/icons/black/24/restart-line.png create mode 100644 pcsx2-qt/resources/icons/black/24/save-3-line.png create mode 100644 pcsx2-qt/resources/icons/black/24/screenshot-2-line.png create mode 100644 pcsx2-qt/resources/icons/black/24/sd-card-line.png create mode 100644 pcsx2-qt/resources/icons/black/24/settings-3-line.png create mode 100644 pcsx2-qt/resources/icons/black/24/shut-down-line.png create mode 100644 pcsx2-qt/resources/icons/black/24/volume-up-line.png create mode 100644 pcsx2-qt/resources/icons/black/24/window-2-line.png create mode 100644 pcsx2-qt/resources/icons/black/32/artboard-2-line.png create mode 100644 pcsx2-qt/resources/icons/black/32/book-open-line.png create mode 100644 pcsx2-qt/resources/icons/black/32/brush-line.png create mode 100644 pcsx2-qt/resources/icons/black/32/close-line.png create mode 100644 pcsx2-qt/resources/icons/black/32/dashboard-line.png create mode 100644 pcsx2-qt/resources/icons/black/32/disc-line.png create mode 100644 pcsx2-qt/resources/icons/black/32/door-open-line.png create mode 100644 pcsx2-qt/resources/icons/black/32/download-2-line.png create mode 100644 pcsx2-qt/resources/icons/black/32/dvd-line.png create mode 100644 pcsx2-qt/resources/icons/black/32/file-add-line.png create mode 100644 pcsx2-qt/resources/icons/black/32/file-line.png create mode 100644 pcsx2-qt/resources/icons/black/32/file-list-line.png create mode 100644 pcsx2-qt/resources/icons/black/32/file-reduce-line.png create mode 100644 pcsx2-qt/resources/icons/black/32/file-search-line.png create mode 100644 pcsx2-qt/resources/icons/black/32/file-settings-line.png create mode 100644 pcsx2-qt/resources/icons/black/32/flask-line.png create mode 100644 pcsx2-qt/resources/icons/black/32/folder-add-line.png create mode 100644 pcsx2-qt/resources/icons/black/32/folder-open-line.png create mode 100644 pcsx2-qt/resources/icons/black/32/folder-reduce-line.png create mode 100644 pcsx2-qt/resources/icons/black/32/folder-settings-line.png create mode 100644 pcsx2-qt/resources/icons/black/32/fullscreen-line.png create mode 100644 pcsx2-qt/resources/icons/black/32/function-line.png create mode 100644 pcsx2-qt/resources/icons/black/32/gamepad-line.png create mode 100644 pcsx2-qt/resources/icons/black/32/hard-drive-2-line.png create mode 100644 pcsx2-qt/resources/icons/black/32/keyboard-line.png create mode 100644 pcsx2-qt/resources/icons/black/32/layout-grid-line.png create mode 100644 pcsx2-qt/resources/icons/black/32/list-check.png create mode 100644 pcsx2-qt/resources/icons/black/32/pause-line.png create mode 100644 pcsx2-qt/resources/icons/black/32/play-line.png create mode 100644 pcsx2-qt/resources/icons/black/32/refresh-line.png create mode 100644 pcsx2-qt/resources/icons/black/32/restart-line.png create mode 100644 pcsx2-qt/resources/icons/black/32/save-3-line.png create mode 100644 pcsx2-qt/resources/icons/black/32/screenshot-2-line.png create mode 100644 pcsx2-qt/resources/icons/black/32/sd-card-line.png create mode 100644 pcsx2-qt/resources/icons/black/32/settings-3-line.png create mode 100644 pcsx2-qt/resources/icons/black/32/shut-down-line.png create mode 100644 pcsx2-qt/resources/icons/black/32/tv-2-line.png create mode 100644 pcsx2-qt/resources/icons/black/32/volume-up-line.png create mode 100644 pcsx2-qt/resources/icons/black/32/window-2-line.png create mode 100644 pcsx2-qt/resources/icons/black/48/artboard-2-line.png create mode 100644 pcsx2-qt/resources/icons/black/48/book-open-line.png create mode 100644 pcsx2-qt/resources/icons/black/48/brush-line.png create mode 100644 pcsx2-qt/resources/icons/black/48/close-line.png create mode 100644 pcsx2-qt/resources/icons/black/48/dashboard-line.png create mode 100644 pcsx2-qt/resources/icons/black/48/disc-line.png create mode 100644 pcsx2-qt/resources/icons/black/48/door-open-line.png create mode 100644 pcsx2-qt/resources/icons/black/48/download-2-line.png create mode 100644 pcsx2-qt/resources/icons/black/48/dvd-line.png create mode 100644 pcsx2-qt/resources/icons/black/48/file-add-line.png create mode 100644 pcsx2-qt/resources/icons/black/48/file-line.png create mode 100644 pcsx2-qt/resources/icons/black/48/file-list-line.png create mode 100644 pcsx2-qt/resources/icons/black/48/file-reduce-line.png create mode 100644 pcsx2-qt/resources/icons/black/48/file-search-line.png create mode 100644 pcsx2-qt/resources/icons/black/48/file-settings-line.png create mode 100644 pcsx2-qt/resources/icons/black/48/flask-line.png create mode 100644 pcsx2-qt/resources/icons/black/48/folder-add-line.png create mode 100644 pcsx2-qt/resources/icons/black/48/folder-open-line.png create mode 100644 pcsx2-qt/resources/icons/black/48/folder-reduce-line.png create mode 100644 pcsx2-qt/resources/icons/black/48/folder-settings-line.png create mode 100644 pcsx2-qt/resources/icons/black/48/fullscreen-line.png create mode 100644 pcsx2-qt/resources/icons/black/48/function-line.png create mode 100644 pcsx2-qt/resources/icons/black/48/gamepad-line.png create mode 100644 pcsx2-qt/resources/icons/black/48/hard-drive-2-line.png create mode 100644 pcsx2-qt/resources/icons/black/48/keyboard-line.png create mode 100644 pcsx2-qt/resources/icons/black/48/layout-grid-line.png create mode 100644 pcsx2-qt/resources/icons/black/48/list-check.png create mode 100644 pcsx2-qt/resources/icons/black/48/pause-line.png create mode 100644 pcsx2-qt/resources/icons/black/48/play-line.png create mode 100644 pcsx2-qt/resources/icons/black/48/refresh-line.png create mode 100644 pcsx2-qt/resources/icons/black/48/restart-line.png create mode 100644 pcsx2-qt/resources/icons/black/48/save-3-line.png create mode 100644 pcsx2-qt/resources/icons/black/48/screenshot-2-line.png create mode 100644 pcsx2-qt/resources/icons/black/48/sd-card-line.png create mode 100644 pcsx2-qt/resources/icons/black/48/settings-3-line.png create mode 100644 pcsx2-qt/resources/icons/black/48/shut-down-line.png create mode 100644 pcsx2-qt/resources/icons/black/48/volume-up-line.png create mode 100644 pcsx2-qt/resources/icons/black/48/window-2-line.png create mode 100644 pcsx2-qt/resources/icons/black/64/artboard-2-line.png create mode 100644 pcsx2-qt/resources/icons/black/64/book-open-line.png create mode 100644 pcsx2-qt/resources/icons/black/64/brush-line.png create mode 100644 pcsx2-qt/resources/icons/black/64/close-line.png create mode 100644 pcsx2-qt/resources/icons/black/64/dashboard-line.png create mode 100644 pcsx2-qt/resources/icons/black/64/disc-line.png create mode 100644 pcsx2-qt/resources/icons/black/64/door-open-line.png create mode 100644 pcsx2-qt/resources/icons/black/64/download-2-line.png create mode 100644 pcsx2-qt/resources/icons/black/64/dvd-line.png create mode 100644 pcsx2-qt/resources/icons/black/64/file-add-line.png create mode 100644 pcsx2-qt/resources/icons/black/64/file-line.png create mode 100644 pcsx2-qt/resources/icons/black/64/file-list-line.png create mode 100644 pcsx2-qt/resources/icons/black/64/file-reduce-line.png create mode 100644 pcsx2-qt/resources/icons/black/64/file-search-line.png create mode 100644 pcsx2-qt/resources/icons/black/64/file-settings-line.png create mode 100644 pcsx2-qt/resources/icons/black/64/flask-line.png create mode 100644 pcsx2-qt/resources/icons/black/64/folder-add-line.png create mode 100644 pcsx2-qt/resources/icons/black/64/folder-open-line.png create mode 100644 pcsx2-qt/resources/icons/black/64/folder-reduce-line.png create mode 100644 pcsx2-qt/resources/icons/black/64/folder-settings-line.png create mode 100644 pcsx2-qt/resources/icons/black/64/fullscreen-line.png create mode 100644 pcsx2-qt/resources/icons/black/64/function-line.png create mode 100644 pcsx2-qt/resources/icons/black/64/gamepad-line.png create mode 100644 pcsx2-qt/resources/icons/black/64/hard-drive-2-line.png create mode 100644 pcsx2-qt/resources/icons/black/64/keyboard-line.png create mode 100644 pcsx2-qt/resources/icons/black/64/layout-grid-line.png create mode 100644 pcsx2-qt/resources/icons/black/64/list-check.png create mode 100644 pcsx2-qt/resources/icons/black/64/pause-line.png create mode 100644 pcsx2-qt/resources/icons/black/64/play-line.png create mode 100644 pcsx2-qt/resources/icons/black/64/refresh-line.png create mode 100644 pcsx2-qt/resources/icons/black/64/restart-line.png create mode 100644 pcsx2-qt/resources/icons/black/64/save-3-line.png create mode 100644 pcsx2-qt/resources/icons/black/64/screenshot-2-line.png create mode 100644 pcsx2-qt/resources/icons/black/64/sd-card-line.png create mode 100644 pcsx2-qt/resources/icons/black/64/settings-3-line.png create mode 100644 pcsx2-qt/resources/icons/black/64/shut-down-line.png create mode 100644 pcsx2-qt/resources/icons/black/64/tv-2-line.png create mode 100644 pcsx2-qt/resources/icons/black/64/volume-up-line.png create mode 100644 pcsx2-qt/resources/icons/black/64/window-2-line.png create mode 100644 pcsx2-qt/resources/icons/black/index.theme create mode 100644 pcsx2-qt/resources/icons/discord.png create mode 100644 pcsx2-qt/resources/icons/flag-eu.png create mode 100644 pcsx2-qt/resources/icons/flag-eu.svg create mode 100644 pcsx2-qt/resources/icons/flag-eu@2x.png create mode 100644 pcsx2-qt/resources/icons/flag-jp.png create mode 100644 pcsx2-qt/resources/icons/flag-jp.svg create mode 100644 pcsx2-qt/resources/icons/flag-jp@2x.png create mode 100644 pcsx2-qt/resources/icons/flag-other.png create mode 100644 pcsx2-qt/resources/icons/flag-other@2x.png create mode 100644 pcsx2-qt/resources/icons/flag-us.png create mode 100644 pcsx2-qt/resources/icons/flag-us.svg create mode 100644 pcsx2-qt/resources/icons/flag-us@2x.png create mode 100644 pcsx2-qt/resources/icons/logo.png create mode 100644 pcsx2-qt/resources/icons/media-optical-24.png create mode 100644 pcsx2-qt/resources/icons/media-optical-24@2x.png create mode 100644 pcsx2-qt/resources/icons/media-optical-gear-24.png create mode 100644 pcsx2-qt/resources/icons/media-optical-gear-24@2x.png create mode 100644 pcsx2-qt/resources/icons/media-optical.png create mode 100644 pcsx2-qt/resources/icons/media-optical@2x.png create mode 100644 pcsx2-qt/resources/icons/star-0.png create mode 100644 pcsx2-qt/resources/icons/star-1.png create mode 100644 pcsx2-qt/resources/icons/star-2.png create mode 100644 pcsx2-qt/resources/icons/star-3.png create mode 100644 pcsx2-qt/resources/icons/star-4.png create mode 100644 pcsx2-qt/resources/icons/star-5.png create mode 100644 pcsx2-qt/resources/icons/white/16/artboard-2-line.png create mode 100644 pcsx2-qt/resources/icons/white/16/book-open-line.png create mode 100644 pcsx2-qt/resources/icons/white/16/brush-line.png create mode 100644 pcsx2-qt/resources/icons/white/16/close-line.png create mode 100644 pcsx2-qt/resources/icons/white/16/dashboard-line.png create mode 100644 pcsx2-qt/resources/icons/white/16/disc-line.png create mode 100644 pcsx2-qt/resources/icons/white/16/door-open-line.png create mode 100644 pcsx2-qt/resources/icons/white/16/download-2-line.png create mode 100644 pcsx2-qt/resources/icons/white/16/dvd-line.png create mode 100644 pcsx2-qt/resources/icons/white/16/file-add-line.png create mode 100644 pcsx2-qt/resources/icons/white/16/file-line.png create mode 100644 pcsx2-qt/resources/icons/white/16/file-list-line.png create mode 100644 pcsx2-qt/resources/icons/white/16/file-reduce-line.png create mode 100644 pcsx2-qt/resources/icons/white/16/file-search-line.png create mode 100644 pcsx2-qt/resources/icons/white/16/file-settings-line.png create mode 100644 pcsx2-qt/resources/icons/white/16/flask-line.png create mode 100644 pcsx2-qt/resources/icons/white/16/folder-add-line.png create mode 100644 pcsx2-qt/resources/icons/white/16/folder-open-line.png create mode 100644 pcsx2-qt/resources/icons/white/16/folder-reduce-line.png create mode 100644 pcsx2-qt/resources/icons/white/16/folder-settings-line.png create mode 100644 pcsx2-qt/resources/icons/white/16/fullscreen-line.png create mode 100644 pcsx2-qt/resources/icons/white/16/function-line.png create mode 100644 pcsx2-qt/resources/icons/white/16/gamepad-line.png create mode 100644 pcsx2-qt/resources/icons/white/16/hard-drive-2-line.png create mode 100644 pcsx2-qt/resources/icons/white/16/keyboard-line.png create mode 100644 pcsx2-qt/resources/icons/white/16/layout-grid-line.png create mode 100644 pcsx2-qt/resources/icons/white/16/list-check.png create mode 100644 pcsx2-qt/resources/icons/white/16/pause-line.png create mode 100644 pcsx2-qt/resources/icons/white/16/play-line.png create mode 100644 pcsx2-qt/resources/icons/white/16/refresh-line.png create mode 100644 pcsx2-qt/resources/icons/white/16/restart-line.png create mode 100644 pcsx2-qt/resources/icons/white/16/save-3-line.png create mode 100644 pcsx2-qt/resources/icons/white/16/screenshot-2-line.png create mode 100644 pcsx2-qt/resources/icons/white/16/sd-card-line.png create mode 100644 pcsx2-qt/resources/icons/white/16/settings-3-line.png create mode 100644 pcsx2-qt/resources/icons/white/16/shut-down-line.png create mode 100644 pcsx2-qt/resources/icons/white/16/tv-2-line.png create mode 100644 pcsx2-qt/resources/icons/white/16/volume-up-line.png create mode 100644 pcsx2-qt/resources/icons/white/16/window-2-line.png create mode 100644 pcsx2-qt/resources/icons/white/24/artboard-2-line.png create mode 100644 pcsx2-qt/resources/icons/white/24/book-open-line.png create mode 100644 pcsx2-qt/resources/icons/white/24/brush-line.png create mode 100644 pcsx2-qt/resources/icons/white/24/close-line.png create mode 100644 pcsx2-qt/resources/icons/white/24/dashboard-line.png create mode 100644 pcsx2-qt/resources/icons/white/24/disc-line.png create mode 100644 pcsx2-qt/resources/icons/white/24/door-open-line.png create mode 100644 pcsx2-qt/resources/icons/white/24/download-2-line.png create mode 100644 pcsx2-qt/resources/icons/white/24/dvd-line.png create mode 100644 pcsx2-qt/resources/icons/white/24/file-add-line.png create mode 100644 pcsx2-qt/resources/icons/white/24/file-line.png create mode 100644 pcsx2-qt/resources/icons/white/24/file-list-line.png create mode 100644 pcsx2-qt/resources/icons/white/24/file-reduce-line.png create mode 100644 pcsx2-qt/resources/icons/white/24/file-search-line.png create mode 100644 pcsx2-qt/resources/icons/white/24/file-settings-line.png create mode 100644 pcsx2-qt/resources/icons/white/24/flask-line.png create mode 100644 pcsx2-qt/resources/icons/white/24/folder-add-line.png create mode 100644 pcsx2-qt/resources/icons/white/24/folder-open-line.png create mode 100644 pcsx2-qt/resources/icons/white/24/folder-reduce-line.png create mode 100644 pcsx2-qt/resources/icons/white/24/folder-settings-line.png create mode 100644 pcsx2-qt/resources/icons/white/24/fullscreen-line.png create mode 100644 pcsx2-qt/resources/icons/white/24/function-line.png create mode 100644 pcsx2-qt/resources/icons/white/24/gamepad-line.png create mode 100644 pcsx2-qt/resources/icons/white/24/hard-drive-2-line.png create mode 100644 pcsx2-qt/resources/icons/white/24/keyboard-line.png create mode 100644 pcsx2-qt/resources/icons/white/24/layout-grid-line.png create mode 100644 pcsx2-qt/resources/icons/white/24/list-check.png create mode 100644 pcsx2-qt/resources/icons/white/24/pause-line.png create mode 100644 pcsx2-qt/resources/icons/white/24/play-line.png create mode 100644 pcsx2-qt/resources/icons/white/24/refresh-line.png create mode 100644 pcsx2-qt/resources/icons/white/24/restart-line.png create mode 100644 pcsx2-qt/resources/icons/white/24/save-3-line.png create mode 100644 pcsx2-qt/resources/icons/white/24/screenshot-2-line.png create mode 100644 pcsx2-qt/resources/icons/white/24/sd-card-line.png create mode 100644 pcsx2-qt/resources/icons/white/24/settings-3-line.png create mode 100644 pcsx2-qt/resources/icons/white/24/shut-down-line.png create mode 100644 pcsx2-qt/resources/icons/white/24/tv-2-line.png create mode 100644 pcsx2-qt/resources/icons/white/24/volume-up-line.png create mode 100644 pcsx2-qt/resources/icons/white/24/window-2-line.png create mode 100644 pcsx2-qt/resources/icons/white/32/artboard-2-line.png create mode 100644 pcsx2-qt/resources/icons/white/32/book-open-line.png create mode 100644 pcsx2-qt/resources/icons/white/32/brush-line.png create mode 100644 pcsx2-qt/resources/icons/white/32/close-line.png create mode 100644 pcsx2-qt/resources/icons/white/32/dashboard-line.png create mode 100644 pcsx2-qt/resources/icons/white/32/disc-line.png create mode 100644 pcsx2-qt/resources/icons/white/32/door-open-line.png create mode 100644 pcsx2-qt/resources/icons/white/32/download-2-line.png create mode 100644 pcsx2-qt/resources/icons/white/32/dvd-line.png create mode 100644 pcsx2-qt/resources/icons/white/32/file-add-line.png create mode 100644 pcsx2-qt/resources/icons/white/32/file-line.png create mode 100644 pcsx2-qt/resources/icons/white/32/file-list-line.png create mode 100644 pcsx2-qt/resources/icons/white/32/file-reduce-line.png create mode 100644 pcsx2-qt/resources/icons/white/32/file-search-line.png create mode 100644 pcsx2-qt/resources/icons/white/32/file-settings-line.png create mode 100644 pcsx2-qt/resources/icons/white/32/flask-line.png create mode 100644 pcsx2-qt/resources/icons/white/32/folder-add-line.png create mode 100644 pcsx2-qt/resources/icons/white/32/folder-open-line.png create mode 100644 pcsx2-qt/resources/icons/white/32/folder-reduce-line.png create mode 100644 pcsx2-qt/resources/icons/white/32/folder-settings-line.png create mode 100644 pcsx2-qt/resources/icons/white/32/fullscreen-line.png create mode 100644 pcsx2-qt/resources/icons/white/32/function-line.png create mode 100644 pcsx2-qt/resources/icons/white/32/gamepad-line.png create mode 100644 pcsx2-qt/resources/icons/white/32/hard-drive-2-line.png create mode 100644 pcsx2-qt/resources/icons/white/32/keyboard-line.png create mode 100644 pcsx2-qt/resources/icons/white/32/layout-grid-line.png create mode 100644 pcsx2-qt/resources/icons/white/32/list-check.png create mode 100644 pcsx2-qt/resources/icons/white/32/pause-line.png create mode 100644 pcsx2-qt/resources/icons/white/32/play-line.png create mode 100644 pcsx2-qt/resources/icons/white/32/refresh-line.png create mode 100644 pcsx2-qt/resources/icons/white/32/restart-line.png create mode 100644 pcsx2-qt/resources/icons/white/32/save-3-line.png create mode 100644 pcsx2-qt/resources/icons/white/32/screenshot-2-line.png create mode 100644 pcsx2-qt/resources/icons/white/32/sd-card-line.png create mode 100644 pcsx2-qt/resources/icons/white/32/settings-3-line.png create mode 100644 pcsx2-qt/resources/icons/white/32/shut-down-line.png create mode 100644 pcsx2-qt/resources/icons/white/32/tv-2-line.png create mode 100644 pcsx2-qt/resources/icons/white/32/volume-up-line.png create mode 100644 pcsx2-qt/resources/icons/white/32/window-2-line.png create mode 100644 pcsx2-qt/resources/icons/white/48/artboard-2-line.png create mode 100644 pcsx2-qt/resources/icons/white/48/book-open-line.png create mode 100644 pcsx2-qt/resources/icons/white/48/brush-line.png create mode 100644 pcsx2-qt/resources/icons/white/48/close-line.png create mode 100644 pcsx2-qt/resources/icons/white/48/dashboard-line.png create mode 100644 pcsx2-qt/resources/icons/white/48/disc-line.png create mode 100644 pcsx2-qt/resources/icons/white/48/door-open-line.png create mode 100644 pcsx2-qt/resources/icons/white/48/download-2-line.png create mode 100644 pcsx2-qt/resources/icons/white/48/dvd-line.png create mode 100644 pcsx2-qt/resources/icons/white/48/file-add-line.png create mode 100644 pcsx2-qt/resources/icons/white/48/file-line.png create mode 100644 pcsx2-qt/resources/icons/white/48/file-list-line.png create mode 100644 pcsx2-qt/resources/icons/white/48/file-reduce-line.png create mode 100644 pcsx2-qt/resources/icons/white/48/file-search-line.png create mode 100644 pcsx2-qt/resources/icons/white/48/file-settings-line.png create mode 100644 pcsx2-qt/resources/icons/white/48/flask-line.png create mode 100644 pcsx2-qt/resources/icons/white/48/folder-add-line.png create mode 100644 pcsx2-qt/resources/icons/white/48/folder-open-line.png create mode 100644 pcsx2-qt/resources/icons/white/48/folder-reduce-line.png create mode 100644 pcsx2-qt/resources/icons/white/48/folder-settings-line.png create mode 100644 pcsx2-qt/resources/icons/white/48/fullscreen-line.png create mode 100644 pcsx2-qt/resources/icons/white/48/function-line.png create mode 100644 pcsx2-qt/resources/icons/white/48/gamepad-line.png create mode 100644 pcsx2-qt/resources/icons/white/48/hard-drive-2-line.png create mode 100644 pcsx2-qt/resources/icons/white/48/keyboard-line.png create mode 100644 pcsx2-qt/resources/icons/white/48/layout-grid-line.png create mode 100644 pcsx2-qt/resources/icons/white/48/list-check.png create mode 100644 pcsx2-qt/resources/icons/white/48/pause-line.png create mode 100644 pcsx2-qt/resources/icons/white/48/play-line.png create mode 100644 pcsx2-qt/resources/icons/white/48/refresh-line.png create mode 100644 pcsx2-qt/resources/icons/white/48/restart-line.png create mode 100644 pcsx2-qt/resources/icons/white/48/save-3-line.png create mode 100644 pcsx2-qt/resources/icons/white/48/screenshot-2-line.png create mode 100644 pcsx2-qt/resources/icons/white/48/sd-card-line.png create mode 100644 pcsx2-qt/resources/icons/white/48/settings-3-line.png create mode 100644 pcsx2-qt/resources/icons/white/48/shut-down-line.png create mode 100644 pcsx2-qt/resources/icons/white/48/tv-2-line.png create mode 100644 pcsx2-qt/resources/icons/white/48/volume-up-line.png create mode 100644 pcsx2-qt/resources/icons/white/48/window-2-line.png create mode 100644 pcsx2-qt/resources/icons/white/64/artboard-2-line.png create mode 100644 pcsx2-qt/resources/icons/white/64/book-open-line.png create mode 100644 pcsx2-qt/resources/icons/white/64/brush-line.png create mode 100644 pcsx2-qt/resources/icons/white/64/close-line.png create mode 100644 pcsx2-qt/resources/icons/white/64/dashboard-line.png create mode 100644 pcsx2-qt/resources/icons/white/64/disc-line.png create mode 100644 pcsx2-qt/resources/icons/white/64/door-open-line.png create mode 100644 pcsx2-qt/resources/icons/white/64/download-2-line.png create mode 100644 pcsx2-qt/resources/icons/white/64/dvd-line.png create mode 100644 pcsx2-qt/resources/icons/white/64/file-add-line.png create mode 100644 pcsx2-qt/resources/icons/white/64/file-line.png create mode 100644 pcsx2-qt/resources/icons/white/64/file-list-line.png create mode 100644 pcsx2-qt/resources/icons/white/64/file-reduce-line.png create mode 100644 pcsx2-qt/resources/icons/white/64/file-search-line.png create mode 100644 pcsx2-qt/resources/icons/white/64/file-settings-line.png create mode 100644 pcsx2-qt/resources/icons/white/64/flask-line.png create mode 100644 pcsx2-qt/resources/icons/white/64/folder-add-line.png create mode 100644 pcsx2-qt/resources/icons/white/64/folder-open-line.png create mode 100644 pcsx2-qt/resources/icons/white/64/folder-reduce-line.png create mode 100644 pcsx2-qt/resources/icons/white/64/folder-settings-line.png create mode 100644 pcsx2-qt/resources/icons/white/64/fullscreen-line.png create mode 100644 pcsx2-qt/resources/icons/white/64/function-line.png create mode 100644 pcsx2-qt/resources/icons/white/64/gamepad-line.png create mode 100644 pcsx2-qt/resources/icons/white/64/hard-drive-2-line.png create mode 100644 pcsx2-qt/resources/icons/white/64/keyboard-line.png create mode 100644 pcsx2-qt/resources/icons/white/64/layout-grid-line.png create mode 100644 pcsx2-qt/resources/icons/white/64/list-check.png create mode 100644 pcsx2-qt/resources/icons/white/64/pause-line.png create mode 100644 pcsx2-qt/resources/icons/white/64/play-line.png create mode 100644 pcsx2-qt/resources/icons/white/64/refresh-line.png create mode 100644 pcsx2-qt/resources/icons/white/64/restart-line.png create mode 100644 pcsx2-qt/resources/icons/white/64/save-3-line.png create mode 100644 pcsx2-qt/resources/icons/white/64/screenshot-2-line.png create mode 100644 pcsx2-qt/resources/icons/white/64/sd-card-line.png create mode 100644 pcsx2-qt/resources/icons/white/64/settings-3-line.png create mode 100644 pcsx2-qt/resources/icons/white/64/shut-down-line.png create mode 100644 pcsx2-qt/resources/icons/white/64/tv-2-line.png create mode 100644 pcsx2-qt/resources/icons/white/64/volume-up-line.png create mode 100644 pcsx2-qt/resources/icons/white/64/window-2-line.png create mode 100644 pcsx2-qt/resources/icons/white/index.theme create mode 100644 pcsx2-qt/resources/images/dualshock-2.png create mode 100644 pcsx2-qt/resources/images/dualshock-2@2x.png create mode 100644 pcsx2-qt/resources/resources.qrc diff --git a/3rdparty/qt/.gitignore b/3rdparty/qt/.gitignore new file mode 100644 index 0000000000..2d7fc13007 --- /dev/null +++ b/3rdparty/qt/.gitignore @@ -0,0 +1,3 @@ +/* +!.gitignore +!README.md diff --git a/3rdparty/qt/README.md b/3rdparty/qt/README.md new file mode 100644 index 0000000000..2d19550e4a --- /dev/null +++ b/3rdparty/qt/README.md @@ -0,0 +1,11 @@ +# PCSX2 Qt Binaries + +## Windows + +You can download the binaries from https://github.com/PCSX2/pcsx2-windows-dependencies + +Grab the latest release without symbols (or, if you want to debug Qt, with symbols), and extract it to this directory. + +## Linux + +TODO \ No newline at end of file diff --git a/PCSX2_qt.sln b/PCSX2_qt.sln new file mode 100644 index 0000000000..e68711dad4 --- /dev/null +++ b/PCSX2_qt.sln @@ -0,0 +1,656 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31606.5 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "3rdparty", "3rdparty", "{78EBE642-7A4D-4EA7-86BE-5639C6646C38}" + ProjectSection(SolutionItems) = preProject + 3rdparty\svn_readme.txt = 3rdparty\svn_readme.txt + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{2D6F0A62-A247-4CCF-947F-FCD54BE16103}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SoundTouch", "3rdparty\soundtouch\SoundTouch.vcxproj", "{E9B51944-7E6D-4BCD-83F2-7BBD5A46182D}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlib", "3rdparty\zlib\zlib.vcxproj", "{2F6C0388-20CB-4242-9F6C-A6EBB6A83F47}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "bin2cpp", "tools\bin2cpp\bin2c.vcxproj", "{677B7D11-D5E1-40B3-88B1-9A4DF83D2213}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libjpeg", "3rdparty\libjpeg\libjpeg.vcxproj", "{BC236261-77E8-4567-8D09-45CD02965EB6}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "wx30_config", "3rdparty\wxwidgets3.0\build\msw\wx30_config.vcxproj", "{01F4CE10-2CFB-41A8-B41F-E54337868A1D}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "wx30_base", "3rdparty\wxwidgets3.0\build\msw\wx30_base.vcxproj", "{3FCC50C2-81E9-5DB2-B8D8-2129427568B1}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libpng", "3rdparty\libpng\projects\vstudio\libpng\libpng.vcxproj", "{D6973076-9317-4EF2-A0B8-B7A18AC0713E}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pthreads4w", "3rdparty\pthreads4w\build\pthreads4w.vcxproj", "{0FAE817D-9A32-4830-857E-81DA57246E16}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "baseclasses", "3rdparty\baseclasses\baseclasses.vcxproj", "{27F17499-A372-4408-8AFA-4F9F4584FBD3}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "liblzma", "3rdparty\xz\liblzma.vcxproj", "{12728250-16EC-4DC6-94D7-E21DD88947F8}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "fmt", "3rdparty\fmt\fmt.vcxproj", "{449AD25E-424A-4714-BABC-68706CDCC33B}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libsamplerate", "3rdparty\libsamplerate\libsamplerate.vcxproj", "{47AFDBEF-F15F-4BC0-B436-5BE443C3F80F}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libchdr", "3rdparty\libchdr\libchdr.vcxproj", "{A0D2B3AD-1F72-4EE3-8B5C-F2C358DA35F0}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "jpgd", "3rdparty\jpgd\jpgd.vcxproj", "{ED2F21FD-0A36-4A8F-9B90-E7D92A2ACB63}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "common", "common\common.vcxproj", "{4639972E-424E-4E13-8B07-CA403C481346}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pcsx2core", "pcsx2\pcsx2core.vcxproj", "{6C7986C4-3E4D-4DCC-B3C6-6BB12B238995}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "glad", "3rdparty\glad\glad.vcxproj", "{C0293B32-5ACF-40F0-AA6C-E6DA6F3BF33A}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "imgui", "3rdparty\imgui\imgui.vcxproj", "{88FB34EC-845E-4F21-A552-F1573B9ED167}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pcsx2-qt", "pcsx2-qt\pcsx2-qt.vcxproj", "{2A016F21-87AE-4154-8271-1F57E91408E9}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "simpleini", "3rdparty\simpleini\simpleini.vcxproj", "{1EC8B3C0-8FB3-46DE-A2E0-A9121203F266}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "cubeb", "3rdparty\cubeb\cubeb.vcxproj", "{BF74C473-DC04-44B3-92E8-4145F4E77342}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ryml", "3rdparty\rapidyaml\ryml.vcxproj", "{DE9653B6-17DD-356A-9EE0-28A731772587}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "glslang", "3rdparty\glslang\glslang.vcxproj", "{EF6834A9-11F3-4331-BC34-21B325ABB180}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug AVX2|Win32 = Debug AVX2|Win32 + Debug AVX2|x64 = Debug AVX2|x64 + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Devel AVX2|Win32 = Devel AVX2|Win32 + Devel AVX2|x64 = Devel AVX2|x64 + Devel|Win32 = Devel|Win32 + Devel|x64 = Devel|x64 + Release AVX2|Win32 = Release AVX2|Win32 + Release AVX2|x64 = Release AVX2|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E9B51944-7E6D-4BCD-83F2-7BBD5A46182D}.Debug AVX2|Win32.ActiveCfg = Debug|Win32 + {E9B51944-7E6D-4BCD-83F2-7BBD5A46182D}.Debug AVX2|Win32.Build.0 = Debug|Win32 + {E9B51944-7E6D-4BCD-83F2-7BBD5A46182D}.Debug AVX2|x64.ActiveCfg = Debug|x64 + {E9B51944-7E6D-4BCD-83F2-7BBD5A46182D}.Debug AVX2|x64.Build.0 = Debug|x64 + {E9B51944-7E6D-4BCD-83F2-7BBD5A46182D}.Debug|Win32.ActiveCfg = Debug|Win32 + {E9B51944-7E6D-4BCD-83F2-7BBD5A46182D}.Debug|Win32.Build.0 = Debug|Win32 + {E9B51944-7E6D-4BCD-83F2-7BBD5A46182D}.Debug|x64.ActiveCfg = Debug|x64 + {E9B51944-7E6D-4BCD-83F2-7BBD5A46182D}.Debug|x64.Build.0 = Debug|x64 + {E9B51944-7E6D-4BCD-83F2-7BBD5A46182D}.Devel AVX2|Win32.ActiveCfg = Devel|Win32 + {E9B51944-7E6D-4BCD-83F2-7BBD5A46182D}.Devel AVX2|Win32.Build.0 = Devel|Win32 + {E9B51944-7E6D-4BCD-83F2-7BBD5A46182D}.Devel AVX2|x64.ActiveCfg = Devel|x64 + {E9B51944-7E6D-4BCD-83F2-7BBD5A46182D}.Devel AVX2|x64.Build.0 = Devel|x64 + {E9B51944-7E6D-4BCD-83F2-7BBD5A46182D}.Devel|Win32.ActiveCfg = Devel|Win32 + {E9B51944-7E6D-4BCD-83F2-7BBD5A46182D}.Devel|Win32.Build.0 = Devel|Win32 + {E9B51944-7E6D-4BCD-83F2-7BBD5A46182D}.Devel|x64.ActiveCfg = Devel|x64 + {E9B51944-7E6D-4BCD-83F2-7BBD5A46182D}.Devel|x64.Build.0 = Devel|x64 + {E9B51944-7E6D-4BCD-83F2-7BBD5A46182D}.Release AVX2|Win32.ActiveCfg = Release|Win32 + {E9B51944-7E6D-4BCD-83F2-7BBD5A46182D}.Release AVX2|Win32.Build.0 = Release|Win32 + {E9B51944-7E6D-4BCD-83F2-7BBD5A46182D}.Release AVX2|x64.ActiveCfg = Release|x64 + {E9B51944-7E6D-4BCD-83F2-7BBD5A46182D}.Release AVX2|x64.Build.0 = Release|x64 + {E9B51944-7E6D-4BCD-83F2-7BBD5A46182D}.Release|Win32.ActiveCfg = Release|Win32 + {E9B51944-7E6D-4BCD-83F2-7BBD5A46182D}.Release|Win32.Build.0 = Release|Win32 + {E9B51944-7E6D-4BCD-83F2-7BBD5A46182D}.Release|x64.ActiveCfg = Release|x64 + {E9B51944-7E6D-4BCD-83F2-7BBD5A46182D}.Release|x64.Build.0 = Release|x64 + {2F6C0388-20CB-4242-9F6C-A6EBB6A83F47}.Debug AVX2|Win32.ActiveCfg = Debug|Win32 + {2F6C0388-20CB-4242-9F6C-A6EBB6A83F47}.Debug AVX2|Win32.Build.0 = Debug|Win32 + {2F6C0388-20CB-4242-9F6C-A6EBB6A83F47}.Debug AVX2|x64.ActiveCfg = Debug|x64 + {2F6C0388-20CB-4242-9F6C-A6EBB6A83F47}.Debug AVX2|x64.Build.0 = Debug|x64 + {2F6C0388-20CB-4242-9F6C-A6EBB6A83F47}.Debug|Win32.ActiveCfg = Debug|Win32 + {2F6C0388-20CB-4242-9F6C-A6EBB6A83F47}.Debug|Win32.Build.0 = Debug|Win32 + {2F6C0388-20CB-4242-9F6C-A6EBB6A83F47}.Debug|x64.ActiveCfg = Debug|x64 + {2F6C0388-20CB-4242-9F6C-A6EBB6A83F47}.Debug|x64.Build.0 = Debug|x64 + {2F6C0388-20CB-4242-9F6C-A6EBB6A83F47}.Devel AVX2|Win32.ActiveCfg = Devel|Win32 + {2F6C0388-20CB-4242-9F6C-A6EBB6A83F47}.Devel AVX2|Win32.Build.0 = Devel|Win32 + {2F6C0388-20CB-4242-9F6C-A6EBB6A83F47}.Devel AVX2|x64.ActiveCfg = Devel|x64 + {2F6C0388-20CB-4242-9F6C-A6EBB6A83F47}.Devel AVX2|x64.Build.0 = Devel|x64 + {2F6C0388-20CB-4242-9F6C-A6EBB6A83F47}.Devel|Win32.ActiveCfg = Devel|Win32 + {2F6C0388-20CB-4242-9F6C-A6EBB6A83F47}.Devel|Win32.Build.0 = Devel|Win32 + {2F6C0388-20CB-4242-9F6C-A6EBB6A83F47}.Devel|x64.ActiveCfg = Devel|x64 + {2F6C0388-20CB-4242-9F6C-A6EBB6A83F47}.Devel|x64.Build.0 = Devel|x64 + {2F6C0388-20CB-4242-9F6C-A6EBB6A83F47}.Release AVX2|Win32.ActiveCfg = Release|Win32 + {2F6C0388-20CB-4242-9F6C-A6EBB6A83F47}.Release AVX2|Win32.Build.0 = Release|Win32 + {2F6C0388-20CB-4242-9F6C-A6EBB6A83F47}.Release AVX2|x64.ActiveCfg = Release|x64 + {2F6C0388-20CB-4242-9F6C-A6EBB6A83F47}.Release AVX2|x64.Build.0 = Release|x64 + {2F6C0388-20CB-4242-9F6C-A6EBB6A83F47}.Release|Win32.ActiveCfg = Release|Win32 + {2F6C0388-20CB-4242-9F6C-A6EBB6A83F47}.Release|Win32.Build.0 = Release|Win32 + {2F6C0388-20CB-4242-9F6C-A6EBB6A83F47}.Release|x64.ActiveCfg = Release|x64 + {2F6C0388-20CB-4242-9F6C-A6EBB6A83F47}.Release|x64.Build.0 = Release|x64 + {677B7D11-D5E1-40B3-88B1-9A4DF83D2213}.Debug AVX2|Win32.ActiveCfg = Debug|Win32 + {677B7D11-D5E1-40B3-88B1-9A4DF83D2213}.Debug AVX2|Win32.Build.0 = Debug|Win32 + {677B7D11-D5E1-40B3-88B1-9A4DF83D2213}.Debug AVX2|x64.ActiveCfg = Debug|x64 + {677B7D11-D5E1-40B3-88B1-9A4DF83D2213}.Debug AVX2|x64.Build.0 = Debug|x64 + {677B7D11-D5E1-40B3-88B1-9A4DF83D2213}.Debug|Win32.ActiveCfg = Debug|Win32 + {677B7D11-D5E1-40B3-88B1-9A4DF83D2213}.Debug|Win32.Build.0 = Debug|Win32 + {677B7D11-D5E1-40B3-88B1-9A4DF83D2213}.Debug|x64.ActiveCfg = Debug|x64 + {677B7D11-D5E1-40B3-88B1-9A4DF83D2213}.Debug|x64.Build.0 = Debug|x64 + {677B7D11-D5E1-40B3-88B1-9A4DF83D2213}.Devel AVX2|Win32.ActiveCfg = Devel|Win32 + {677B7D11-D5E1-40B3-88B1-9A4DF83D2213}.Devel AVX2|Win32.Build.0 = Devel|Win32 + {677B7D11-D5E1-40B3-88B1-9A4DF83D2213}.Devel AVX2|x64.ActiveCfg = Devel|x64 + {677B7D11-D5E1-40B3-88B1-9A4DF83D2213}.Devel AVX2|x64.Build.0 = Devel|x64 + {677B7D11-D5E1-40B3-88B1-9A4DF83D2213}.Devel|Win32.ActiveCfg = Devel|Win32 + {677B7D11-D5E1-40B3-88B1-9A4DF83D2213}.Devel|Win32.Build.0 = Devel|Win32 + {677B7D11-D5E1-40B3-88B1-9A4DF83D2213}.Devel|x64.ActiveCfg = Devel|x64 + {677B7D11-D5E1-40B3-88B1-9A4DF83D2213}.Devel|x64.Build.0 = Devel|x64 + {677B7D11-D5E1-40B3-88B1-9A4DF83D2213}.Release AVX2|Win32.ActiveCfg = Release|Win32 + {677B7D11-D5E1-40B3-88B1-9A4DF83D2213}.Release AVX2|Win32.Build.0 = Release|Win32 + {677B7D11-D5E1-40B3-88B1-9A4DF83D2213}.Release AVX2|x64.ActiveCfg = Release|x64 + {677B7D11-D5E1-40B3-88B1-9A4DF83D2213}.Release AVX2|x64.Build.0 = Release|x64 + {677B7D11-D5E1-40B3-88B1-9A4DF83D2213}.Release|Win32.ActiveCfg = Release|Win32 + {677B7D11-D5E1-40B3-88B1-9A4DF83D2213}.Release|Win32.Build.0 = Release|Win32 + {677B7D11-D5E1-40B3-88B1-9A4DF83D2213}.Release|x64.ActiveCfg = Release|x64 + {677B7D11-D5E1-40B3-88B1-9A4DF83D2213}.Release|x64.Build.0 = Release|x64 + {BC236261-77E8-4567-8D09-45CD02965EB6}.Debug AVX2|Win32.ActiveCfg = Debug|Win32 + {BC236261-77E8-4567-8D09-45CD02965EB6}.Debug AVX2|Win32.Build.0 = Debug|Win32 + {BC236261-77E8-4567-8D09-45CD02965EB6}.Debug AVX2|x64.ActiveCfg = Debug|x64 + {BC236261-77E8-4567-8D09-45CD02965EB6}.Debug AVX2|x64.Build.0 = Debug|x64 + {BC236261-77E8-4567-8D09-45CD02965EB6}.Debug|Win32.ActiveCfg = Debug|Win32 + {BC236261-77E8-4567-8D09-45CD02965EB6}.Debug|Win32.Build.0 = Debug|Win32 + {BC236261-77E8-4567-8D09-45CD02965EB6}.Debug|x64.ActiveCfg = Debug|x64 + {BC236261-77E8-4567-8D09-45CD02965EB6}.Debug|x64.Build.0 = Debug|x64 + {BC236261-77E8-4567-8D09-45CD02965EB6}.Devel AVX2|Win32.ActiveCfg = Devel|Win32 + {BC236261-77E8-4567-8D09-45CD02965EB6}.Devel AVX2|Win32.Build.0 = Devel|Win32 + {BC236261-77E8-4567-8D09-45CD02965EB6}.Devel AVX2|x64.ActiveCfg = Devel|x64 + {BC236261-77E8-4567-8D09-45CD02965EB6}.Devel AVX2|x64.Build.0 = Devel|x64 + {BC236261-77E8-4567-8D09-45CD02965EB6}.Devel|Win32.ActiveCfg = Devel|Win32 + {BC236261-77E8-4567-8D09-45CD02965EB6}.Devel|Win32.Build.0 = Devel|Win32 + {BC236261-77E8-4567-8D09-45CD02965EB6}.Devel|x64.ActiveCfg = Devel|x64 + {BC236261-77E8-4567-8D09-45CD02965EB6}.Devel|x64.Build.0 = Devel|x64 + {BC236261-77E8-4567-8D09-45CD02965EB6}.Release AVX2|Win32.ActiveCfg = Release|Win32 + {BC236261-77E8-4567-8D09-45CD02965EB6}.Release AVX2|Win32.Build.0 = Release|Win32 + {BC236261-77E8-4567-8D09-45CD02965EB6}.Release AVX2|x64.ActiveCfg = Release|x64 + {BC236261-77E8-4567-8D09-45CD02965EB6}.Release AVX2|x64.Build.0 = Release|x64 + {BC236261-77E8-4567-8D09-45CD02965EB6}.Release|Win32.ActiveCfg = Release|Win32 + {BC236261-77E8-4567-8D09-45CD02965EB6}.Release|Win32.Build.0 = Release|Win32 + {BC236261-77E8-4567-8D09-45CD02965EB6}.Release|x64.ActiveCfg = Release|x64 + {BC236261-77E8-4567-8D09-45CD02965EB6}.Release|x64.Build.0 = Release|x64 + {01F4CE10-2CFB-41A8-B41F-E54337868A1D}.Debug AVX2|Win32.ActiveCfg = Debug|Win32 + {01F4CE10-2CFB-41A8-B41F-E54337868A1D}.Debug AVX2|Win32.Build.0 = Debug|Win32 + {01F4CE10-2CFB-41A8-B41F-E54337868A1D}.Debug AVX2|x64.ActiveCfg = Debug|x64 + {01F4CE10-2CFB-41A8-B41F-E54337868A1D}.Debug AVX2|x64.Build.0 = Debug|x64 + {01F4CE10-2CFB-41A8-B41F-E54337868A1D}.Debug|Win32.ActiveCfg = Debug|Win32 + {01F4CE10-2CFB-41A8-B41F-E54337868A1D}.Debug|Win32.Build.0 = Debug|Win32 + {01F4CE10-2CFB-41A8-B41F-E54337868A1D}.Debug|x64.ActiveCfg = Debug|x64 + {01F4CE10-2CFB-41A8-B41F-E54337868A1D}.Debug|x64.Build.0 = Debug|x64 + {01F4CE10-2CFB-41A8-B41F-E54337868A1D}.Devel AVX2|Win32.ActiveCfg = Devel|Win32 + {01F4CE10-2CFB-41A8-B41F-E54337868A1D}.Devel AVX2|Win32.Build.0 = Devel|Win32 + {01F4CE10-2CFB-41A8-B41F-E54337868A1D}.Devel AVX2|x64.ActiveCfg = Devel|x64 + {01F4CE10-2CFB-41A8-B41F-E54337868A1D}.Devel AVX2|x64.Build.0 = Devel|x64 + {01F4CE10-2CFB-41A8-B41F-E54337868A1D}.Devel|Win32.ActiveCfg = Devel|Win32 + {01F4CE10-2CFB-41A8-B41F-E54337868A1D}.Devel|Win32.Build.0 = Devel|Win32 + {01F4CE10-2CFB-41A8-B41F-E54337868A1D}.Devel|x64.ActiveCfg = Devel|x64 + {01F4CE10-2CFB-41A8-B41F-E54337868A1D}.Devel|x64.Build.0 = Devel|x64 + {01F4CE10-2CFB-41A8-B41F-E54337868A1D}.Release AVX2|Win32.ActiveCfg = Release|Win32 + {01F4CE10-2CFB-41A8-B41F-E54337868A1D}.Release AVX2|Win32.Build.0 = Release|Win32 + {01F4CE10-2CFB-41A8-B41F-E54337868A1D}.Release AVX2|x64.ActiveCfg = Release|x64 + {01F4CE10-2CFB-41A8-B41F-E54337868A1D}.Release AVX2|x64.Build.0 = Release|x64 + {01F4CE10-2CFB-41A8-B41F-E54337868A1D}.Release|Win32.ActiveCfg = Release|Win32 + {01F4CE10-2CFB-41A8-B41F-E54337868A1D}.Release|Win32.Build.0 = Release|Win32 + {01F4CE10-2CFB-41A8-B41F-E54337868A1D}.Release|x64.ActiveCfg = Release|x64 + {01F4CE10-2CFB-41A8-B41F-E54337868A1D}.Release|x64.Build.0 = Release|x64 + {3FCC50C2-81E9-5DB2-B8D8-2129427568B1}.Debug AVX2|Win32.ActiveCfg = Debug|Win32 + {3FCC50C2-81E9-5DB2-B8D8-2129427568B1}.Debug AVX2|Win32.Build.0 = Debug|Win32 + {3FCC50C2-81E9-5DB2-B8D8-2129427568B1}.Debug AVX2|x64.ActiveCfg = Debug|x64 + {3FCC50C2-81E9-5DB2-B8D8-2129427568B1}.Debug AVX2|x64.Build.0 = Debug|x64 + {3FCC50C2-81E9-5DB2-B8D8-2129427568B1}.Debug|Win32.ActiveCfg = Debug|Win32 + {3FCC50C2-81E9-5DB2-B8D8-2129427568B1}.Debug|Win32.Build.0 = Debug|Win32 + {3FCC50C2-81E9-5DB2-B8D8-2129427568B1}.Debug|x64.ActiveCfg = Debug|x64 + {3FCC50C2-81E9-5DB2-B8D8-2129427568B1}.Debug|x64.Build.0 = Debug|x64 + {3FCC50C2-81E9-5DB2-B8D8-2129427568B1}.Devel AVX2|Win32.ActiveCfg = Devel|Win32 + {3FCC50C2-81E9-5DB2-B8D8-2129427568B1}.Devel AVX2|Win32.Build.0 = Devel|Win32 + {3FCC50C2-81E9-5DB2-B8D8-2129427568B1}.Devel AVX2|x64.ActiveCfg = Devel|x64 + {3FCC50C2-81E9-5DB2-B8D8-2129427568B1}.Devel AVX2|x64.Build.0 = Devel|x64 + {3FCC50C2-81E9-5DB2-B8D8-2129427568B1}.Devel|Win32.ActiveCfg = Devel|Win32 + {3FCC50C2-81E9-5DB2-B8D8-2129427568B1}.Devel|Win32.Build.0 = Devel|Win32 + {3FCC50C2-81E9-5DB2-B8D8-2129427568B1}.Devel|x64.ActiveCfg = Devel|x64 + {3FCC50C2-81E9-5DB2-B8D8-2129427568B1}.Devel|x64.Build.0 = Devel|x64 + {3FCC50C2-81E9-5DB2-B8D8-2129427568B1}.Release AVX2|Win32.ActiveCfg = Release|Win32 + {3FCC50C2-81E9-5DB2-B8D8-2129427568B1}.Release AVX2|Win32.Build.0 = Release|Win32 + {3FCC50C2-81E9-5DB2-B8D8-2129427568B1}.Release AVX2|x64.ActiveCfg = Release|x64 + {3FCC50C2-81E9-5DB2-B8D8-2129427568B1}.Release AVX2|x64.Build.0 = Release|x64 + {3FCC50C2-81E9-5DB2-B8D8-2129427568B1}.Release|Win32.ActiveCfg = Release|Win32 + {3FCC50C2-81E9-5DB2-B8D8-2129427568B1}.Release|Win32.Build.0 = Release|Win32 + {3FCC50C2-81E9-5DB2-B8D8-2129427568B1}.Release|x64.ActiveCfg = Release|x64 + {3FCC50C2-81E9-5DB2-B8D8-2129427568B1}.Release|x64.Build.0 = Release|x64 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Debug AVX2|Win32.ActiveCfg = Debug|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Debug AVX2|Win32.Build.0 = Debug|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Debug AVX2|x64.ActiveCfg = Debug|x64 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Debug AVX2|x64.Build.0 = Debug|x64 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Debug|Win32.ActiveCfg = Debug|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Debug|Win32.Build.0 = Debug|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Debug|x64.ActiveCfg = Debug|x64 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Debug|x64.Build.0 = Debug|x64 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Devel AVX2|Win32.ActiveCfg = Devel|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Devel AVX2|Win32.Build.0 = Devel|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Devel AVX2|x64.ActiveCfg = Devel|x64 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Devel AVX2|x64.Build.0 = Devel|x64 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Devel|Win32.ActiveCfg = Devel|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Devel|Win32.Build.0 = Devel|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Devel|x64.ActiveCfg = Devel|x64 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Devel|x64.Build.0 = Devel|x64 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Release AVX2|Win32.ActiveCfg = Release|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Release AVX2|Win32.Build.0 = Release|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Release AVX2|x64.ActiveCfg = Release|x64 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Release AVX2|x64.Build.0 = Release|x64 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Release|Win32.ActiveCfg = Release|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Release|Win32.Build.0 = Release|Win32 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Release|x64.ActiveCfg = Release|x64 + {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Release|x64.Build.0 = Release|x64 + {0FAE817D-9A32-4830-857E-81DA57246E16}.Debug AVX2|Win32.ActiveCfg = Debug|Win32 + {0FAE817D-9A32-4830-857E-81DA57246E16}.Debug AVX2|Win32.Build.0 = Debug|Win32 + {0FAE817D-9A32-4830-857E-81DA57246E16}.Debug AVX2|x64.ActiveCfg = Debug|x64 + {0FAE817D-9A32-4830-857E-81DA57246E16}.Debug AVX2|x64.Build.0 = Debug|x64 + {0FAE817D-9A32-4830-857E-81DA57246E16}.Debug|Win32.ActiveCfg = Debug|Win32 + {0FAE817D-9A32-4830-857E-81DA57246E16}.Debug|Win32.Build.0 = Debug|Win32 + {0FAE817D-9A32-4830-857E-81DA57246E16}.Debug|x64.ActiveCfg = Debug|x64 + {0FAE817D-9A32-4830-857E-81DA57246E16}.Debug|x64.Build.0 = Debug|x64 + {0FAE817D-9A32-4830-857E-81DA57246E16}.Devel AVX2|Win32.ActiveCfg = Devel|Win32 + {0FAE817D-9A32-4830-857E-81DA57246E16}.Devel AVX2|Win32.Build.0 = Devel|Win32 + {0FAE817D-9A32-4830-857E-81DA57246E16}.Devel AVX2|x64.ActiveCfg = Devel|x64 + {0FAE817D-9A32-4830-857E-81DA57246E16}.Devel AVX2|x64.Build.0 = Devel|x64 + {0FAE817D-9A32-4830-857E-81DA57246E16}.Devel|Win32.ActiveCfg = Devel|Win32 + {0FAE817D-9A32-4830-857E-81DA57246E16}.Devel|Win32.Build.0 = Devel|Win32 + {0FAE817D-9A32-4830-857E-81DA57246E16}.Devel|x64.ActiveCfg = Devel|x64 + {0FAE817D-9A32-4830-857E-81DA57246E16}.Devel|x64.Build.0 = Devel|x64 + {0FAE817D-9A32-4830-857E-81DA57246E16}.Release AVX2|Win32.ActiveCfg = Release|Win32 + {0FAE817D-9A32-4830-857E-81DA57246E16}.Release AVX2|Win32.Build.0 = Release|Win32 + {0FAE817D-9A32-4830-857E-81DA57246E16}.Release AVX2|x64.ActiveCfg = Release|x64 + {0FAE817D-9A32-4830-857E-81DA57246E16}.Release AVX2|x64.Build.0 = Release|x64 + {0FAE817D-9A32-4830-857E-81DA57246E16}.Release|Win32.ActiveCfg = Release|Win32 + {0FAE817D-9A32-4830-857E-81DA57246E16}.Release|Win32.Build.0 = Release|Win32 + {0FAE817D-9A32-4830-857E-81DA57246E16}.Release|x64.ActiveCfg = Release|x64 + {0FAE817D-9A32-4830-857E-81DA57246E16}.Release|x64.Build.0 = Release|x64 + {27F17499-A372-4408-8AFA-4F9F4584FBD3}.Debug AVX2|Win32.ActiveCfg = Debug|Win32 + {27F17499-A372-4408-8AFA-4F9F4584FBD3}.Debug AVX2|Win32.Build.0 = Debug|Win32 + {27F17499-A372-4408-8AFA-4F9F4584FBD3}.Debug AVX2|x64.ActiveCfg = Debug|x64 + {27F17499-A372-4408-8AFA-4F9F4584FBD3}.Debug AVX2|x64.Build.0 = Debug|x64 + {27F17499-A372-4408-8AFA-4F9F4584FBD3}.Debug|Win32.ActiveCfg = Debug|Win32 + {27F17499-A372-4408-8AFA-4F9F4584FBD3}.Debug|Win32.Build.0 = Debug|Win32 + {27F17499-A372-4408-8AFA-4F9F4584FBD3}.Debug|x64.ActiveCfg = Debug|x64 + {27F17499-A372-4408-8AFA-4F9F4584FBD3}.Debug|x64.Build.0 = Debug|x64 + {27F17499-A372-4408-8AFA-4F9F4584FBD3}.Devel AVX2|Win32.ActiveCfg = Devel|Win32 + {27F17499-A372-4408-8AFA-4F9F4584FBD3}.Devel AVX2|Win32.Build.0 = Devel|Win32 + {27F17499-A372-4408-8AFA-4F9F4584FBD3}.Devel AVX2|x64.ActiveCfg = Devel|x64 + {27F17499-A372-4408-8AFA-4F9F4584FBD3}.Devel AVX2|x64.Build.0 = Devel|x64 + {27F17499-A372-4408-8AFA-4F9F4584FBD3}.Devel|Win32.ActiveCfg = Devel|Win32 + {27F17499-A372-4408-8AFA-4F9F4584FBD3}.Devel|Win32.Build.0 = Devel|Win32 + {27F17499-A372-4408-8AFA-4F9F4584FBD3}.Devel|x64.ActiveCfg = Devel|x64 + {27F17499-A372-4408-8AFA-4F9F4584FBD3}.Devel|x64.Build.0 = Devel|x64 + {27F17499-A372-4408-8AFA-4F9F4584FBD3}.Release AVX2|Win32.ActiveCfg = Release|Win32 + {27F17499-A372-4408-8AFA-4F9F4584FBD3}.Release AVX2|Win32.Build.0 = Release|Win32 + {27F17499-A372-4408-8AFA-4F9F4584FBD3}.Release AVX2|x64.ActiveCfg = Release|x64 + {27F17499-A372-4408-8AFA-4F9F4584FBD3}.Release AVX2|x64.Build.0 = Release|x64 + {27F17499-A372-4408-8AFA-4F9F4584FBD3}.Release|Win32.ActiveCfg = Release|Win32 + {27F17499-A372-4408-8AFA-4F9F4584FBD3}.Release|Win32.Build.0 = Release|Win32 + {27F17499-A372-4408-8AFA-4F9F4584FBD3}.Release|x64.ActiveCfg = Release|x64 + {27F17499-A372-4408-8AFA-4F9F4584FBD3}.Release|x64.Build.0 = Release|x64 + {12728250-16EC-4DC6-94D7-E21DD88947F8}.Debug AVX2|Win32.ActiveCfg = Debug|Win32 + {12728250-16EC-4DC6-94D7-E21DD88947F8}.Debug AVX2|Win32.Build.0 = Debug|Win32 + {12728250-16EC-4DC6-94D7-E21DD88947F8}.Debug AVX2|x64.ActiveCfg = Debug|x64 + {12728250-16EC-4DC6-94D7-E21DD88947F8}.Debug AVX2|x64.Build.0 = Debug|x64 + {12728250-16EC-4DC6-94D7-E21DD88947F8}.Debug|Win32.ActiveCfg = Debug|Win32 + {12728250-16EC-4DC6-94D7-E21DD88947F8}.Debug|Win32.Build.0 = Debug|Win32 + {12728250-16EC-4DC6-94D7-E21DD88947F8}.Debug|x64.ActiveCfg = Debug|x64 + {12728250-16EC-4DC6-94D7-E21DD88947F8}.Debug|x64.Build.0 = Debug|x64 + {12728250-16EC-4DC6-94D7-E21DD88947F8}.Devel AVX2|Win32.ActiveCfg = Devel|Win32 + {12728250-16EC-4DC6-94D7-E21DD88947F8}.Devel AVX2|Win32.Build.0 = Devel|Win32 + {12728250-16EC-4DC6-94D7-E21DD88947F8}.Devel AVX2|x64.ActiveCfg = Devel|x64 + {12728250-16EC-4DC6-94D7-E21DD88947F8}.Devel AVX2|x64.Build.0 = Devel|x64 + {12728250-16EC-4DC6-94D7-E21DD88947F8}.Devel|Win32.ActiveCfg = Devel|Win32 + {12728250-16EC-4DC6-94D7-E21DD88947F8}.Devel|Win32.Build.0 = Devel|Win32 + {12728250-16EC-4DC6-94D7-E21DD88947F8}.Devel|x64.ActiveCfg = Devel|x64 + {12728250-16EC-4DC6-94D7-E21DD88947F8}.Devel|x64.Build.0 = Devel|x64 + {12728250-16EC-4DC6-94D7-E21DD88947F8}.Release AVX2|Win32.ActiveCfg = Release|Win32 + {12728250-16EC-4DC6-94D7-E21DD88947F8}.Release AVX2|Win32.Build.0 = Release|Win32 + {12728250-16EC-4DC6-94D7-E21DD88947F8}.Release AVX2|x64.ActiveCfg = Release|x64 + {12728250-16EC-4DC6-94D7-E21DD88947F8}.Release AVX2|x64.Build.0 = Release|x64 + {12728250-16EC-4DC6-94D7-E21DD88947F8}.Release|Win32.ActiveCfg = Release|Win32 + {12728250-16EC-4DC6-94D7-E21DD88947F8}.Release|Win32.Build.0 = Release|Win32 + {12728250-16EC-4DC6-94D7-E21DD88947F8}.Release|x64.ActiveCfg = Release|x64 + {12728250-16EC-4DC6-94D7-E21DD88947F8}.Release|x64.Build.0 = Release|x64 + {449AD25E-424A-4714-BABC-68706CDCC33B}.Debug AVX2|Win32.ActiveCfg = Debug|Win32 + {449AD25E-424A-4714-BABC-68706CDCC33B}.Debug AVX2|Win32.Build.0 = Debug|Win32 + {449AD25E-424A-4714-BABC-68706CDCC33B}.Debug AVX2|x64.ActiveCfg = Debug|x64 + {449AD25E-424A-4714-BABC-68706CDCC33B}.Debug AVX2|x64.Build.0 = Debug|x64 + {449AD25E-424A-4714-BABC-68706CDCC33B}.Debug|Win32.ActiveCfg = Debug|Win32 + {449AD25E-424A-4714-BABC-68706CDCC33B}.Debug|Win32.Build.0 = Debug|Win32 + {449AD25E-424A-4714-BABC-68706CDCC33B}.Debug|x64.ActiveCfg = Debug|x64 + {449AD25E-424A-4714-BABC-68706CDCC33B}.Debug|x64.Build.0 = Debug|x64 + {449AD25E-424A-4714-BABC-68706CDCC33B}.Devel AVX2|Win32.ActiveCfg = Devel|Win32 + {449AD25E-424A-4714-BABC-68706CDCC33B}.Devel AVX2|Win32.Build.0 = Devel|Win32 + {449AD25E-424A-4714-BABC-68706CDCC33B}.Devel AVX2|x64.ActiveCfg = Devel|x64 + {449AD25E-424A-4714-BABC-68706CDCC33B}.Devel AVX2|x64.Build.0 = Devel|x64 + {449AD25E-424A-4714-BABC-68706CDCC33B}.Devel|Win32.ActiveCfg = Devel|Win32 + {449AD25E-424A-4714-BABC-68706CDCC33B}.Devel|Win32.Build.0 = Devel|Win32 + {449AD25E-424A-4714-BABC-68706CDCC33B}.Devel|x64.ActiveCfg = Devel|x64 + {449AD25E-424A-4714-BABC-68706CDCC33B}.Devel|x64.Build.0 = Devel|x64 + {449AD25E-424A-4714-BABC-68706CDCC33B}.Release AVX2|Win32.ActiveCfg = Release|Win32 + {449AD25E-424A-4714-BABC-68706CDCC33B}.Release AVX2|Win32.Build.0 = Release|Win32 + {449AD25E-424A-4714-BABC-68706CDCC33B}.Release AVX2|x64.ActiveCfg = Release|x64 + {449AD25E-424A-4714-BABC-68706CDCC33B}.Release AVX2|x64.Build.0 = Release|x64 + {449AD25E-424A-4714-BABC-68706CDCC33B}.Release|Win32.ActiveCfg = Release|Win32 + {449AD25E-424A-4714-BABC-68706CDCC33B}.Release|Win32.Build.0 = Release|Win32 + {449AD25E-424A-4714-BABC-68706CDCC33B}.Release|x64.ActiveCfg = Release|x64 + {449AD25E-424A-4714-BABC-68706CDCC33B}.Release|x64.Build.0 = Release|x64 + {47AFDBEF-F15F-4BC0-B436-5BE443C3F80F}.Debug AVX2|Win32.ActiveCfg = Debug|Win32 + {47AFDBEF-F15F-4BC0-B436-5BE443C3F80F}.Debug AVX2|Win32.Build.0 = Debug|Win32 + {47AFDBEF-F15F-4BC0-B436-5BE443C3F80F}.Debug AVX2|x64.ActiveCfg = Debug|x64 + {47AFDBEF-F15F-4BC0-B436-5BE443C3F80F}.Debug AVX2|x64.Build.0 = Debug|x64 + {47AFDBEF-F15F-4BC0-B436-5BE443C3F80F}.Debug|Win32.ActiveCfg = Debug|Win32 + {47AFDBEF-F15F-4BC0-B436-5BE443C3F80F}.Debug|Win32.Build.0 = Debug|Win32 + {47AFDBEF-F15F-4BC0-B436-5BE443C3F80F}.Debug|x64.ActiveCfg = Debug|x64 + {47AFDBEF-F15F-4BC0-B436-5BE443C3F80F}.Debug|x64.Build.0 = Debug|x64 + {47AFDBEF-F15F-4BC0-B436-5BE443C3F80F}.Devel AVX2|Win32.ActiveCfg = Devel|Win32 + {47AFDBEF-F15F-4BC0-B436-5BE443C3F80F}.Devel AVX2|Win32.Build.0 = Devel|Win32 + {47AFDBEF-F15F-4BC0-B436-5BE443C3F80F}.Devel AVX2|x64.ActiveCfg = Devel|x64 + {47AFDBEF-F15F-4BC0-B436-5BE443C3F80F}.Devel AVX2|x64.Build.0 = Devel|x64 + {47AFDBEF-F15F-4BC0-B436-5BE443C3F80F}.Devel|Win32.ActiveCfg = Devel|Win32 + {47AFDBEF-F15F-4BC0-B436-5BE443C3F80F}.Devel|Win32.Build.0 = Devel|Win32 + {47AFDBEF-F15F-4BC0-B436-5BE443C3F80F}.Devel|x64.ActiveCfg = Devel|x64 + {47AFDBEF-F15F-4BC0-B436-5BE443C3F80F}.Devel|x64.Build.0 = Devel|x64 + {47AFDBEF-F15F-4BC0-B436-5BE443C3F80F}.Release AVX2|Win32.ActiveCfg = Release|Win32 + {47AFDBEF-F15F-4BC0-B436-5BE443C3F80F}.Release AVX2|Win32.Build.0 = Release|Win32 + {47AFDBEF-F15F-4BC0-B436-5BE443C3F80F}.Release AVX2|x64.ActiveCfg = Release|x64 + {47AFDBEF-F15F-4BC0-B436-5BE443C3F80F}.Release AVX2|x64.Build.0 = Release|x64 + {47AFDBEF-F15F-4BC0-B436-5BE443C3F80F}.Release|Win32.ActiveCfg = Release|Win32 + {47AFDBEF-F15F-4BC0-B436-5BE443C3F80F}.Release|Win32.Build.0 = Release|Win32 + {47AFDBEF-F15F-4BC0-B436-5BE443C3F80F}.Release|x64.ActiveCfg = Release|x64 + {47AFDBEF-F15F-4BC0-B436-5BE443C3F80F}.Release|x64.Build.0 = Release|x64 + {A0D2B3AD-1F72-4EE3-8B5C-F2C358DA35F0}.Debug AVX2|Win32.ActiveCfg = Debug|Win32 + {A0D2B3AD-1F72-4EE3-8B5C-F2C358DA35F0}.Debug AVX2|Win32.Build.0 = Debug|Win32 + {A0D2B3AD-1F72-4EE3-8B5C-F2C358DA35F0}.Debug AVX2|x64.ActiveCfg = Debug|x64 + {A0D2B3AD-1F72-4EE3-8B5C-F2C358DA35F0}.Debug AVX2|x64.Build.0 = Debug|x64 + {A0D2B3AD-1F72-4EE3-8B5C-F2C358DA35F0}.Debug|Win32.ActiveCfg = Debug|Win32 + {A0D2B3AD-1F72-4EE3-8B5C-F2C358DA35F0}.Debug|Win32.Build.0 = Debug|Win32 + {A0D2B3AD-1F72-4EE3-8B5C-F2C358DA35F0}.Debug|x64.ActiveCfg = Debug|x64 + {A0D2B3AD-1F72-4EE3-8B5C-F2C358DA35F0}.Debug|x64.Build.0 = Debug|x64 + {A0D2B3AD-1F72-4EE3-8B5C-F2C358DA35F0}.Devel AVX2|Win32.ActiveCfg = Devel|Win32 + {A0D2B3AD-1F72-4EE3-8B5C-F2C358DA35F0}.Devel AVX2|Win32.Build.0 = Devel|Win32 + {A0D2B3AD-1F72-4EE3-8B5C-F2C358DA35F0}.Devel AVX2|x64.ActiveCfg = Devel|x64 + {A0D2B3AD-1F72-4EE3-8B5C-F2C358DA35F0}.Devel AVX2|x64.Build.0 = Devel|x64 + {A0D2B3AD-1F72-4EE3-8B5C-F2C358DA35F0}.Devel|Win32.ActiveCfg = Devel|Win32 + {A0D2B3AD-1F72-4EE3-8B5C-F2C358DA35F0}.Devel|Win32.Build.0 = Devel|Win32 + {A0D2B3AD-1F72-4EE3-8B5C-F2C358DA35F0}.Devel|x64.ActiveCfg = Devel|x64 + {A0D2B3AD-1F72-4EE3-8B5C-F2C358DA35F0}.Devel|x64.Build.0 = Devel|x64 + {A0D2B3AD-1F72-4EE3-8B5C-F2C358DA35F0}.Release AVX2|Win32.ActiveCfg = Release|Win32 + {A0D2B3AD-1F72-4EE3-8B5C-F2C358DA35F0}.Release AVX2|Win32.Build.0 = Release|Win32 + {A0D2B3AD-1F72-4EE3-8B5C-F2C358DA35F0}.Release AVX2|x64.ActiveCfg = Release|x64 + {A0D2B3AD-1F72-4EE3-8B5C-F2C358DA35F0}.Release AVX2|x64.Build.0 = Release|x64 + {A0D2B3AD-1F72-4EE3-8B5C-F2C358DA35F0}.Release|Win32.ActiveCfg = Release|Win32 + {A0D2B3AD-1F72-4EE3-8B5C-F2C358DA35F0}.Release|Win32.Build.0 = Release|Win32 + {A0D2B3AD-1F72-4EE3-8B5C-F2C358DA35F0}.Release|x64.ActiveCfg = Release|x64 + {A0D2B3AD-1F72-4EE3-8B5C-F2C358DA35F0}.Release|x64.Build.0 = Release|x64 + {ED2F21FD-0A36-4A8F-9B90-E7D92A2ACB63}.Debug AVX2|Win32.ActiveCfg = Debug|Win32 + {ED2F21FD-0A36-4A8F-9B90-E7D92A2ACB63}.Debug AVX2|Win32.Build.0 = Debug|Win32 + {ED2F21FD-0A36-4A8F-9B90-E7D92A2ACB63}.Debug AVX2|x64.ActiveCfg = Debug|x64 + {ED2F21FD-0A36-4A8F-9B90-E7D92A2ACB63}.Debug AVX2|x64.Build.0 = Debug|x64 + {ED2F21FD-0A36-4A8F-9B90-E7D92A2ACB63}.Debug|Win32.ActiveCfg = Debug|Win32 + {ED2F21FD-0A36-4A8F-9B90-E7D92A2ACB63}.Debug|Win32.Build.0 = Debug|Win32 + {ED2F21FD-0A36-4A8F-9B90-E7D92A2ACB63}.Debug|x64.ActiveCfg = Debug|x64 + {ED2F21FD-0A36-4A8F-9B90-E7D92A2ACB63}.Debug|x64.Build.0 = Debug|x64 + {ED2F21FD-0A36-4A8F-9B90-E7D92A2ACB63}.Devel AVX2|Win32.ActiveCfg = Devel|Win32 + {ED2F21FD-0A36-4A8F-9B90-E7D92A2ACB63}.Devel AVX2|Win32.Build.0 = Devel|Win32 + {ED2F21FD-0A36-4A8F-9B90-E7D92A2ACB63}.Devel AVX2|x64.ActiveCfg = Devel|x64 + {ED2F21FD-0A36-4A8F-9B90-E7D92A2ACB63}.Devel AVX2|x64.Build.0 = Devel|x64 + {ED2F21FD-0A36-4A8F-9B90-E7D92A2ACB63}.Devel|Win32.ActiveCfg = Devel|Win32 + {ED2F21FD-0A36-4A8F-9B90-E7D92A2ACB63}.Devel|Win32.Build.0 = Devel|Win32 + {ED2F21FD-0A36-4A8F-9B90-E7D92A2ACB63}.Devel|x64.ActiveCfg = Devel|x64 + {ED2F21FD-0A36-4A8F-9B90-E7D92A2ACB63}.Devel|x64.Build.0 = Devel|x64 + {ED2F21FD-0A36-4A8F-9B90-E7D92A2ACB63}.Release AVX2|Win32.ActiveCfg = Release|Win32 + {ED2F21FD-0A36-4A8F-9B90-E7D92A2ACB63}.Release AVX2|Win32.Build.0 = Release|Win32 + {ED2F21FD-0A36-4A8F-9B90-E7D92A2ACB63}.Release AVX2|x64.ActiveCfg = Release|x64 + {ED2F21FD-0A36-4A8F-9B90-E7D92A2ACB63}.Release AVX2|x64.Build.0 = Release|x64 + {ED2F21FD-0A36-4A8F-9B90-E7D92A2ACB63}.Release|Win32.ActiveCfg = Release|Win32 + {ED2F21FD-0A36-4A8F-9B90-E7D92A2ACB63}.Release|Win32.Build.0 = Release|Win32 + {ED2F21FD-0A36-4A8F-9B90-E7D92A2ACB63}.Release|x64.ActiveCfg = Release|x64 + {ED2F21FD-0A36-4A8F-9B90-E7D92A2ACB63}.Release|x64.Build.0 = Release|x64 + {4639972E-424E-4E13-8B07-CA403C481346}.Debug AVX2|Win32.ActiveCfg = Debug|Win32 + {4639972E-424E-4E13-8B07-CA403C481346}.Debug AVX2|Win32.Build.0 = Debug|Win32 + {4639972E-424E-4E13-8B07-CA403C481346}.Debug AVX2|x64.ActiveCfg = Debug|x64 + {4639972E-424E-4E13-8B07-CA403C481346}.Debug AVX2|x64.Build.0 = Debug|x64 + {4639972E-424E-4E13-8B07-CA403C481346}.Debug|Win32.ActiveCfg = Debug|Win32 + {4639972E-424E-4E13-8B07-CA403C481346}.Debug|Win32.Build.0 = Debug|Win32 + {4639972E-424E-4E13-8B07-CA403C481346}.Debug|x64.ActiveCfg = Debug|x64 + {4639972E-424E-4E13-8B07-CA403C481346}.Debug|x64.Build.0 = Debug|x64 + {4639972E-424E-4E13-8B07-CA403C481346}.Devel AVX2|Win32.ActiveCfg = Devel|Win32 + {4639972E-424E-4E13-8B07-CA403C481346}.Devel AVX2|Win32.Build.0 = Devel|Win32 + {4639972E-424E-4E13-8B07-CA403C481346}.Devel AVX2|x64.ActiveCfg = Devel|x64 + {4639972E-424E-4E13-8B07-CA403C481346}.Devel AVX2|x64.Build.0 = Devel|x64 + {4639972E-424E-4E13-8B07-CA403C481346}.Devel|Win32.ActiveCfg = Devel|Win32 + {4639972E-424E-4E13-8B07-CA403C481346}.Devel|Win32.Build.0 = Devel|Win32 + {4639972E-424E-4E13-8B07-CA403C481346}.Devel|x64.ActiveCfg = Devel|x64 + {4639972E-424E-4E13-8B07-CA403C481346}.Devel|x64.Build.0 = Devel|x64 + {4639972E-424E-4E13-8B07-CA403C481346}.Release AVX2|Win32.ActiveCfg = Release|Win32 + {4639972E-424E-4E13-8B07-CA403C481346}.Release AVX2|Win32.Build.0 = Release|Win32 + {4639972E-424E-4E13-8B07-CA403C481346}.Release AVX2|x64.ActiveCfg = Release|x64 + {4639972E-424E-4E13-8B07-CA403C481346}.Release AVX2|x64.Build.0 = Release|x64 + {4639972E-424E-4E13-8B07-CA403C481346}.Release|Win32.ActiveCfg = Release|Win32 + {4639972E-424E-4E13-8B07-CA403C481346}.Release|Win32.Build.0 = Release|Win32 + {4639972E-424E-4E13-8B07-CA403C481346}.Release|x64.ActiveCfg = Release|x64 + {4639972E-424E-4E13-8B07-CA403C481346}.Release|x64.Build.0 = Release|x64 + {6C7986C4-3E4D-4DCC-B3C6-6BB12B238995}.Debug AVX2|Win32.ActiveCfg = Debug AVX2|Win32 + {6C7986C4-3E4D-4DCC-B3C6-6BB12B238995}.Debug AVX2|Win32.Build.0 = Debug AVX2|Win32 + {6C7986C4-3E4D-4DCC-B3C6-6BB12B238995}.Debug AVX2|x64.ActiveCfg = Debug AVX2|x64 + {6C7986C4-3E4D-4DCC-B3C6-6BB12B238995}.Debug AVX2|x64.Build.0 = Debug AVX2|x64 + {6C7986C4-3E4D-4DCC-B3C6-6BB12B238995}.Debug|Win32.ActiveCfg = Debug|Win32 + {6C7986C4-3E4D-4DCC-B3C6-6BB12B238995}.Debug|Win32.Build.0 = Debug|Win32 + {6C7986C4-3E4D-4DCC-B3C6-6BB12B238995}.Debug|x64.ActiveCfg = Debug|x64 + {6C7986C4-3E4D-4DCC-B3C6-6BB12B238995}.Debug|x64.Build.0 = Debug|x64 + {6C7986C4-3E4D-4DCC-B3C6-6BB12B238995}.Devel AVX2|Win32.ActiveCfg = Devel AVX2|Win32 + {6C7986C4-3E4D-4DCC-B3C6-6BB12B238995}.Devel AVX2|Win32.Build.0 = Devel AVX2|Win32 + {6C7986C4-3E4D-4DCC-B3C6-6BB12B238995}.Devel AVX2|x64.ActiveCfg = Devel AVX2|x64 + {6C7986C4-3E4D-4DCC-B3C6-6BB12B238995}.Devel AVX2|x64.Build.0 = Devel AVX2|x64 + {6C7986C4-3E4D-4DCC-B3C6-6BB12B238995}.Devel|Win32.ActiveCfg = Devel|Win32 + {6C7986C4-3E4D-4DCC-B3C6-6BB12B238995}.Devel|Win32.Build.0 = Devel|Win32 + {6C7986C4-3E4D-4DCC-B3C6-6BB12B238995}.Devel|x64.ActiveCfg = Devel|x64 + {6C7986C4-3E4D-4DCC-B3C6-6BB12B238995}.Devel|x64.Build.0 = Devel|x64 + {6C7986C4-3E4D-4DCC-B3C6-6BB12B238995}.Release AVX2|Win32.ActiveCfg = Release AVX2|Win32 + {6C7986C4-3E4D-4DCC-B3C6-6BB12B238995}.Release AVX2|Win32.Build.0 = Release AVX2|Win32 + {6C7986C4-3E4D-4DCC-B3C6-6BB12B238995}.Release AVX2|x64.ActiveCfg = Release AVX2|x64 + {6C7986C4-3E4D-4DCC-B3C6-6BB12B238995}.Release AVX2|x64.Build.0 = Release AVX2|x64 + {6C7986C4-3E4D-4DCC-B3C6-6BB12B238995}.Release|Win32.ActiveCfg = Release|Win32 + {6C7986C4-3E4D-4DCC-B3C6-6BB12B238995}.Release|Win32.Build.0 = Release|Win32 + {6C7986C4-3E4D-4DCC-B3C6-6BB12B238995}.Release|x64.ActiveCfg = Release|x64 + {6C7986C4-3E4D-4DCC-B3C6-6BB12B238995}.Release|x64.Build.0 = Release|x64 + {C0293B32-5ACF-40F0-AA6C-E6DA6F3BF33A}.Debug AVX2|Win32.ActiveCfg = Debug|Win32 + {C0293B32-5ACF-40F0-AA6C-E6DA6F3BF33A}.Debug AVX2|Win32.Build.0 = Debug|Win32 + {C0293B32-5ACF-40F0-AA6C-E6DA6F3BF33A}.Debug AVX2|x64.ActiveCfg = Debug|x64 + {C0293B32-5ACF-40F0-AA6C-E6DA6F3BF33A}.Debug AVX2|x64.Build.0 = Debug|x64 + {C0293B32-5ACF-40F0-AA6C-E6DA6F3BF33A}.Debug|Win32.ActiveCfg = Debug|Win32 + {C0293B32-5ACF-40F0-AA6C-E6DA6F3BF33A}.Debug|Win32.Build.0 = Debug|Win32 + {C0293B32-5ACF-40F0-AA6C-E6DA6F3BF33A}.Debug|x64.ActiveCfg = Debug|x64 + {C0293B32-5ACF-40F0-AA6C-E6DA6F3BF33A}.Debug|x64.Build.0 = Debug|x64 + {C0293B32-5ACF-40F0-AA6C-E6DA6F3BF33A}.Devel AVX2|Win32.ActiveCfg = Devel|Win32 + {C0293B32-5ACF-40F0-AA6C-E6DA6F3BF33A}.Devel AVX2|Win32.Build.0 = Devel|Win32 + {C0293B32-5ACF-40F0-AA6C-E6DA6F3BF33A}.Devel AVX2|x64.ActiveCfg = Devel|x64 + {C0293B32-5ACF-40F0-AA6C-E6DA6F3BF33A}.Devel AVX2|x64.Build.0 = Devel|x64 + {C0293B32-5ACF-40F0-AA6C-E6DA6F3BF33A}.Devel|Win32.ActiveCfg = Devel|Win32 + {C0293B32-5ACF-40F0-AA6C-E6DA6F3BF33A}.Devel|Win32.Build.0 = Devel|Win32 + {C0293B32-5ACF-40F0-AA6C-E6DA6F3BF33A}.Devel|x64.ActiveCfg = Devel|x64 + {C0293B32-5ACF-40F0-AA6C-E6DA6F3BF33A}.Devel|x64.Build.0 = Devel|x64 + {C0293B32-5ACF-40F0-AA6C-E6DA6F3BF33A}.Release AVX2|Win32.ActiveCfg = Release|Win32 + {C0293B32-5ACF-40F0-AA6C-E6DA6F3BF33A}.Release AVX2|Win32.Build.0 = Release|Win32 + {C0293B32-5ACF-40F0-AA6C-E6DA6F3BF33A}.Release AVX2|x64.ActiveCfg = Release|x64 + {C0293B32-5ACF-40F0-AA6C-E6DA6F3BF33A}.Release AVX2|x64.Build.0 = Release|x64 + {C0293B32-5ACF-40F0-AA6C-E6DA6F3BF33A}.Release|Win32.ActiveCfg = Release|Win32 + {C0293B32-5ACF-40F0-AA6C-E6DA6F3BF33A}.Release|Win32.Build.0 = Release|Win32 + {C0293B32-5ACF-40F0-AA6C-E6DA6F3BF33A}.Release|x64.ActiveCfg = Release|x64 + {C0293B32-5ACF-40F0-AA6C-E6DA6F3BF33A}.Release|x64.Build.0 = Release|x64 + {88FB34EC-845E-4F21-A552-F1573B9ED167}.Debug AVX2|Win32.ActiveCfg = Debug|Win32 + {88FB34EC-845E-4F21-A552-F1573B9ED167}.Debug AVX2|Win32.Build.0 = Debug|Win32 + {88FB34EC-845E-4F21-A552-F1573B9ED167}.Debug AVX2|x64.ActiveCfg = Debug|x64 + {88FB34EC-845E-4F21-A552-F1573B9ED167}.Debug AVX2|x64.Build.0 = Debug|x64 + {88FB34EC-845E-4F21-A552-F1573B9ED167}.Debug|Win32.ActiveCfg = Debug|Win32 + {88FB34EC-845E-4F21-A552-F1573B9ED167}.Debug|Win32.Build.0 = Debug|Win32 + {88FB34EC-845E-4F21-A552-F1573B9ED167}.Debug|x64.ActiveCfg = Debug|x64 + {88FB34EC-845E-4F21-A552-F1573B9ED167}.Debug|x64.Build.0 = Debug|x64 + {88FB34EC-845E-4F21-A552-F1573B9ED167}.Devel AVX2|Win32.ActiveCfg = Devel|Win32 + {88FB34EC-845E-4F21-A552-F1573B9ED167}.Devel AVX2|Win32.Build.0 = Devel|Win32 + {88FB34EC-845E-4F21-A552-F1573B9ED167}.Devel AVX2|x64.ActiveCfg = Devel|x64 + {88FB34EC-845E-4F21-A552-F1573B9ED167}.Devel AVX2|x64.Build.0 = Devel|x64 + {88FB34EC-845E-4F21-A552-F1573B9ED167}.Devel|Win32.ActiveCfg = Devel|Win32 + {88FB34EC-845E-4F21-A552-F1573B9ED167}.Devel|Win32.Build.0 = Devel|Win32 + {88FB34EC-845E-4F21-A552-F1573B9ED167}.Devel|x64.ActiveCfg = Devel|x64 + {88FB34EC-845E-4F21-A552-F1573B9ED167}.Devel|x64.Build.0 = Devel|x64 + {88FB34EC-845E-4F21-A552-F1573B9ED167}.Release AVX2|Win32.ActiveCfg = Release|Win32 + {88FB34EC-845E-4F21-A552-F1573B9ED167}.Release AVX2|Win32.Build.0 = Release|Win32 + {88FB34EC-845E-4F21-A552-F1573B9ED167}.Release AVX2|x64.ActiveCfg = Release|x64 + {88FB34EC-845E-4F21-A552-F1573B9ED167}.Release AVX2|x64.Build.0 = Release|x64 + {88FB34EC-845E-4F21-A552-F1573B9ED167}.Release|Win32.ActiveCfg = Release|Win32 + {88FB34EC-845E-4F21-A552-F1573B9ED167}.Release|Win32.Build.0 = Release|Win32 + {88FB34EC-845E-4F21-A552-F1573B9ED167}.Release|x64.ActiveCfg = Release|x64 + {88FB34EC-845E-4F21-A552-F1573B9ED167}.Release|x64.Build.0 = Release|x64 + {2A016F21-87AE-4154-8271-1F57E91408E9}.Debug AVX2|Win32.ActiveCfg = Debug AVX2|Win32 + {2A016F21-87AE-4154-8271-1F57E91408E9}.Debug AVX2|Win32.Build.0 = Debug AVX2|Win32 + {2A016F21-87AE-4154-8271-1F57E91408E9}.Debug AVX2|x64.ActiveCfg = Debug AVX2|x64 + {2A016F21-87AE-4154-8271-1F57E91408E9}.Debug AVX2|x64.Build.0 = Debug AVX2|x64 + {2A016F21-87AE-4154-8271-1F57E91408E9}.Debug|Win32.ActiveCfg = Debug|Win32 + {2A016F21-87AE-4154-8271-1F57E91408E9}.Debug|Win32.Build.0 = Debug|Win32 + {2A016F21-87AE-4154-8271-1F57E91408E9}.Debug|x64.ActiveCfg = Debug|x64 + {2A016F21-87AE-4154-8271-1F57E91408E9}.Debug|x64.Build.0 = Debug|x64 + {2A016F21-87AE-4154-8271-1F57E91408E9}.Devel AVX2|Win32.ActiveCfg = Devel AVX2|Win32 + {2A016F21-87AE-4154-8271-1F57E91408E9}.Devel AVX2|Win32.Build.0 = Devel AVX2|Win32 + {2A016F21-87AE-4154-8271-1F57E91408E9}.Devel AVX2|x64.ActiveCfg = Devel AVX2|x64 + {2A016F21-87AE-4154-8271-1F57E91408E9}.Devel AVX2|x64.Build.0 = Devel AVX2|x64 + {2A016F21-87AE-4154-8271-1F57E91408E9}.Devel|Win32.ActiveCfg = Devel|Win32 + {2A016F21-87AE-4154-8271-1F57E91408E9}.Devel|Win32.Build.0 = Devel|Win32 + {2A016F21-87AE-4154-8271-1F57E91408E9}.Devel|x64.ActiveCfg = Devel|x64 + {2A016F21-87AE-4154-8271-1F57E91408E9}.Devel|x64.Build.0 = Devel|x64 + {2A016F21-87AE-4154-8271-1F57E91408E9}.Release AVX2|Win32.ActiveCfg = Release AVX2|Win32 + {2A016F21-87AE-4154-8271-1F57E91408E9}.Release AVX2|Win32.Build.0 = Release AVX2|Win32 + {2A016F21-87AE-4154-8271-1F57E91408E9}.Release AVX2|x64.ActiveCfg = Release AVX2|x64 + {2A016F21-87AE-4154-8271-1F57E91408E9}.Release AVX2|x64.Build.0 = Release AVX2|x64 + {2A016F21-87AE-4154-8271-1F57E91408E9}.Release|Win32.ActiveCfg = Release|Win32 + {2A016F21-87AE-4154-8271-1F57E91408E9}.Release|Win32.Build.0 = Release|Win32 + {2A016F21-87AE-4154-8271-1F57E91408E9}.Release|x64.ActiveCfg = Release|x64 + {2A016F21-87AE-4154-8271-1F57E91408E9}.Release|x64.Build.0 = Release|x64 + {1EC8B3C0-8FB3-46DE-A2E0-A9121203F266}.Debug AVX2|Win32.ActiveCfg = Debug|Win32 + {1EC8B3C0-8FB3-46DE-A2E0-A9121203F266}.Debug AVX2|Win32.Build.0 = Debug|Win32 + {1EC8B3C0-8FB3-46DE-A2E0-A9121203F266}.Debug AVX2|x64.ActiveCfg = Debug|x64 + {1EC8B3C0-8FB3-46DE-A2E0-A9121203F266}.Debug AVX2|x64.Build.0 = Debug|x64 + {1EC8B3C0-8FB3-46DE-A2E0-A9121203F266}.Debug|Win32.ActiveCfg = Debug|Win32 + {1EC8B3C0-8FB3-46DE-A2E0-A9121203F266}.Debug|Win32.Build.0 = Debug|Win32 + {1EC8B3C0-8FB3-46DE-A2E0-A9121203F266}.Debug|x64.ActiveCfg = Debug|x64 + {1EC8B3C0-8FB3-46DE-A2E0-A9121203F266}.Debug|x64.Build.0 = Debug|x64 + {1EC8B3C0-8FB3-46DE-A2E0-A9121203F266}.Devel AVX2|Win32.ActiveCfg = Devel|Win32 + {1EC8B3C0-8FB3-46DE-A2E0-A9121203F266}.Devel AVX2|Win32.Build.0 = Devel|Win32 + {1EC8B3C0-8FB3-46DE-A2E0-A9121203F266}.Devel AVX2|x64.ActiveCfg = Devel|x64 + {1EC8B3C0-8FB3-46DE-A2E0-A9121203F266}.Devel AVX2|x64.Build.0 = Devel|x64 + {1EC8B3C0-8FB3-46DE-A2E0-A9121203F266}.Devel|Win32.ActiveCfg = Devel|Win32 + {1EC8B3C0-8FB3-46DE-A2E0-A9121203F266}.Devel|Win32.Build.0 = Devel|Win32 + {1EC8B3C0-8FB3-46DE-A2E0-A9121203F266}.Devel|x64.ActiveCfg = Devel|x64 + {1EC8B3C0-8FB3-46DE-A2E0-A9121203F266}.Devel|x64.Build.0 = Devel|x64 + {1EC8B3C0-8FB3-46DE-A2E0-A9121203F266}.Release AVX2|Win32.ActiveCfg = Release|Win32 + {1EC8B3C0-8FB3-46DE-A2E0-A9121203F266}.Release AVX2|Win32.Build.0 = Release|Win32 + {1EC8B3C0-8FB3-46DE-A2E0-A9121203F266}.Release AVX2|x64.ActiveCfg = Release|x64 + {1EC8B3C0-8FB3-46DE-A2E0-A9121203F266}.Release AVX2|x64.Build.0 = Release|x64 + {1EC8B3C0-8FB3-46DE-A2E0-A9121203F266}.Release|Win32.ActiveCfg = Release|Win32 + {1EC8B3C0-8FB3-46DE-A2E0-A9121203F266}.Release|Win32.Build.0 = Release|Win32 + {1EC8B3C0-8FB3-46DE-A2E0-A9121203F266}.Release|x64.ActiveCfg = Release|x64 + {1EC8B3C0-8FB3-46DE-A2E0-A9121203F266}.Release|x64.Build.0 = Release|x64 + {BF74C473-DC04-44B3-92E8-4145F4E77342}.Debug AVX2|Win32.ActiveCfg = Debug|Win32 + {BF74C473-DC04-44B3-92E8-4145F4E77342}.Debug AVX2|Win32.Build.0 = Debug|Win32 + {BF74C473-DC04-44B3-92E8-4145F4E77342}.Debug AVX2|x64.ActiveCfg = Debug|x64 + {BF74C473-DC04-44B3-92E8-4145F4E77342}.Debug AVX2|x64.Build.0 = Debug|x64 + {BF74C473-DC04-44B3-92E8-4145F4E77342}.Debug|Win32.ActiveCfg = Debug|Win32 + {BF74C473-DC04-44B3-92E8-4145F4E77342}.Debug|Win32.Build.0 = Debug|Win32 + {BF74C473-DC04-44B3-92E8-4145F4E77342}.Debug|x64.ActiveCfg = Debug|x64 + {BF74C473-DC04-44B3-92E8-4145F4E77342}.Debug|x64.Build.0 = Debug|x64 + {BF74C473-DC04-44B3-92E8-4145F4E77342}.Devel AVX2|Win32.ActiveCfg = Devel|Win32 + {BF74C473-DC04-44B3-92E8-4145F4E77342}.Devel AVX2|Win32.Build.0 = Devel|Win32 + {BF74C473-DC04-44B3-92E8-4145F4E77342}.Devel AVX2|x64.ActiveCfg = Devel|x64 + {BF74C473-DC04-44B3-92E8-4145F4E77342}.Devel AVX2|x64.Build.0 = Devel|x64 + {BF74C473-DC04-44B3-92E8-4145F4E77342}.Devel|Win32.ActiveCfg = Devel|Win32 + {BF74C473-DC04-44B3-92E8-4145F4E77342}.Devel|Win32.Build.0 = Devel|Win32 + {BF74C473-DC04-44B3-92E8-4145F4E77342}.Devel|x64.ActiveCfg = Devel|x64 + {BF74C473-DC04-44B3-92E8-4145F4E77342}.Devel|x64.Build.0 = Devel|x64 + {BF74C473-DC04-44B3-92E8-4145F4E77342}.Release AVX2|Win32.ActiveCfg = Release|Win32 + {BF74C473-DC04-44B3-92E8-4145F4E77342}.Release AVX2|Win32.Build.0 = Release|Win32 + {BF74C473-DC04-44B3-92E8-4145F4E77342}.Release AVX2|x64.ActiveCfg = Release|x64 + {BF74C473-DC04-44B3-92E8-4145F4E77342}.Release AVX2|x64.Build.0 = Release|x64 + {BF74C473-DC04-44B3-92E8-4145F4E77342}.Release|Win32.ActiveCfg = Release|Win32 + {BF74C473-DC04-44B3-92E8-4145F4E77342}.Release|Win32.Build.0 = Release|Win32 + {BF74C473-DC04-44B3-92E8-4145F4E77342}.Release|x64.ActiveCfg = Release|x64 + {BF74C473-DC04-44B3-92E8-4145F4E77342}.Release|x64.Build.0 = Release|x64 + {DE9653B6-17DD-356A-9EE0-28A731772587}.Debug AVX2|Win32.ActiveCfg = Debug|Win32 + {DE9653B6-17DD-356A-9EE0-28A731772587}.Debug AVX2|Win32.Build.0 = Debug|Win32 + {DE9653B6-17DD-356A-9EE0-28A731772587}.Debug AVX2|x64.ActiveCfg = Debug|x64 + {DE9653B6-17DD-356A-9EE0-28A731772587}.Debug AVX2|x64.Build.0 = Debug|x64 + {DE9653B6-17DD-356A-9EE0-28A731772587}.Debug|Win32.ActiveCfg = Debug|Win32 + {DE9653B6-17DD-356A-9EE0-28A731772587}.Debug|Win32.Build.0 = Debug|Win32 + {DE9653B6-17DD-356A-9EE0-28A731772587}.Debug|x64.ActiveCfg = Debug|x64 + {DE9653B6-17DD-356A-9EE0-28A731772587}.Debug|x64.Build.0 = Debug|x64 + {DE9653B6-17DD-356A-9EE0-28A731772587}.Devel AVX2|Win32.ActiveCfg = Devel|Win32 + {DE9653B6-17DD-356A-9EE0-28A731772587}.Devel AVX2|Win32.Build.0 = Devel|Win32 + {DE9653B6-17DD-356A-9EE0-28A731772587}.Devel AVX2|x64.ActiveCfg = Devel|x64 + {DE9653B6-17DD-356A-9EE0-28A731772587}.Devel AVX2|x64.Build.0 = Devel|x64 + {DE9653B6-17DD-356A-9EE0-28A731772587}.Devel|Win32.ActiveCfg = Devel|Win32 + {DE9653B6-17DD-356A-9EE0-28A731772587}.Devel|Win32.Build.0 = Devel|Win32 + {DE9653B6-17DD-356A-9EE0-28A731772587}.Devel|x64.ActiveCfg = Devel|x64 + {DE9653B6-17DD-356A-9EE0-28A731772587}.Devel|x64.Build.0 = Devel|x64 + {DE9653B6-17DD-356A-9EE0-28A731772587}.Release AVX2|Win32.ActiveCfg = Release|Win32 + {DE9653B6-17DD-356A-9EE0-28A731772587}.Release AVX2|Win32.Build.0 = Release|Win32 + {DE9653B6-17DD-356A-9EE0-28A731772587}.Release AVX2|x64.ActiveCfg = Release|x64 + {DE9653B6-17DD-356A-9EE0-28A731772587}.Release AVX2|x64.Build.0 = Release|x64 + {DE9653B6-17DD-356A-9EE0-28A731772587}.Release|Win32.ActiveCfg = Release|Win32 + {DE9653B6-17DD-356A-9EE0-28A731772587}.Release|Win32.Build.0 = Release|Win32 + {DE9653B6-17DD-356A-9EE0-28A731772587}.Release|x64.ActiveCfg = Release|x64 + {DE9653B6-17DD-356A-9EE0-28A731772587}.Release|x64.Build.0 = Release|x64 + {EF6834A9-11F3-4331-BC34-21B325ABB180}.Debug AVX2|Win32.ActiveCfg = Debug|Win32 + {EF6834A9-11F3-4331-BC34-21B325ABB180}.Debug AVX2|Win32.Build.0 = Debug|Win32 + {EF6834A9-11F3-4331-BC34-21B325ABB180}.Debug AVX2|x64.ActiveCfg = Debug|x64 + {EF6834A9-11F3-4331-BC34-21B325ABB180}.Debug AVX2|x64.Build.0 = Debug|x64 + {EF6834A9-11F3-4331-BC34-21B325ABB180}.Debug|Win32.ActiveCfg = Debug|Win32 + {EF6834A9-11F3-4331-BC34-21B325ABB180}.Debug|Win32.Build.0 = Debug|Win32 + {EF6834A9-11F3-4331-BC34-21B325ABB180}.Debug|x64.ActiveCfg = Debug|x64 + {EF6834A9-11F3-4331-BC34-21B325ABB180}.Debug|x64.Build.0 = Debug|x64 + {EF6834A9-11F3-4331-BC34-21B325ABB180}.Devel AVX2|Win32.ActiveCfg = Devel|Win32 + {EF6834A9-11F3-4331-BC34-21B325ABB180}.Devel AVX2|Win32.Build.0 = Devel|Win32 + {EF6834A9-11F3-4331-BC34-21B325ABB180}.Devel AVX2|x64.ActiveCfg = Devel|x64 + {EF6834A9-11F3-4331-BC34-21B325ABB180}.Devel AVX2|x64.Build.0 = Devel|x64 + {EF6834A9-11F3-4331-BC34-21B325ABB180}.Devel|Win32.ActiveCfg = Devel|Win32 + {EF6834A9-11F3-4331-BC34-21B325ABB180}.Devel|Win32.Build.0 = Devel|Win32 + {EF6834A9-11F3-4331-BC34-21B325ABB180}.Devel|x64.ActiveCfg = Devel|x64 + {EF6834A9-11F3-4331-BC34-21B325ABB180}.Devel|x64.Build.0 = Devel|x64 + {EF6834A9-11F3-4331-BC34-21B325ABB180}.Release AVX2|Win32.ActiveCfg = Release|Win32 + {EF6834A9-11F3-4331-BC34-21B325ABB180}.Release AVX2|Win32.Build.0 = Release|Win32 + {EF6834A9-11F3-4331-BC34-21B325ABB180}.Release AVX2|x64.ActiveCfg = Release|x64 + {EF6834A9-11F3-4331-BC34-21B325ABB180}.Release AVX2|x64.Build.0 = Release|x64 + {EF6834A9-11F3-4331-BC34-21B325ABB180}.Release|Win32.ActiveCfg = Release|Win32 + {EF6834A9-11F3-4331-BC34-21B325ABB180}.Release|Win32.Build.0 = Release|Win32 + {EF6834A9-11F3-4331-BC34-21B325ABB180}.Release|x64.ActiveCfg = Release|x64 + {EF6834A9-11F3-4331-BC34-21B325ABB180}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {E9B51944-7E6D-4BCD-83F2-7BBD5A46182D} = {78EBE642-7A4D-4EA7-86BE-5639C6646C38} + {2F6C0388-20CB-4242-9F6C-A6EBB6A83F47} = {78EBE642-7A4D-4EA7-86BE-5639C6646C38} + {677B7D11-D5E1-40B3-88B1-9A4DF83D2213} = {2D6F0A62-A247-4CCF-947F-FCD54BE16103} + {BC236261-77E8-4567-8D09-45CD02965EB6} = {78EBE642-7A4D-4EA7-86BE-5639C6646C38} + {01F4CE10-2CFB-41A8-B41F-E54337868A1D} = {78EBE642-7A4D-4EA7-86BE-5639C6646C38} + {3FCC50C2-81E9-5DB2-B8D8-2129427568B1} = {78EBE642-7A4D-4EA7-86BE-5639C6646C38} + {D6973076-9317-4EF2-A0B8-B7A18AC0713E} = {78EBE642-7A4D-4EA7-86BE-5639C6646C38} + {0FAE817D-9A32-4830-857E-81DA57246E16} = {78EBE642-7A4D-4EA7-86BE-5639C6646C38} + {27F17499-A372-4408-8AFA-4F9F4584FBD3} = {78EBE642-7A4D-4EA7-86BE-5639C6646C38} + {12728250-16EC-4DC6-94D7-E21DD88947F8} = {78EBE642-7A4D-4EA7-86BE-5639C6646C38} + {449AD25E-424A-4714-BABC-68706CDCC33B} = {78EBE642-7A4D-4EA7-86BE-5639C6646C38} + {47AFDBEF-F15F-4BC0-B436-5BE443C3F80F} = {78EBE642-7A4D-4EA7-86BE-5639C6646C38} + {A0D2B3AD-1F72-4EE3-8B5C-F2C358DA35F0} = {78EBE642-7A4D-4EA7-86BE-5639C6646C38} + {ED2F21FD-0A36-4A8F-9B90-E7D92A2ACB63} = {78EBE642-7A4D-4EA7-86BE-5639C6646C38} + {C0293B32-5ACF-40F0-AA6C-E6DA6F3BF33A} = {78EBE642-7A4D-4EA7-86BE-5639C6646C38} + {88FB34EC-845E-4F21-A552-F1573B9ED167} = {78EBE642-7A4D-4EA7-86BE-5639C6646C38} + {1EC8B3C0-8FB3-46DE-A2E0-A9121203F266} = {78EBE642-7A4D-4EA7-86BE-5639C6646C38} + {BF74C473-DC04-44B3-92E8-4145F4E77342} = {78EBE642-7A4D-4EA7-86BE-5639C6646C38} + {DE9653B6-17DD-356A-9EE0-28A731772587} = {78EBE642-7A4D-4EA7-86BE-5639C6646C38} + {EF6834A9-11F3-4331-BC34-21B325ABB180} = {78EBE642-7A4D-4EA7-86BE-5639C6646C38} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {0BC474EA-3628-45D3-9DBC-E22D0B7E0F77} + EndGlobalSection +EndGlobal diff --git a/bin/resources/cover-placeholder.png b/bin/resources/cover-placeholder.png new file mode 100644 index 0000000000000000000000000000000000000000..2108b465642987b4d309d7dd30abcfda825244d6 GIT binary patch literal 83189 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 z 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 b*oN8e{sCEEo z+itMf8{ir~Q5`lLTI@E!eI8cv2d4U)<$@nYnrrVgfT2N+!OqkKcebB9cgQvzot@#= z+Kd7LuKORs>s;5g!sRvQ>K%o(?&5AKPqY>*0SE8@zy)RFwb6P#EVP^?xWHj(#qO$L zzQ`xaa1O>;LBvi9*ZJY>O6fYYX^ncUGA&N%PC2|V=Z?ritWqX}!41Ki%4r}S-MJt0 zO)>=w`>hz})utc4_uu8WTm&$VV6uTMg=eN~y#Q(L!XZ&E-rmD-n4P&pWH*bP0q@>r z8|%$>y{D%SDnkVh-0nBfDgv{?*Y4+(!?(EtHguClP6Vgt^|O@hk4jWbDZE)Lr8}j# zPCAuqPx64BED(ii2w=d$mRA_PaFysQhF;ud!N&CA7st#&H%uu2&!>ImJ4)+YOygP_ zPa&67G3tsHuuR4>e9dQ!TsO&v4ZH+hH`nA9)Uy^0S?HQ{m9M#XwM|xb`FA7HOO_~wybws*PEEFU9fnac1*weN^}*Vraz%@i}`B7(+JE%1J@?& z|4PG+Z@`{K$jt}=b4=pdw()vb0hIR@nvv}Ui+?d;5~W6L3}#xqLm&z~^tGo2VTcZQqT*3o*_O&D0i`_iP*oaLuV#)G`^F6a0& zfQBwqF_nq0L$uJwgeWwkk2$xdhbUv8yio5hYJ^GQ!7oyAEp}3tBO-z)l+>tCr(J4bF%q86@V6S$Ewkd@g&@@^~(ZznXT1*$N ziU!6ODtvElQoU{gcHkXU)xb-m;988Ar3>)VEHFyE0MA@I?dx*0PfJJJnkFOh;-qPV z5h@T9h;_(iE$f0(B~5>DF<; z`#kU_T1dQGY_(y-1@T(Rv+&w!-}`Mihj3Q*+L)Dnd+9e`YAaNi+(UKV^E5O2PS0Vf zi|ss`QG1$cLR)0pQ>YD1pSfEYnUdS#25CdzTt$7KQlh7SSY)w@^@i8MHzr(H19t|D z-h@2OshmgJblpgc?OOZYT@Jjnf5n@E@iyL5>xJ$q-uG|S8#t_)Oi`NkX!{sOlS0od8a>Sy0^*Iq-b0!bx>n2nJ||z{*=)a5Lrm<@7W%M$9lCE# z@Au@Q{f5GLlPi%9g>K{3dOf5{Ena%tOn~DYuH{R{rtyk3?g2e;r}5%@-($DIt8YkP zHuTM7bdPY)2e=VA)aXWaT)V$?C-E{*^wKaLCvg;moGwF$5s^3{mBLbo`%)06sNKo- zqOxE6{_ge1kIK&MukJJX(sXksA@OE$e=Z|w*yTUn=e~T;6t1=?R4I{cpNWM`mOsy& z4&!;B6<8U{40%&`@-d9tG02vpPMm|K`ZjDDaq8n}0H%RTBXhx#vVkpa3eRtsHb&~zxgLbh=Wr`Jk{aOZWxxu3nUtA#H1Pg2PR zniJucMqrx{dZlINL|&1NRi9i&5qoH6h-Wzv{&~xQ9SemQjHGLuGP_c zLz3#9#zPI<$8jz7srpPuX~HdM(RkA=DFvzbT`&O7jVdr4OA)Z;o_h*p#<5V-s`9CZ zqoM5AJkrX~8bcHHovQYT_6Mit3|oToT|m|qX;ezO9Jbru`HZwt1zf9&5n4~l-@L2F zi)_1-swLi`LAK%MUR!J9{45;acD`~1-Yux0PWfCT-A>^Uti=m)L3O}uTs+q|oGPyOBJo>Gn8Te84ZKWC z5Qf+aUG99L=_wrwo0%su4Xj;GpfyLvD!LY;g7uso>SblTQk&fWtdgqoe zdJPpk#FBPhIqfAko76)lv?%-^;7g2fd7)=*M~i?u|Dd(BJNE_|MJMbGB zh__s-r;gtBpg06--9U?eJnL`;^wdWr)-jB=FW|2H?6%JF4(pY0a2Mz1t2#6e-o$I& zv$X6zl=0#mI5gm@+?pa{UOBwm6AA)Diy=>_DF}rLCI&?naW Y7a3yE>0WhE)c^nh07*qoM6N<$f-u)-9smFU literal 0 HcmV?d00001 diff --git a/cmake/SearchForStuff.cmake b/cmake/SearchForStuff.cmake index 1e4e073332..63934ff50c 100644 --- a/cmake/SearchForStuff.cmake +++ b/cmake/SearchForStuff.cmake @@ -232,6 +232,10 @@ if(NOT USE_SYSTEM_YAML) endif() endif() +if(QT_BUILD) + find_package(Qt6 COMPONENTS Core Gui Widgets Network LinguistTools REQUIRED) +endif() + add_subdirectory(3rdparty/libchdr/libchdr EXCLUDE_FROM_ALL) if(USE_NATIVE_TOOLS) diff --git a/common/vsprops/QtCompile.props b/common/vsprops/QtCompile.props new file mode 100644 index 0000000000..a3bdfb5d2b --- /dev/null +++ b/common/vsprops/QtCompile.props @@ -0,0 +1,197 @@ + + + + $(SolutionDir)bin\ + $(SolutionDir)3rdparty\qt\6.2.2\msvc2019_64\ + $(SolutionDir)3rdparty\qt\6.2.2\msvc2019_arm64\ + $(QTDIRDefault) + $(QTDIR)\ + $(SolutionDir)3rdparty\qt\6.2.2\msvc2019_64\ + false + true + $(QTDIR)include\ + $(QTDIR)lib\ + $(QTDIR)bin\ + $(QTDIRHost)bin\ + $(QTDIR)plugins\ + $(QTDIR)translations\ + $(IntDir) + $(QtToolOutDir)moc_ + $(BinaryOutputDir)translations\ + d + $(QtDebugSuffix) + QtPlugins + + + + QT_NO_DEBUG;%(PreprocessorDefinitions) + $(ProjectDir);%(AdditionalIncludeDirectories) + $(QtToolOutDir);%(AdditionalIncludeDirectories) + $(QtIncludeDir);%(AdditionalIncludeDirectories) + + + $(QtLibDir);%(AdditionalLibraryDirectories) + Qt6Core$(QtLibSuffix).lib;Qt6Gui$(QtLibSuffix).lib;Qt6Widgets$(QtLibSuffix).lib;Qt6Network$(QtLibSuffix).lib;%(AdditionalDependencies) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -DQT_NO_DEBUG -DNDEBUG $(MocDefines) + "-I$(QtIncludeDir)" "-I$(SolutionDir)pcsx2" "-I$(SolutionDir)" -I. + + + + + + + + + + + + + + + + + + + + QtResource + + + QtUi + + + QtMoc + + + QtTs + + + + + + + + + + + + + + + $(BinaryOutputDir)qt.conf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/common/vsprops/QtCompile.targets b/common/vsprops/QtCompile.targets new file mode 100644 index 0000000000..75ac29f304 --- /dev/null +++ b/common/vsprops/QtCompile.targets @@ -0,0 +1,10 @@ + + + + QtResourceClean;QtUiClean;QtMocClean;QtTsClean;$(CleanDependsOn) + + + \ No newline at end of file diff --git a/common/vsprops/QtCompile.xml b/common/vsprops/QtCompile.xml new file mode 100644 index 0000000000..376118df78 --- /dev/null +++ b/common/vsprops/QtCompile.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pcsx2-qt/AboutDialog.cpp b/pcsx2-qt/AboutDialog.cpp new file mode 100644 index 0000000000..5818bf45e3 --- /dev/null +++ b/pcsx2-qt/AboutDialog.cpp @@ -0,0 +1,76 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" + +#include "AboutDialog.h" +#include "QtUtils.h" +#include "svnrev.h" +#include +#include + +AboutDialog::AboutDialog(QWidget* parent) + : QDialog(parent) +{ + m_ui.setupUi(this); + + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + setFixedSize(geometry().width(), geometry().height()); + + m_ui.scmversion->setTextInteractionFlags(Qt::TextSelectableByMouse); + m_ui.scmversion->setText(GIT_REV); + + m_ui.links->setTextInteractionFlags(Qt::TextBrowserInteraction); + m_ui.links->setOpenExternalLinks(true); + m_ui.links->setText(QStringLiteral( + R"(%2 | %4 | %6 | %8)") + .arg(getWebsiteUrl()) + .arg(tr("Website")) + .arg(getSupportForumsUrl()) + .arg(tr("Support Forums")) + .arg(getGitHubRepositoryUrl()) + .arg(tr("GitHub Repository")) + .arg(getLicenseUrl()) + .arg(tr("License"))); + + connect(m_ui.buttonBox, &QDialogButtonBox::rejected, this, &QDialog::close); +} + +AboutDialog::~AboutDialog() = default; + +QString AboutDialog::getWebsiteUrl() +{ + return QStringLiteral("https://pcsx2.net/"); +} + +QString AboutDialog::getSupportForumsUrl() +{ + return QStringLiteral("https://forums.pcsx2.net/"); +} + +QString AboutDialog::getGitHubRepositoryUrl() +{ + return QStringLiteral("https://github.com/PCSX2/pcsx2"); +} + +QString AboutDialog::getLicenseUrl() +{ + return QStringLiteral("https://github.com/PCSX2/pcsx2/blob/master/pcsx2/Docs/License.txt"); +} + +QString AboutDialog::getDiscordServerUrl() +{ + return QStringLiteral("https://discord.com/invite/TCz3t9k"); +} diff --git a/pcsx2-qt/AboutDialog.h b/pcsx2-qt/AboutDialog.h new file mode 100644 index 0000000000..74109bc056 --- /dev/null +++ b/pcsx2-qt/AboutDialog.h @@ -0,0 +1,37 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#pragma once + +#include "ui_AboutDialog.h" +#include + +class AboutDialog final : public QDialog +{ + Q_OBJECT + +public: + explicit AboutDialog(QWidget* parent = nullptr); + ~AboutDialog(); + + static QString getWebsiteUrl(); + static QString getSupportForumsUrl(); + static QString getGitHubRepositoryUrl(); + static QString getLicenseUrl(); + static QString getDiscordServerUrl(); + +private: + Ui::AboutDialog m_ui; +}; diff --git a/pcsx2-qt/AboutDialog.ui b/pcsx2-qt/AboutDialog.ui new file mode 100644 index 0000000000..1212f95874 --- /dev/null +++ b/pcsx2-qt/AboutDialog.ui @@ -0,0 +1,168 @@ + + + AboutDialog + + + + 0 + 0 + 576 + 294 + + + + About PCSX2 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 260 + 260 + + + + + + + :/icons/logo.png + + + Qt::AlignCenter + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + SCM Version + + + Qt::AlignCenter + + + + + + + <html><head/><body><p>PCSX2 is a free and open-source PlayStation 2 (PS2) emulator. Its purpose is to emulate the PS2's hardware, using a combination of MIPS CPU Interpreters, Recompilers and a Virtual Machine which manages hardware states and PS2 system memory. This allows you to play PS2 games on your PC, with many additional features and benefits.</p></body></html> + + + Qt::AlignJustify|Qt::AlignVCenter + + + true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + <html><head/><body><p>PlayStation 2 and PS2 are registered trademarks of Sony Interactive Entertainment. This application is not affiliated in any way with Sony Interactive Entertainment.</p></body></html> + + + Qt::AlignJustify|Qt::AlignVCenter + + + true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + TextLabel + + + Qt::AlignCenter + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + QDialogButtonBox::Close + + + true + + + + + + + + + + + diff --git a/pcsx2-qt/CMakeLists.txt b/pcsx2-qt/CMakeLists.txt new file mode 100644 index 0000000000..0beb5bed99 --- /dev/null +++ b/pcsx2-qt/CMakeLists.txt @@ -0,0 +1,102 @@ +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +set(CMAKE_AUTOUIC ON) + +add_executable(pcsx2-qt) + +if (PACKAGE_MODE) + install(TARGETS pcsx2-qt DESTINATION ${CMAKE_INSTALL_BINDIR}) +else() + install(TARGETS pcsx2-qt DESTINATION ${CMAKE_SOURCE_DIR}/bin) +endif() + +target_sources(pcsx2-qt PRIVATE + AboutDialog.cpp + AboutDialog.h + AboutDialog.ui + DisplayWidget.cpp + DisplayWidget.h + EmuThread.cpp + EmuThread.h + Main.cpp + MainWindow.cpp + MainWindow.h + MainWindow.ui + PrecompiledHeader.cpp + PrecompiledHeader.h + QtHost.cpp + QtHost.h + QtKeyCodes.cpp + QtUtils.cpp + QtUtils.h + SettingWidgetBinder.h + GameList/GameListModel.cpp + GameList/GameListModel.h + GameList/GameListRefreshThread.cpp + GameList/GameListRefreshThread.h + GameList/GameListWidget.cpp + GameList/GameListWidget.h + Settings/AdvancedSystemSettingsWidget.cpp + Settings/AdvancedSystemSettingsWidget.h + Settings/AdvancedSystemSettingsWidget.ui + Settings/BIOSSettingsWidget.cpp + Settings/BIOSSettingsWidget.h + Settings/BIOSSettingsWidget.ui + Settings/ControllerBindingWidget.ui + Settings/ControllerBindingWidget_DualShock2.ui + Settings/ControllerBindingWidgets.cpp + Settings/ControllerBindingWidgets.h + Settings/ControllerGlobalSettingsWidget.cpp + Settings/ControllerGlobalSettingsWidget.h + Settings/ControllerGlobalSettingsWidget.ui + Settings/ControllerSettingsDialog.cpp + Settings/ControllerSettingsDialog.h + Settings/ControllerSettingsDialog.ui + Settings/EmulationSettingsWidget.cpp + Settings/EmulationSettingsWidget.h + Settings/EmulationSettingsWidget.ui + Settings/GameFixSettingsWidget.cpp + Settings/GameFixSettingsWidget.h + Settings/GameFixSettingsWidget.ui + Settings/GameListSettingsWidget.cpp + Settings/GameListSettingsWidget.h + Settings/GameListSettingsWidget.ui + Settings/GraphicsSettingsWidget.cpp + Settings/GraphicsSettingsWidget.h + Settings/GraphicsSettingsWidget.ui + Settings/HotkeySettingsWidget.cpp + Settings/HotkeySettingsWidget.h + Settings/InputBindingDialog.cpp + Settings/InputBindingDialog.h + Settings/InputBindingDialog.ui + Settings/InputBindingWidget.cpp + Settings/InputBindingWidget.h + Settings/InterfaceSettingsWidget.cpp + Settings/InterfaceSettingsWidget.h + Settings/InterfaceSettingsWidget.ui + Settings/SettingsDialog.cpp + Settings/SettingsDialog.h + Settings/SettingsDialog.ui + Settings/SystemSettingsWidget.cpp + Settings/SystemSettingsWidget.h + Settings/SystemSettingsWidget.ui + resources/resources.qrc +) + +target_precompile_headers(pcsx2-qt PRIVATE PrecompiledHeader.h) + +target_include_directories(pcsx2-qt PRIVATE + ${Qt6Gui_PRIVATE_INCLUDE_DIRS} + "${CMAKE_BINARY_DIR}/common/include" + "${CMAKE_SOURCE_DIR}/pcsx2" + "${CMAKE_SOURCE_DIR}/pcsx2-qt" +) + +target_link_libraries(pcsx2-qt PRIVATE + PCSX2_FLAGS + PCSX2 + Qt6::Core + Qt6::Gui + Qt6::Widgets + Qt6::Network +) diff --git a/pcsx2-qt/DisplayWidget.cpp b/pcsx2-qt/DisplayWidget.cpp new file mode 100644 index 0000000000..f96ba25962 --- /dev/null +++ b/pcsx2-qt/DisplayWidget.cpp @@ -0,0 +1,334 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" + +#include "DisplayWidget.h" +#include "EmuThread.h" +#include "QtHost.h" + +#include "pcsx2/GS/GSIntrin.h" // _BitScanForward + +#include +#include +#include +#include +#include +#include +#include + +#if !defined(_WIN32) && !defined(APPLE) +#include +#endif + +DisplayWidget::DisplayWidget(QWidget* parent) + : QWidget(parent) +{ + // 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); + setFocusPolicy(Qt::StrongFocus); + setMouseTracking(true); +} + +DisplayWidget::~DisplayWidget() = default; + +qreal DisplayWidget::devicePixelRatioFromScreen() const +{ + QScreen* screen_for_ratio; +#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) + screen_for_ratio = windowHandle()->screen(); +#else + screen_for_ratio = screen(); +#endif + if (!screen_for_ratio) + screen_for_ratio = QGuiApplication::primaryScreen(); + + return screen_for_ratio ? screen_for_ratio->devicePixelRatio() : static_cast(1); +} + +int DisplayWidget::scaledWindowWidth() const +{ + return static_cast(std::ceil(static_cast(width()) * devicePixelRatioFromScreen())); +} + +int DisplayWidget::scaledWindowHeight() const +{ + return static_cast(std::ceil(static_cast(height()) * devicePixelRatioFromScreen())); +} + +std::optional DisplayWidget::getWindowInfo() const +{ + 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(winId()); +#elif defined(__APPLE__) + wi.type = WindowInfo::Type::MacOS; + wi.window_handle = reinterpret_cast(winId()); +#else + QPlatformNativeInterface* pni = QGuiApplication::platformNativeInterface(); + const QString platform_name = QGuiApplication::platformName(); + if (platform_name == QStringLiteral("xcb")) + { + wi.type = WindowInfo::Type::X11; + wi.display_connection = pni->nativeResourceForWindow("display", windowHandle()); + wi.window_handle = reinterpret_cast(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 + { + qCritical() << "Unknown PNI platform " << platform_name; + return std::nullopt; + } +#endif + + wi.surface_width = scaledWindowWidth(); + wi.surface_height = scaledWindowHeight(); + wi.surface_scale = devicePixelRatioFromScreen(); + return wi; +} + +void DisplayWidget::setRelativeMode(bool enabled) +{ + if (m_relative_mouse_enabled == enabled) + return; + + if (enabled) + { + m_relative_mouse_start_position = QCursor::pos(); + + const QPoint center_pos = mapToGlobal(QPoint(width() / 2, height() / 2)); + QCursor::setPos(center_pos); + m_relative_mouse_last_position = center_pos; + grabMouse(); + } + else + { + QCursor::setPos(m_relative_mouse_start_position); + releaseMouse(); + } + + m_relative_mouse_enabled = enabled; +} + +QPaintEngine* DisplayWidget::paintEngine() const +{ + return nullptr; +} + +bool DisplayWidget::event(QEvent* event) +{ + switch (event->type()) + { + case QEvent::KeyPress: + case QEvent::KeyRelease: + { + const QKeyEvent* key_event = static_cast(event); + if (!key_event->isAutoRepeat()) + { + emit windowKeyEvent(key_event->key(), static_cast(key_event->modifiers()), + event->type() == QEvent::KeyPress); + } + + return true; + } + + case QEvent::MouseMove: + { + const QMouseEvent* mouse_event = static_cast(event); + + if (!m_relative_mouse_enabled) + { + const qreal dpr = devicePixelRatioFromScreen(); + const QPoint mouse_pos = mouse_event->pos(); + const int scaled_x = static_cast(static_cast(mouse_pos.x()) * dpr); + const int scaled_y = static_cast(static_cast(mouse_pos.y()) * dpr); + + windowMouseMoveEvent(scaled_x, scaled_y); + } + else + { + const QPoint center_pos = mapToGlobal(QPoint((width() + 1) / 2, (height() + 1) / 2)); + const QPoint mouse_pos = mapToGlobal(mouse_event->pos()); + + const int dx = mouse_pos.x() - center_pos.x(); + const int dy = mouse_pos.y() - center_pos.y(); + m_relative_mouse_last_position.setX(m_relative_mouse_last_position.x() + dx); + m_relative_mouse_last_position.setY(m_relative_mouse_last_position.y() + dy); + windowMouseMoveEvent(m_relative_mouse_last_position.x(), m_relative_mouse_last_position.y()); + QCursor::setPos(center_pos); + } + + return true; + } + + case QEvent::MouseButtonPress: + case QEvent::MouseButtonDblClick: + case QEvent::MouseButtonRelease: + { + unsigned long button_index; + if (_BitScanForward(&button_index, static_cast(static_cast(event)->button()))) + emit windowMouseButtonEvent(static_cast(button_index + 1u), event->type() != QEvent::MouseButtonRelease); + + if (event->type() == QEvent::MouseButtonDblClick && + static_cast(event)->button() == Qt::LeftButton && + Host::GetBoolSettingValue("UI", "DoubleClickTogglesFullscreen", true)) + { + g_emu_thread->toggleFullscreen(); + } + + return true; + } + + case QEvent::Wheel: + { + const QWheelEvent* wheel_event = static_cast(event); + emit windowMouseWheelEvent(wheel_event->angleDelta()); + return true; + } + + case QEvent::Resize: + { + QWidget::event(event); + + emit windowResizedEvent(scaledWindowWidth(), scaledWindowHeight(), devicePixelRatioFromScreen()); + return true; + } + + case QEvent::Close: + { + emit windowClosedEvent(); + QWidget::event(event); + return true; + } + + case QEvent::WindowStateChange: + { + QWidget::event(event); + + if (static_cast(event)->oldState() & Qt::WindowMinimized) + emit windowRestoredEvent(); + + return true; + } + + case QEvent::FocusIn: + { + QWidget::event(event); + emit windowFocusEvent(); + return true; + } + + case QEvent::ActivationChange: + { + QWidget::event(event); + if (isActiveWindow()) + emit windowFocusEvent(); + + return true; + } + + default: + return QWidget::event(event); + } +} + +DisplayContainer::DisplayContainer() + : QStackedWidget(nullptr) +{ +} + +DisplayContainer::~DisplayContainer() = default; + +bool DisplayContainer::IsNeeded(bool fullscreen, bool render_to_main) +{ +#if defined(_WIN32) || defined(__APPLE__) + return false; +#else + if (fullscreen || render_to_main) + return false; + + // We only need this on Wayland because of client-side decorations... + 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) +{ + const bool res = QStackedWidget::event(event); + if (!m_display_widget) + return res; + + switch (event->type()) + { + case QEvent::Close: + { + emit m_display_widget->windowClosedEvent(); + } + break; + + case QEvent::WindowStateChange: + { + if (static_cast(event)->oldState() & Qt::WindowMinimized) + emit m_display_widget->windowRestoredEvent(); + } + break; + + case QEvent::FocusIn: + { + emit m_display_widget->windowFocusEvent(); + } + break; + + case QEvent::ActivationChange: + { + if (isActiveWindow()) + emit m_display_widget->windowFocusEvent(); + } + break; + + default: + break; + } + + return res; +} diff --git a/pcsx2-qt/DisplayWidget.h b/pcsx2-qt/DisplayWidget.h new file mode 100644 index 0000000000..cffc351f99 --- /dev/null +++ b/pcsx2-qt/DisplayWidget.h @@ -0,0 +1,77 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#pragma once +#include "common/WindowInfo.h" +#include +#include +#include + +class DisplayWidget final : public QWidget +{ + Q_OBJECT + +public: + explicit DisplayWidget(QWidget* parent); + ~DisplayWidget(); + + QPaintEngine* paintEngine() const override; + + int scaledWindowWidth() const; + int scaledWindowHeight() const; + qreal devicePixelRatioFromScreen() const; + + std::optional getWindowInfo() const; + + void setRelativeMode(bool enabled); + +Q_SIGNALS: + void windowFocusEvent(); + void windowResizedEvent(int width, int height, float scale); + void windowRestoredEvent(); + void windowClosedEvent(); + void windowKeyEvent(int key_code, int mods, bool pressed); + void windowMouseMoveEvent(int x, int y); + void windowMouseButtonEvent(int button, bool pressed); + void windowMouseWheelEvent(const QPoint& angle_delta); + +protected: + bool event(QEvent* event) override; + +private: + QPoint m_relative_mouse_start_position{}; + QPoint m_relative_mouse_last_position{}; + bool m_relative_mouse_enabled = false; +}; + +class DisplayContainer final : public QStackedWidget +{ + Q_OBJECT + +public: + DisplayContainer(); + ~DisplayContainer(); + + 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; +}; diff --git a/pcsx2-qt/EmuThread.cpp b/pcsx2-qt/EmuThread.cpp new file mode 100644 index 0000000000..705553189e --- /dev/null +++ b/pcsx2-qt/EmuThread.cpp @@ -0,0 +1,755 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" + +#include + +#include "common/Assertions.h" +#include "common/Console.h" +#include "common/Exceptions.h" +#include "common/SettingsWrapper.h" +#include "common/StringUtil.h" + +#include "pcsx2/CDVD/CDVD.h" +#include "pcsx2/Frontend/InputManager.h" +#include "pcsx2/Frontend/ImGuiManager.h" +#include "pcsx2/GS.h" +#include "pcsx2/HostDisplay.h" +#include "pcsx2/PAD/Host/PAD.h" +#include "pcsx2/PerformanceMetrics.h" +#include "pcsx2/VMManager.h" + +#include "pcsx2/Frontend/OpenGLHostDisplay.h" + +#ifdef _WIN32 +#include "pcsx2/Frontend/D3D11HostDisplay.h" +#endif + +#include "DisplayWidget.h" +#include "EmuThread.h" +#include "MainWindow.h" +#include "QtHost.h" + +EmuThread* g_emu_thread = nullptr; +WindowInfo g_gs_window_info; + +static std::unique_ptr s_host_display; + +EmuThread::EmuThread(QThread* ui_thread) + : QThread() + , m_ui_thread(ui_thread) +{ +} + +EmuThread::~EmuThread() = default; + +bool EmuThread::isOnEmuThread() const +{ + return QThread::currentThread() == this; +} + +void EmuThread::start() +{ + pxAssertRel(!g_emu_thread, "Emu thread does not exist"); + + g_emu_thread = new EmuThread(QThread::currentThread()); + g_emu_thread->QThread::start(); + g_emu_thread->m_started_semaphore.acquire(); + g_emu_thread->moveToThread(g_emu_thread); + g_main_window->connectVMThreadSignals(g_emu_thread); +} + +void EmuThread::stop() +{ + pxAssertRel(g_emu_thread, "Emu thread exists"); + pxAssertRel(!g_emu_thread->isOnEmuThread(), "Not called on the emu thread"); + + QMetaObject::invokeMethod(g_emu_thread, &EmuThread::stopInThread, Qt::QueuedConnection); + while (g_emu_thread->isRunning()) + QApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 1); +} + +void EmuThread::stopInThread() +{ + if (VMManager::HasValidVM()) + destroyVM(); + + m_event_loop->quit(); + m_shutdown_flag.store(true); +} + +void EmuThread::startVM(std::shared_ptr boot_params) +{ + if (!isOnEmuThread()) + { + QMetaObject::invokeMethod(this, "startVM", Qt::QueuedConnection, + Q_ARG(std::shared_ptr, boot_params)); + return; + } + + pxAssertRel(!VMManager::HasValidVM(), "VM is shut down"); + + emit onVMStarting(); + + // create the display, this may take a while... + m_is_fullscreen = boot_params->fullscreen.value_or(QtHost::GetBaseBoolSettingValue("UI", "StartFullscreen", false)); + m_is_rendering_to_main = !m_is_fullscreen && QtHost::GetBaseBoolSettingValue("UI", "RenderToMainWindow", true); + if (!VMManager::Initialize(*boot_params)) + return; + + VMManager::SetState(VMState::Running); + m_event_loop->quit(); +} + +void EmuThread::resetVM() +{ + if (!isOnEmuThread()) + { + QMetaObject::invokeMethod(this, &EmuThread::resetVM, Qt::QueuedConnection); + return; + } + + VMManager::Reset(); +} + +void EmuThread::setVMPaused(bool paused) +{ + if (!isOnEmuThread()) + { + QMetaObject::invokeMethod(this, "setVMPaused", Qt::QueuedConnection, Q_ARG(bool, paused)); + return; + } + + VMManager::SetPaused(paused); +} + +void EmuThread::shutdownVM(bool allow_save_to_state /* = true */, bool blocking /* = false */) +{ + if (!isOnEmuThread()) + { + QMetaObject::invokeMethod(this, "shutdownVM", Qt::QueuedConnection, Q_ARG(bool, allow_save_to_state), + Q_ARG(bool, blocking)); + + if (blocking) + { + // we need to yield here, since the display gets destroyed + while (VMManager::HasValidVM()) + QApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 1); + } + + return; + } + + const VMState state = VMManager::GetState(); + if (state == VMState::Paused) + m_event_loop->quit(); + else if (state != VMState::Running) + return; + + VMManager::SetState(VMState::Stopping); +} + +void EmuThread::loadState(const QString& filename) +{ + if (!isOnEmuThread()) + { + QMetaObject::invokeMethod(this, "loadState", Qt::QueuedConnection, Q_ARG(const QString&, filename)); + return; + } + + if (!VMManager::HasValidVM()) + return; + + VMManager::LoadState(filename.toUtf8().constData()); +} + +void EmuThread::loadStateFromSlot(qint32 slot) +{ + if (!isOnEmuThread()) + { + QMetaObject::invokeMethod(this, "loadStateFromSlot", Qt::QueuedConnection, Q_ARG(qint32, slot)); + return; + } + + if (!VMManager::HasValidVM()) + return; + + VMManager::LoadStateFromSlot(slot); +} + +void EmuThread::saveState(const QString& filename) +{ + if (!isOnEmuThread()) + { + QMetaObject::invokeMethod(this, "saveState", Qt::QueuedConnection, Q_ARG(const QString&, filename)); + return; + } + + if (!VMManager::HasValidVM()) + return; + + if (!VMManager::SaveState(filename.toUtf8().constData())) + { + // this one is usually the result of a user-chosen path, so we can display a message box safely here + Console.Error("Failed to save state"); + } +} + +void EmuThread::saveStateToSlot(qint32 slot) +{ + if (!isOnEmuThread()) + { + QMetaObject::invokeMethod(this, "saveStateToSlot", Qt::QueuedConnection, Q_ARG(qint32, slot)); + return; + } + + if (!VMManager::HasValidVM()) + return; + + VMManager::SaveStateToSlot(slot); +} + +void EmuThread::run() +{ + PerformanceMetrics::SetCPUThreadTimer(Common::ThreadCPUTimer::GetForCallingThread()); + m_event_loop = new QEventLoop(); + m_started_semaphore.release(); + + if (!VMManager::InitializeMemory()) + pxFailRel("Failed to allocate memory map"); + + // we need input sources ready for binding + reloadInputSources(); + createBackgroundControllerPollTimer(); + startBackgroundControllerPollTimer(); + + while (!m_shutdown_flag.load()) + { + if (!VMManager::HasValidVM()) + { + m_event_loop->exec(); + continue; + } + + executeVM(); + } + + stopBackgroundControllerPollTimer(); + destroyBackgroundControllerPollTimer(); + InputManager::CloseSources(); + VMManager::ReleaseMemory(); + PerformanceMetrics::SetCPUThreadTimer(Common::ThreadCPUTimer()); + moveToThread(m_ui_thread); + deleteLater(); +} + +void EmuThread::destroyVM() +{ + VMManager::Shutdown(); +} + +void EmuThread::executeVM() +{ + for (;;) + { + switch (VMManager::GetState()) + { + case VMState::Initializing: + pxFailRel("Shouldn't be in the starting state state"); + continue; + + case VMState::Paused: + m_event_loop->exec(); + continue; + + case VMState::Running: + m_event_loop->processEvents(QEventLoop::AllEvents); + VMManager::Execute(); + continue; + + case VMState::Stopping: + destroyVM(); + m_event_loop->processEvents(QEventLoop::AllEvents); + return; + } + } +} + +void EmuThread::createBackgroundControllerPollTimer() +{ + pxAssert(!m_background_controller_polling_timer); + m_background_controller_polling_timer = new QTimer(this); + m_background_controller_polling_timer->setSingleShot(false); + m_background_controller_polling_timer->setTimerType(Qt::CoarseTimer); + connect(m_background_controller_polling_timer, &QTimer::timeout, this, &EmuThread::doBackgroundControllerPoll); +} + +void EmuThread::destroyBackgroundControllerPollTimer() +{ + delete m_background_controller_polling_timer; + m_background_controller_polling_timer = nullptr; +} + +void EmuThread::startBackgroundControllerPollTimer() +{ + if (m_background_controller_polling_timer->isActive()) + return; + + m_background_controller_polling_timer->start(BACKGROUND_CONTROLLER_POLLING_INTERVAL); +} + +void EmuThread::stopBackgroundControllerPollTimer() +{ + if (!m_background_controller_polling_timer->isActive()) + return; + + m_background_controller_polling_timer->stop(); +} + +void EmuThread::doBackgroundControllerPoll() +{ + InputManager::PollSources(); +} + +void EmuThread::toggleFullscreen() +{ + if (!isOnEmuThread()) + { + QMetaObject::invokeMethod(this, &EmuThread::toggleFullscreen, Qt::QueuedConnection); + return; + } + + setFullscreen(!m_is_fullscreen); +} + +void EmuThread::setFullscreen(bool fullscreen) +{ + if (!isOnEmuThread()) + { + QMetaObject::invokeMethod(this, "setFullscreen", Qt::QueuedConnection, Q_ARG(bool, fullscreen)); + return; + } + + if (!VMManager::HasValidVM() || m_is_fullscreen == fullscreen) + return; + + // This will call back to us on the MTGS thread. + m_is_fullscreen = fullscreen; + GetMTGS().UpdateDisplayWindow(); + GetMTGS().WaitGS(); +} + +void EmuThread::applySettings() +{ + if (!isOnEmuThread()) + { + QMetaObject::invokeMethod(this, &EmuThread::applySettings, Qt::QueuedConnection); + return; + } + + checkForSettingChanges(); + VMManager::ApplySettings(); +} + +void EmuThread::checkForSettingChanges() +{ + if (VMManager::HasValidVM()) + { + const bool render_to_main = QtHost::GetBaseBoolSettingValue("UI", "RenderToMainWindow", true); + if (!m_is_fullscreen && m_is_rendering_to_main != render_to_main) + { + m_is_rendering_to_main = render_to_main; + GetMTGS().UpdateDisplayWindow(); + GetMTGS().WaitGS(); + } + } +} + +void EmuThread::toggleSoftwareRendering() +{ + if (!isOnEmuThread()) + { + QMetaObject::invokeMethod(this, &EmuThread::toggleSoftwareRendering, Qt::QueuedConnection); + return; + } + + if (!VMManager::HasValidVM()) + return; + + GetMTGS().ToggleSoftwareRendering(); +} + +void EmuThread::switchRenderer(GSRendererType renderer) +{ + if (!isOnEmuThread()) + { + QMetaObject::invokeMethod(this, "switchRenderer", Qt::QueuedConnection, Q_ARG(GSRendererType, renderer)); + return; + } + + if (!VMManager::HasValidVM()) + return; + + GetMTGS().SwitchRenderer(renderer); +} + +void EmuThread::changeDisc(const QString& path) +{ + if (!isOnEmuThread()) + { + QMetaObject::invokeMethod(this, "changeDisc", Qt::QueuedConnection, Q_ARG(const QString&, path)); + return; + } + + if (!VMManager::HasValidVM()) + return; + + VMManager::ChangeDisc(path.toStdString()); +} + +void EmuThread::reloadPatches() +{ + if (!isOnEmuThread()) + { + QMetaObject::invokeMethod(this, &EmuThread::reloadPatches, Qt::QueuedConnection); + return; + } + + if (!VMManager::HasValidVM()) + return; + + VMManager::ReloadPatches(true); +} + +void EmuThread::reloadInputSources() +{ + if (!isOnEmuThread()) + { + QMetaObject::invokeMethod(this, &EmuThread::reloadInputSources, Qt::QueuedConnection); + return; + } + + auto lock = Host::GetSettingsLock(); + SettingsInterface* si = Host::GetSettingsInterface(); + InputManager::ReloadSources(*si); + + // skip loading bindings if we're not running, since it'll get done on startup anyway + if (VMManager::HasValidVM()) + InputManager::ReloadBindings(*si); +} + +void EmuThread::reloadInputBindings() +{ + if (!isOnEmuThread()) + { + QMetaObject::invokeMethod(this, &EmuThread::reloadInputBindings, Qt::QueuedConnection); + return; + } + + // skip loading bindings if we're not running, since it'll get done on startup anyway + if (!VMManager::HasValidVM()) + return; + + auto lock = Host::GetSettingsLock(); + SettingsInterface* si = Host::GetSettingsInterface(); + InputManager::ReloadBindings(*si); +} + +void EmuThread::requestDisplaySize(float scale) +{ + if (!isOnEmuThread()) + { + QMetaObject::invokeMethod(this, "requestDisplaySize", Qt::QueuedConnection, Q_ARG(float, scale)); + return; + } + + if (!VMManager::HasValidVM()) + return; + + VMManager::RequestDisplaySize(scale); +} + +void EmuThread::enumerateInputDevices() +{ + if (!isOnEmuThread()) + { + QMetaObject::invokeMethod(this, &EmuThread::enumerateInputDevices, Qt::QueuedConnection); + return; + } + + const std::vector> devs(InputManager::EnumerateDevices()); + QList> qdevs; + qdevs.reserve(devs.size()); + for (const std::pair& dev : devs) + qdevs.emplace_back(QString::fromStdString(dev.first), QString::fromStdString(dev.second)); + + onInputDevicesEnumerated(qdevs); +} + +void EmuThread::enumerateVibrationMotors() +{ + if (!isOnEmuThread()) + { + QMetaObject::invokeMethod(this, &EmuThread::enumerateVibrationMotors, Qt::QueuedConnection); + return; + } + + const std::vector motors(InputManager::EnumerateMotors()); + QList qmotors; + qmotors.reserve(motors.size()); + for (InputBindingKey key : motors) + qmotors.push_back(key); + + onVibrationMotorsEnumerated(qmotors); +} + +void EmuThread::connectDisplaySignals(DisplayWidget* widget) +{ + widget->disconnect(this); + + connect(widget, &DisplayWidget::windowFocusEvent, this, &EmuThread::onDisplayWindowFocused); + connect(widget, &DisplayWidget::windowResizedEvent, this, &EmuThread::onDisplayWindowResized); + // connect(widget, &DisplayWidget::windowRestoredEvent, this, &EmuThread::redrawDisplayWindow); + connect(widget, &DisplayWidget::windowClosedEvent, []() { g_emu_thread->shutdownVM(true, true); }); + connect(widget, &DisplayWidget::windowKeyEvent, this, &EmuThread::onDisplayWindowKeyEvent); + connect(widget, &DisplayWidget::windowMouseMoveEvent, this, &EmuThread::onDisplayWindowMouseMoveEvent); + connect(widget, &DisplayWidget::windowMouseButtonEvent, this, &EmuThread::onDisplayWindowMouseButtonEvent); + connect(widget, &DisplayWidget::windowMouseWheelEvent, this, &EmuThread::onDisplayWindowMouseWheelEvent); +} + +void EmuThread::onDisplayWindowMouseMoveEvent(int x, int y) {} + +void EmuThread::onDisplayWindowMouseButtonEvent(int button, bool pressed) +{ + InputManager::InvokeEvents(InputManager::MakeHostMouseButtonKey(button), pressed ? 1.0f : 0.0f); +} + +void EmuThread::onDisplayWindowMouseWheelEvent(const QPoint& delta_angle) {} + +void EmuThread::onDisplayWindowKeyEvent(int key, int mods, bool pressed) +{ + InputManager::InvokeEvents(InputManager::MakeHostKeyboardKey(key), pressed ? 1.0f : 0.0f); +} + +void EmuThread::onDisplayWindowResized(int width, int height, float scale) +{ + if (!VMManager::HasValidVM()) + return; + + GetMTGS().ResizeDisplayWindow(width, height, scale); +} + +void EmuThread::onDisplayWindowFocused() {} + +void EmuThread::updateDisplay() +{ + pxAssertRel(!isOnEmuThread(), "Not on emu thread"); + + // finished with the display for now + HostDisplay* display = Host::GetHostDisplay(); + display->DoneRenderContextCurrent(); + + // but we should get it back after this call + DisplayWidget* widget = onUpdateDisplayRequested(m_is_fullscreen, !m_is_fullscreen && m_is_rendering_to_main); + if (!widget || !display->MakeRenderContextCurrent()) + { + pxFailRel("Failed to recreate context after updating"); + return; + } + + // this is always a new widget, so reconnect it + connectDisplaySignals(widget); +} + +HostDisplay* EmuThread::acquireHostDisplay(HostDisplay::RenderAPI api) +{ + s_host_display = HostDisplay::CreateDisplayForAPI(api); + if (!s_host_display) + return nullptr; + + DisplayWidget* widget = emit onCreateDisplayRequested(m_is_fullscreen, m_is_rendering_to_main); + if (!widget) + { + s_host_display.reset(); + return nullptr; + } + + connectDisplaySignals(widget); + + if (!s_host_display->MakeRenderContextCurrent()) + { + Console.Error("Failed to make render context current"); + releaseHostDisplay(); + return nullptr; + } + + if (!s_host_display->InitializeRenderDevice(StringUtil::wxStringToUTF8String(EmuFolders::Cache.ToString()), false) || + !ImGuiManager::Initialize()) + { + Console.Error("Failed to initialize device/imgui"); + releaseHostDisplay(); + return nullptr; + } + + g_gs_window_info = s_host_display->GetWindowInfo(); + + return s_host_display.get(); +} + +void EmuThread::releaseHostDisplay() +{ + ImGuiManager::Shutdown(); + + if (s_host_display) + { + s_host_display->DestroyRenderSurface(); + s_host_display->DestroyRenderDevice(); + } + + g_gs_window_info = WindowInfo(); + + emit onDestroyDisplayRequested(); + + s_host_display.reset(); +} + +HostDisplay* Host::GetHostDisplay() +{ + return s_host_display.get(); +} + +HostDisplay* Host::AcquireHostDisplay(HostDisplay::RenderAPI api) +{ + return g_emu_thread->acquireHostDisplay(api); +} + +void Host::ReleaseHostDisplay() +{ + g_emu_thread->releaseHostDisplay(); +} + +bool Host::BeginPresentFrame(bool frame_skip) +{ + return s_host_display->BeginPresent(frame_skip); +} + +void Host::EndPresentFrame() +{ + ImGuiManager::RenderOSD(); + s_host_display->EndPresent(); + ImGuiManager::NewFrame(); +} + +void Host::ResizeHostDisplay(u32 new_window_width, u32 new_window_height, float new_window_scale) +{ + s_host_display->ResizeRenderWindow(new_window_width, new_window_height, new_window_scale); + ImGuiManager::WindowResized(); +} + +void Host::RequestResizeHostDisplay(s32 width, s32 height) +{ + g_emu_thread->onResizeDisplayRequested(width, height); +} + +void Host::UpdateHostDisplay() +{ + g_emu_thread->updateDisplay(); + ImGuiManager::WindowResized(); +} + +void Host::OnVMStarting() +{ + g_emu_thread->stopBackgroundControllerPollTimer(); + emit g_emu_thread->onVMStarting(); +} + +void Host::OnVMStarted() +{ + emit g_emu_thread->onVMStarted(); +} + +void Host::OnVMDestroyed() +{ + emit g_emu_thread->onVMStopped(); + g_emu_thread->startBackgroundControllerPollTimer(); +} + +void Host::OnVMPaused() +{ + g_emu_thread->startBackgroundControllerPollTimer(); + emit g_emu_thread->onVMPaused(); +} + +void Host::OnVMResumed() +{ + // exit the event loop when we eventually return + g_emu_thread->getEventLoop()->quit(); + g_emu_thread->stopBackgroundControllerPollTimer(); + emit g_emu_thread->onVMResumed(); +} + +void Host::OnGameChanged(const std::string& disc_path, const std::string& game_serial, const std::string& game_name, + u32 game_crc) +{ + emit g_emu_thread->onGameChanged(QString::fromStdString(disc_path), QString::fromStdString(game_serial), + QString::fromStdString(game_name), game_crc); +} + +void Host::PumpMessagesOnCPUThread() +{ + g_emu_thread->getEventLoop()->processEvents(QEventLoop::AllEvents); +} + +ScopedVMPause::ScopedVMPause(bool was_paused) + : m_was_paused(was_paused) +{ + if (!m_was_paused) + g_emu_thread->setVMPaused(true); +} + +ScopedVMPause::~ScopedVMPause() +{ + if (m_was_paused) + g_emu_thread->setVMPaused(false); +} + +alignas(16) static SysMtgsThread s_mtgs_thread; + +SysMtgsThread& GetMTGS() +{ + return s_mtgs_thread; +} + +// ------------------------------------------------------------------------ +// Hotkeys +// ------------------------------------------------------------------------ + +BEGIN_HOTKEY_LIST(g_host_hotkeys) +DEFINE_HOTKEY("Screenshot", "General", "Save Screenshot", [](bool pressed) { + if (!pressed) + { + // TODO + } +}) +DEFINE_HOTKEY("TogglePause", "System", "Toggle Pause", [](bool pressed) { + if (!pressed) + g_emu_thread->setVMPaused(VMManager::GetState() != VMState::Paused); +}) +DEFINE_HOTKEY("ToggleFullscreen", "General", "Toggle Fullscreen", [](bool pressed) { + if (!pressed) + g_emu_thread->toggleFullscreen(); +}) +END_HOTKEY_LIST() diff --git a/pcsx2-qt/EmuThread.h b/pcsx2-qt/EmuThread.h new file mode 100644 index 0000000000..04da15a10a --- /dev/null +++ b/pcsx2-qt/EmuThread.h @@ -0,0 +1,144 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#pragma once +#include "pcsx2/Host.h" +#include "pcsx2/HostDisplay.h" +#include "pcsx2/Frontend/InputManager.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class DisplayWidget; +struct VMBootParameters; + +class EmuThread : public QThread +{ + Q_OBJECT + +public: + explicit EmuThread(QThread* ui_thread); + ~EmuThread(); + + static void start(); + static void stop(); + + __fi QEventLoop* getEventLoop() const { return m_event_loop; } + + bool isOnEmuThread() const; + + /// Called back from the GS thread when the display state changes (e.g. fullscreen, render to main). + HostDisplay* acquireHostDisplay(HostDisplay::RenderAPI api); + void releaseHostDisplay(); + void updateDisplay(); + + void startBackgroundControllerPollTimer(); + void stopBackgroundControllerPollTimer(); + +public Q_SLOTS: + void startVM(std::shared_ptr boot_params); + void resetVM(); + void setVMPaused(bool paused); + void shutdownVM(bool allow_save_to_state = true, bool blocking = false); + void loadState(const QString& filename); + void loadStateFromSlot(qint32 slot); + void saveState(const QString& filename); + void saveStateToSlot(qint32 slot); + void toggleFullscreen(); + void setFullscreen(bool fullscreen); + void applySettings(); + void toggleSoftwareRendering(); + void switchRenderer(GSRendererType renderer); + void changeDisc(const QString& path); + void reloadPatches(); + void reloadInputSources(); + void reloadInputBindings(); + void requestDisplaySize(float scale); + void enumerateInputDevices(); + void enumerateVibrationMotors(); + +Q_SIGNALS: + DisplayWidget* onCreateDisplayRequested(bool fullscreen, bool render_to_main); + DisplayWidget* onUpdateDisplayRequested(bool fullscreen, bool render_to_main); + void onResizeDisplayRequested(qint32 width, qint32 height); + void onDestroyDisplayRequested(); + void onVMStarting(); + void onVMStarted(); + void onVMPaused(); + void onVMResumed(); + void onVMStopped(); + void onGameChanged(const QString& path, const QString& serial, const QString& name, quint32 crc); + void onInputDevicesEnumerated(const QList>& devices); + void onInputDeviceConnected(const QString& identifier, const QString& device_name); + void onInputDeviceDisconnected(const QString& identifier); + void onVibrationMotorsEnumerated(const QList& motors); + +protected: + void run(); + +private: + static constexpr u32 BACKGROUND_CONTROLLER_POLLING_INTERVAL = + 100; /// Interval at which the controllers are polled when the system is not active. + + void connectDisplaySignals(DisplayWidget* widget); + void destroyVM(); + void executeVM(); + void checkForSettingChanges(); + + void createBackgroundControllerPollTimer(); + void destroyBackgroundControllerPollTimer(); + +private Q_SLOTS: + void stopInThread(); + void doBackgroundControllerPoll(); + void onDisplayWindowMouseMoveEvent(int x, int y); + void onDisplayWindowMouseButtonEvent(int button, bool pressed); + void onDisplayWindowMouseWheelEvent(const QPoint& delta_angle); + void onDisplayWindowResized(int width, int height, float scale); + void onDisplayWindowFocused(); + void onDisplayWindowKeyEvent(int key, int mods, bool pressed); + +private: + QThread* m_ui_thread; + QSemaphore m_started_semaphore; + QEventLoop* m_event_loop = nullptr; + QTimer* m_background_controller_polling_timer = nullptr; + + std::atomic_bool m_shutdown_flag{false}; + + bool m_is_rendering_to_main = false; + bool m_is_fullscreen = false; +}; + +///

+/// Helper class to pause/unpause the emulation thread. +/// +class ScopedVMPause +{ +public: + ScopedVMPause(bool was_paused); + ~ScopedVMPause(); + +private: + bool m_was_paused; +}; + +extern EmuThread* g_emu_thread; diff --git a/pcsx2-qt/GameList/GameListModel.cpp b/pcsx2-qt/GameList/GameListModel.cpp new file mode 100644 index 0000000000..663cec25f0 --- /dev/null +++ b/pcsx2-qt/GameList/GameListModel.cpp @@ -0,0 +1,490 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" + +#include "GameListModel.h" +#include "common/FileSystem.h" +#include "common/StringUtil.h" +#include +#include +#include +#include +#include + +static constexpr std::array s_column_names = { + {"Type", "Code", "Title", "File Title", "CRC", "Size", "Region", "Compatibility", "Cover"}}; + +static constexpr int COVER_ART_WIDTH = 350; +static constexpr int COVER_ART_HEIGHT = 512; +static constexpr int COVER_ART_SPACING = 32; + +static int DPRScale(int size, float dpr) +{ + return static_cast(static_cast(size) * dpr); +} + +static int DPRUnscale(int size, float dpr) +{ + return static_cast(static_cast(size) / dpr); +} + +static void resizeAndPadPixmap(QPixmap* pm, int expected_width, int expected_height, float dpr) +{ + const int dpr_expected_width = DPRScale(expected_width, dpr); + const int dpr_expected_height = DPRScale(expected_height, dpr); + if (pm->width() == dpr_expected_width && pm->height() == dpr_expected_height) + return; + + *pm = pm->scaled(dpr_expected_width, dpr_expected_height, Qt::KeepAspectRatio, Qt::SmoothTransformation); + if (pm->width() == dpr_expected_width && pm->height() == dpr_expected_height) + return; + + // QPainter works in unscaled coordinates. + int xoffs = 0; + int yoffs = 0; + if (pm->width() < dpr_expected_width) + xoffs = DPRUnscale((dpr_expected_width - pm->width()) / 2, dpr); + if (pm->height() < dpr_expected_height) + yoffs = DPRUnscale((dpr_expected_height - pm->height()) / 2, dpr); + + QPixmap padded_image(dpr_expected_width, dpr_expected_height); + padded_image.setDevicePixelRatio(dpr); + padded_image.fill(Qt::transparent); + QPainter painter; + if (painter.begin(&padded_image)) + { + painter.setCompositionMode(QPainter::CompositionMode_Source); + painter.drawPixmap(xoffs, yoffs, *pm); + painter.setCompositionMode(QPainter::CompositionMode_Destination); + painter.fillRect(padded_image.rect(), QColor(0, 0, 0, 0)); + painter.end(); + } + + *pm = padded_image; +} + +static QPixmap createPlaceholderImage(const QPixmap& placeholder_pixmap, int width, int height, float scale, + const std::string& title) +{ + const float dpr = qApp->devicePixelRatio(); + QPixmap pm(placeholder_pixmap.copy()); + pm.setDevicePixelRatio(dpr); + if (pm.isNull()) + return QPixmap(); + + resizeAndPadPixmap(&pm, width, height, dpr); + QPainter painter; + if (painter.begin(&pm)) + { + QFont font; + font.setPointSize(std::max(static_cast(32.0f * scale), 1)); + painter.setFont(font); + painter.setPen(Qt::white); + + const QRect text_rc(0, 0, static_cast(static_cast(width)), + static_cast(static_cast(height))); + painter.drawText(text_rc, Qt::AlignCenter | Qt::TextWordWrap, QString::fromStdString(title)); + painter.end(); + } + + return pm; +} + +std::optional GameListModel::getColumnIdForName(std::string_view name) +{ + for (int column = 0; column < Column_Count; column++) + { + if (name == s_column_names[column]) + return static_cast(column); + } + + return std::nullopt; +} + +const char* GameListModel::getColumnName(Column col) +{ + return s_column_names[static_cast(col)]; +} + +GameListModel::GameListModel(QObject* parent /* = nullptr */) + : QAbstractTableModel(parent) +{ + loadCommonImages(); + setColumnDisplayNames(); +} +GameListModel::~GameListModel() = default; + +void GameListModel::setCoverScale(float scale) +{ + if (m_cover_scale == scale) + return; + + m_cover_pixmap_cache.clear(); + m_cover_scale = scale; +} + +void GameListModel::refreshCovers() +{ + m_cover_pixmap_cache.clear(); + refresh(); +} + +int GameListModel::getCoverArtWidth() const +{ + return std::max(static_cast(static_cast(COVER_ART_WIDTH) * m_cover_scale), 1); +} + +int GameListModel::getCoverArtHeight() const +{ + return std::max(static_cast(static_cast(COVER_ART_HEIGHT) * m_cover_scale), 1); +} + +int GameListModel::getCoverArtSpacing() const +{ + return std::max(static_cast(static_cast(COVER_ART_SPACING) * m_cover_scale), 1); +} + +int GameListModel::rowCount(const QModelIndex& parent) const +{ + if (parent.isValid()) + return 0; + + return static_cast(GameList::GetEntryCount()); +} + +int GameListModel::columnCount(const QModelIndex& parent) const +{ + if (parent.isValid()) + return 0; + + return Column_Count; +} + +QVariant GameListModel::data(const QModelIndex& index, int role) const +{ + if (!index.isValid()) + return {}; + + const int row = index.row(); + if (row < 0 || row >= static_cast(GameList::GetEntryCount())) + return {}; + + const auto lock = GameList::GetLock(); + const GameList::Entry* ge = GameList::GetEntryByIndex(row); + if (!ge) + return {}; + + switch (role) + { + case Qt::DisplayRole: + { + switch (index.column()) + { + case Column_Serial: + return QString::fromStdString(ge->serial); + + case Column_Title: + return QString::fromStdString(ge->title); + + case Column_FileTitle: + { + const std::string_view file_title(FileSystem::GetFileTitleFromPath(ge->path)); + return QString::fromUtf8(file_title.data(), static_cast(file_title.length())); + } + + case Column_CRC: + return QStringLiteral("%1").arg(ge->crc, 8, 16, QChar('0')); + + case Column_Size: + return QString("%1 MB").arg(static_cast(ge->total_size) / 1048576.0, 0, 'f', 2); + + case Column_Cover: + { + if (m_show_titles_for_covers) + return QString::fromStdString(ge->title); + else + return {}; + } + + default: + return {}; + } + } + + case Qt::InitialSortOrderRole: + { + switch (index.column()) + { + case Column_Type: + return static_cast(ge->type); + + case Column_Serial: + return QString::fromStdString(ge->serial); + + case Column_Title: + case Column_Cover: + return QString::fromStdString(ge->title); + + case Column_FileTitle: + { + const std::string_view file_title(FileSystem::GetFileTitleFromPath(ge->path)); + return QString::fromUtf8(file_title.data(), static_cast(file_title.length())); + } + + case Column_CRC: + return static_cast(ge->crc); + + case Column_Region: + return static_cast(ge->region); + + case Column_Compatibility: + return static_cast(ge->compatibility_rating); + + case Column_Size: + return static_cast(ge->total_size); + + default: + return {}; + } + } + + case Qt::DecorationRole: + { + switch (index.column()) + { + case Column_Type: + { + switch (ge->type) + { + case GameList::EntryType::PS1Disc: + case GameList::EntryType::PS2Disc: + // return ((ge->settings.GetUserSettingsCount() > 0) ? m_type_disc_with_settings_pixmap : // m_type_disc_pixmap); + return m_type_disc_pixmap; + case GameList::EntryType::Playlist: + return m_type_playlist_pixmap; + case GameList::EntryType::ELF: + default: + return m_type_exe_pixmap; + } + } + + case Column_Region: + { + switch (ge->region) + { + case GameList::Region::NTSC_J: + return m_region_jp_pixmap; + case GameList::Region::NTSC_UC: + return m_region_us_pixmap; + case GameList::Region::PAL: + return m_region_eu_pixmap; + case GameList::Region::Other: + default: + return m_region_other_pixmap; + } + } + + case Column_Compatibility: + { + return m_compatibiliy_pixmaps[static_cast( + (static_cast(ge->compatibility_rating) >= GameList::CompatibilityRatingCount) ? + GameList::CompatibilityRating::Unknown : + ge->compatibility_rating)]; + } + + case Column_Cover: + { + auto it = m_cover_pixmap_cache.find(ge->path); + if (it != m_cover_pixmap_cache.end()) + return it->second; + + QPixmap image; + std::string path = GameList::GetCoverImagePathForEntry(ge); + if (!path.empty()) + { + const float dpr = qApp->devicePixelRatio(); + image = QPixmap(QString::fromStdString(path)); + if (!image.isNull()) + { + image.setDevicePixelRatio(dpr); + resizeAndPadPixmap(&image, getCoverArtWidth(), getCoverArtHeight(), dpr); + } + } + + if (image.isNull()) + { + image = createPlaceholderImage(m_placeholder_pixmap, getCoverArtWidth(), getCoverArtHeight(), m_cover_scale, + ge->title); + } + + m_cover_pixmap_cache.emplace(ge->path, image); + return image; + } + break; + + default: + return {}; + } + + default: + return {}; + } + } +} + +QVariant GameListModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation != Qt::Horizontal || role != Qt::DisplayRole || section < 0 || section >= Column_Count) + return {}; + + return m_column_display_names[section]; +} + +void GameListModel::refresh() +{ + beginResetModel(); + endResetModel(); +} + +bool GameListModel::titlesLessThan(int left_row, int right_row) const +{ + if (left_row < 0 || left_row >= static_cast(GameList::GetEntryCount()) || right_row < 0 || + right_row >= static_cast(GameList::GetEntryCount())) + { + return false; + } + + const GameList::Entry* left = GameList::GetEntryByIndex(left_row); + const GameList::Entry* right = GameList::GetEntryByIndex(right_row); + return (StringUtil::Strcasecmp(left->title.c_str(), right->title.c_str()) < 0); +} + +bool GameListModel::lessThan(const QModelIndex& left_index, const QModelIndex& right_index, int column) const +{ + if (!left_index.isValid() || !right_index.isValid()) + return false; + + const int left_row = left_index.row(); + const int right_row = right_index.row(); + if (left_row < 0 || left_row >= static_cast(GameList::GetEntryCount()) || right_row < 0 || + right_row >= static_cast(GameList::GetEntryCount())) + { + return false; + } + + const auto lock = GameList::GetLock(); + const GameList::Entry* left = GameList::GetEntryByIndex(left_row); + const GameList::Entry* right = GameList::GetEntryByIndex(right_row); + if (!left || !right) + return false; + + switch (column) + { + case Column_Type: + { + if (left->type == right->type) + return titlesLessThan(left_row, right_row); + + return static_cast(left->type) < static_cast(right->type); + } + + case Column_Serial: + { + if (left->serial == right->serial) + return titlesLessThan(left_row, right_row); + return (StringUtil::Strcasecmp(left->serial.c_str(), right->serial.c_str()) < 0); + } + + case Column_Title: + { + return titlesLessThan(left_row, right_row); + } + + case Column_FileTitle: + { + const std::string_view file_title_left(FileSystem::GetFileTitleFromPath(left->path)); + const std::string_view file_title_right(FileSystem::GetFileTitleFromPath(right->path)); + if (file_title_left == file_title_right) + return titlesLessThan(left_row, right_row); + + const std::size_t smallest = std::min(file_title_left.size(), file_title_right.size()); + return (StringUtil::Strncasecmp(file_title_left.data(), file_title_right.data(), smallest) < 0); + } + + case Column_Region: + { + if (left->region == right->region) + return titlesLessThan(left_row, right_row); + return (static_cast(left->region) < static_cast(right->region)); + } + + case Column_Compatibility: + { + if (left->compatibility_rating == right->compatibility_rating) + return titlesLessThan(left_row, right_row); + + return (static_cast(left->compatibility_rating) < static_cast(right->compatibility_rating)); + } + + case Column_Size: + { + if (left->total_size == right->total_size) + return titlesLessThan(left_row, right_row); + + return (left->total_size < right->total_size); + } + + case Column_CRC: + { + if (left->crc == right->crc) + return titlesLessThan(left_row, right_row); + + return (left->crc < right->crc); + } + + default: + return false; + } +} + +void GameListModel::loadCommonImages() +{ + m_type_disc_pixmap = QIcon(QStringLiteral(":/icons/media-optical-24.png")).pixmap(QSize(24, 24)); + m_type_disc_with_settings_pixmap = QIcon(QStringLiteral(":/icons/media-optical-gear-24.png")).pixmap(QSize(24, 24)); + m_type_exe_pixmap = QIcon(QStringLiteral(":/icons/applications-system-24.png")).pixmap(QSize(24, 24)); + m_type_playlist_pixmap = QIcon(QStringLiteral(":/icons/address-book-new-22.png")).pixmap(QSize(22, 22)); + m_region_eu_pixmap = QIcon(QStringLiteral(":/icons/flag-eu.png")).pixmap(QSize(42, 30)); + m_region_jp_pixmap = QIcon(QStringLiteral(":/icons/flag-jp.png")).pixmap(QSize(42, 30)); + m_region_us_pixmap = QIcon(QStringLiteral(":/icons/flag-us.png")).pixmap(QSize(42, 30)); + m_region_other_pixmap = QIcon(QStringLiteral(":/icons/flag-other.png")).pixmap(QSize(42, 30)); + + for (u32 i = 1; i < GameList::CompatibilityRatingCount; i++) + m_compatibiliy_pixmaps[i].load(QStringLiteral(":/icons/star-%1.png").arg(i - 1)); + + m_placeholder_pixmap.load(QString::fromStdString(Path::CombineStdString(EmuFolders::Resources, "cover-placeholder.png"))); +} + +void GameListModel::setColumnDisplayNames() +{ + m_column_display_names[Column_Type] = tr("Type"); + m_column_display_names[Column_Serial] = tr("Code"); + m_column_display_names[Column_Title] = tr("Title"); + m_column_display_names[Column_FileTitle] = tr("File Title"); + m_column_display_names[Column_CRC] = tr("CRC"); + m_column_display_names[Column_Size] = tr("Size"); + m_column_display_names[Column_Region] = tr("Region"); + m_column_display_names[Column_Compatibility] = tr("Compatibility"); +} diff --git a/pcsx2-qt/GameList/GameListModel.h b/pcsx2-qt/GameList/GameListModel.h new file mode 100644 index 0000000000..09a3396b76 --- /dev/null +++ b/pcsx2-qt/GameList/GameListModel.h @@ -0,0 +1,97 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#pragma once +#include "pcsx2/Frontend/GameList.h" +#include +#include +#include +#include +#include +#include + +class GameListModel final : public QAbstractTableModel +{ + Q_OBJECT + +public: + enum Column : int + { + Column_Type, + Column_Serial, + Column_Title, + Column_FileTitle, + Column_CRC, + Column_Size, + Column_Region, + Column_Compatibility, + Column_Cover, + + Column_Count + }; + + static std::optional getColumnIdForName(std::string_view name); + static const char* getColumnName(Column col); + + GameListModel(QObject* parent = nullptr); + ~GameListModel(); + + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + int columnCount(const QModelIndex& parent = QModelIndex()) const override; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + + __fi const QString& getColumnDisplayName(int column) { return m_column_display_names[column]; } + + void refresh(); + + bool titlesLessThan(int left_row, int right_row) const; + + bool lessThan(const QModelIndex& left_index, const QModelIndex& right_index, int column) const; + + bool getShowCoverTitles() const { return m_show_titles_for_covers; } + void setShowCoverTitles(bool enabled) { m_show_titles_for_covers = enabled; } + + float getCoverScale() const { return m_cover_scale; } + void setCoverScale(float scale); + int getCoverArtWidth() const; + int getCoverArtHeight() const; + int getCoverArtSpacing() const; + void refreshCovers(); + +private: + void loadCommonImages(); + void setColumnDisplayNames(); + + float m_cover_scale = 1.0f; + bool m_show_titles_for_covers = false; + + std::array m_column_display_names; + + QPixmap m_type_disc_pixmap; + QPixmap m_type_disc_with_settings_pixmap; + QPixmap m_type_exe_pixmap; + QPixmap m_type_playlist_pixmap; + + QPixmap m_region_jp_pixmap; + QPixmap m_region_eu_pixmap; + QPixmap m_region_us_pixmap; + QPixmap m_region_other_pixmap; + + QPixmap m_placeholder_pixmap; + + std::array(GameList::CompatibilityRatingCount)> m_compatibiliy_pixmaps; + mutable std::unordered_map m_cover_pixmap_cache; +}; \ No newline at end of file diff --git a/pcsx2-qt/GameList/GameListRefreshThread.cpp b/pcsx2-qt/GameList/GameListRefreshThread.cpp new file mode 100644 index 0000000000..255dc7ef40 --- /dev/null +++ b/pcsx2-qt/GameList/GameListRefreshThread.cpp @@ -0,0 +1,130 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" + +#include "common/ProgressCallback.h" +#include "common/Timer.h" +#include "pcsx2/Frontend/GameList.h" + +#include "GameListRefreshThread.h" + +#include + +// Limit UI update times to 4 per second, so we don't spend longer redrawing the UI than scanning +static constexpr float UI_UPDATE_INTERVAL = 0.25f; + +AsyncRefreshProgressCallback::AsyncRefreshProgressCallback(GameListRefreshThread* parent) + : m_parent(parent) +{ +} + +void AsyncRefreshProgressCallback::Cancel() +{ + // Not atomic, but we don't need to cancel immediately. + m_cancelled = true; +} + +void AsyncRefreshProgressCallback::SetStatusText(const char* text) +{ + QString new_text(QString::fromUtf8(text)); + if (new_text == m_status_text) + return; + + m_status_text = new_text; + fireUpdate(); +} + +void AsyncRefreshProgressCallback::SetProgressRange(u32 range) +{ + BaseProgressCallback::SetProgressRange(range); + if (static_cast(m_progress_range) == m_last_range) + return; + + m_last_range = static_cast(m_progress_range); + fireUpdate(); +} + +void AsyncRefreshProgressCallback::SetProgressValue(u32 value) +{ + BaseProgressCallback::SetProgressValue(value); + if (static_cast(m_progress_value) == m_last_value) + return; + + m_last_value = static_cast(m_progress_value); + fireUpdate(); +} + +void AsyncRefreshProgressCallback::SetTitle(const char* title) {} + +void AsyncRefreshProgressCallback::DisplayError(const char* message) +{ + QMessageBox::critical(nullptr, QStringLiteral("Error"), QString::fromUtf8(message)); +} + +void AsyncRefreshProgressCallback::DisplayWarning(const char* message) +{ + QMessageBox::warning(nullptr, QStringLiteral("Warning"), QString::fromUtf8(message)); +} + +void AsyncRefreshProgressCallback::DisplayInformation(const char* message) +{ + QMessageBox::information(nullptr, QStringLiteral("Information"), QString::fromUtf8(message)); +} + +void AsyncRefreshProgressCallback::DisplayDebugMessage(const char* message) +{ + qDebug() << message; +} + +void AsyncRefreshProgressCallback::ModalError(const char* message) +{ + QMessageBox::critical(nullptr, QStringLiteral("Error"), QString::fromUtf8(message)); +} + +bool AsyncRefreshProgressCallback::ModalConfirmation(const char* message) +{ + return QMessageBox::question(nullptr, QStringLiteral("Question"), QString::fromUtf8(message)) == QMessageBox::Yes; +} + +void AsyncRefreshProgressCallback::ModalInformation(const char* message) +{ + QMessageBox::information(nullptr, QStringLiteral("Information"), QString::fromUtf8(message)); +} + +void AsyncRefreshProgressCallback::fireUpdate() +{ + m_parent->refreshProgress(m_status_text, m_last_value, m_last_range); +} + +GameListRefreshThread::GameListRefreshThread(bool invalidate_cache) + : QThread() + , m_progress(this) + , m_invalidate_cache(invalidate_cache) +{ +} + +GameListRefreshThread::~GameListRefreshThread() = default; + +void GameListRefreshThread::cancel() +{ + m_progress.Cancel(); +} + +void GameListRefreshThread::run() +{ + GameList::Refresh(m_invalidate_cache, &m_progress); + emit refreshComplete(); +} diff --git a/pcsx2-qt/GameList/GameListRefreshThread.h b/pcsx2-qt/GameList/GameListRefreshThread.h new file mode 100644 index 0000000000..f98f031684 --- /dev/null +++ b/pcsx2-qt/GameList/GameListRefreshThread.h @@ -0,0 +1,75 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#pragma once + +#include +#include + +#include "common/ProgressCallback.h" +#include "common/Timer.h" + +class GameListRefreshThread; + +class AsyncRefreshProgressCallback : public BaseProgressCallback +{ +public: + AsyncRefreshProgressCallback(GameListRefreshThread* parent); + + void Cancel(); + + void SetStatusText(const char* text) override; + void SetProgressRange(u32 range) override; + void SetProgressValue(u32 value) override; + void SetTitle(const char* title) override; + void DisplayError(const char* message) override; + void DisplayWarning(const char* message) override; + void DisplayInformation(const char* message) override; + void DisplayDebugMessage(const char* message) override; + void ModalError(const char* message) override; + bool ModalConfirmation(const char* message) override; + void ModalInformation(const char* message) override; + +private: + void fireUpdate(); + + GameListRefreshThread* m_parent; + Common::Timer m_last_update_time; + QString m_status_text; + int m_last_range = 1; + int m_last_value = 0; +}; + +class GameListRefreshThread final : public QThread +{ + Q_OBJECT + +public: + GameListRefreshThread(bool invalidate_cache); + ~GameListRefreshThread(); + + void cancel(); + +Q_SIGNALS: + void refreshProgress(const QString& status, int current, int total); + void refreshComplete(); + +protected: + void run(); + +private: + AsyncRefreshProgressCallback m_progress; + bool m_invalidate_cache; +}; diff --git a/pcsx2-qt/GameList/GameListWidget.cpp b/pcsx2-qt/GameList/GameListWidget.cpp new file mode 100644 index 0000000000..5aefe8ee0d --- /dev/null +++ b/pcsx2-qt/GameList/GameListWidget.cpp @@ -0,0 +1,457 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" + +#include "common/StringUtil.h" + +#include "pcsx2/Frontend/GameList.h" + +#include +#include +#include +#include +#include +#include + +#include "GameListModel.h" +#include "GameListRefreshThread.h" +#include "GameListWidget.h" +#include "QtHost.h" +#include "QtUtils.h" + +class GameListSortModel final : public QSortFilterProxyModel +{ +public: + explicit GameListSortModel(GameListModel* parent) + : QSortFilterProxyModel(parent) + , m_model(parent) + { + } + + bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override + { + // TODO: Search + return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent); + } + + bool lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const override + { + return m_model->lessThan(source_left, source_right, source_left.column()); + } + +private: + GameListModel* m_model; +}; + +GameListWidget::GameListWidget(QWidget* parent /* = nullptr */) + : QStackedWidget(parent) +{ +} + +GameListWidget::~GameListWidget() = default; + +void GameListWidget::initialize() +{ + m_model = new GameListModel(this); + m_model->setCoverScale(QtHost::GetBaseFloatSettingValue("UI", "GameListCoverArtScale", 0.45f)); + m_model->setShowCoverTitles(QtHost::GetBaseBoolSettingValue("UI", "GameListShowCoverTitles", true)); + m_model->setCoverScale(0.45f); + m_model->setShowCoverTitles(true); + + m_sort_model = new GameListSortModel(m_model); + m_sort_model->setSourceModel(m_model); + m_table_view = new QTableView(this); + 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); + m_table_view->setAlternatingRowColors(true); + m_table_view->setShowGrid(false); + m_table_view->setCurrentIndex({}); + m_table_view->horizontalHeader()->setHighlightSections(false); + m_table_view->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu); + m_table_view->verticalHeader()->hide(); + m_table_view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + + 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); + connect(m_table_view->horizontalHeader(), &QHeaderView::sortIndicatorChanged, this, + &GameListWidget::onTableViewHeaderSortIndicatorChanged); + + insertWidget(0, m_table_view); + + m_list_view = new GameListGridListView(this); + m_list_view->setModel(m_sort_model); + m_list_view->setModelColumn(GameListModel::Column_Cover); + m_list_view->setSelectionMode(QAbstractItemView::ExtendedSelection); + m_list_view->setViewMode(QListView::IconMode); + m_list_view->setResizeMode(QListView::Adjust); + m_list_view->setUniformItemSizes(true); + m_list_view->setItemAlignment(Qt::AlignHCenter); + m_list_view->setContextMenuPolicy(Qt::CustomContextMenu); + m_list_view->setFrameStyle(QFrame::NoFrame); + m_list_view->setSpacing(m_model->getCoverArtSpacing()); + updateListFont(); + + connect(m_list_view->selectionModel(), &QItemSelectionModel::currentChanged, this, + &GameListWidget::onSelectionModelCurrentChanged); + connect(m_list_view, &GameListGridListView::zoomIn, this, &GameListWidget::gridZoomIn); + connect(m_list_view, &GameListGridListView::zoomOut, this, &GameListWidget::gridZoomOut); + connect(m_list_view, &QListView::activated, this, &GameListWidget::onListViewItemActivated); + connect(m_list_view, &QListView::customContextMenuRequested, this, &GameListWidget::onListViewContextMenuRequested); + + insertWidget(1, m_list_view); + + if (QtHost::GetBaseBoolSettingValue("UI", "GameListGridView", false)) + setCurrentIndex(1); + else + setCurrentIndex(0); + + resizeTableViewColumnsToFit(); +} + +bool GameListWidget::isShowingGameList() const +{ + return currentIndex() == 0; +} + +bool GameListWidget::isShowingGameGrid() const +{ + return currentIndex() == 1; +} + +bool GameListWidget::getShowGridCoverTitles() const +{ + return m_model->getShowCoverTitles(); +} + +void GameListWidget::refresh(bool invalidate_cache) +{ + if (m_refresh_thread) + { + m_refresh_thread->cancel(); + m_refresh_thread->wait(); + QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); + pxAssertRel(!m_refresh_thread, "Game list thread should be unreferenced by now"); + } + + m_refresh_thread = new GameListRefreshThread(invalidate_cache); + connect(m_refresh_thread, &GameListRefreshThread::refreshProgress, this, &GameListWidget::onRefreshProgress, + Qt::QueuedConnection); + connect(m_refresh_thread, &GameListRefreshThread::refreshComplete, this, &GameListWidget::onRefreshComplete, + Qt::QueuedConnection); + m_refresh_thread->start(); +} + +void GameListWidget::onRefreshProgress(const QString& status, int current, int total) +{ + m_model->refresh(); + emit refreshProgress(status, current, total); +} + +void GameListWidget::onRefreshComplete() +{ + m_model->refresh(); + emit refreshComplete(); + + pxAssertRel(m_refresh_thread, "Has a refresh thread"); + m_refresh_thread->wait(); + delete m_refresh_thread; + m_refresh_thread = nullptr; +} + +void GameListWidget::onSelectionModelCurrentChanged(const QModelIndex& current, const QModelIndex& previous) +{ + const QModelIndex source_index = m_sort_model->mapToSource(current); + if (!source_index.isValid() || source_index.row() >= static_cast(GameList::GetEntryCount())) + return; + + emit selectionChanged(); +} + +void GameListWidget::onTableViewItemActivated(const QModelIndex& index) +{ + const QModelIndex source_index = m_sort_model->mapToSource(index); + if (!source_index.isValid() || source_index.row() >= static_cast(GameList::GetEntryCount())) + return; + + emit entryActivated(); +} + +void GameListWidget::onTableViewContextMenuRequested(const QPoint& point) +{ + emit entryContextMenuRequested(m_table_view->mapToGlobal(point)); +} + +void GameListWidget::onListViewItemActivated(const QModelIndex& index) +{ + const QModelIndex source_index = m_sort_model->mapToSource(index); + if (!source_index.isValid() || source_index.row() >= static_cast(GameList::GetEntryCount())) + return; + + emit entryActivated(); +} + +void GameListWidget::onListViewContextMenuRequested(const QPoint& point) +{ + emit entryContextMenuRequested(m_list_view->mapToGlobal(point)); +} + +void GameListWidget::onTableViewHeaderContextMenuRequested(const QPoint& point) +{ + QMenu menu; + + for (int column = 0; column < GameListModel::Column_Count; column++) + { + if (column == GameListModel::Column_Cover) + continue; + + QAction* action = menu.addAction(m_model->getColumnDisplayName(column)); + 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); + resizeTableViewColumnsToFit(); + }); + } + + menu.exec(m_table_view->mapToGlobal(point)); +} + +void GameListWidget::onTableViewHeaderSortIndicatorChanged(int, Qt::SortOrder) +{ + saveTableViewColumnSortSettings(); +} + +void GameListWidget::listZoom(float delta) +{ + static constexpr float MIN_SCALE = 0.1f; + static constexpr float MAX_SCALE = 2.0f; + + const float new_scale = std::clamp(m_model->getCoverScale() + delta, MIN_SCALE, MAX_SCALE); + QtHost::SetBaseFloatSettingValue("UI", "GameListCoverArtScale", new_scale); + m_model->setCoverScale(new_scale); + updateListFont(); + + m_model->refresh(); +} + +void GameListWidget::gridZoomIn() +{ + listZoom(0.05f); +} + +void GameListWidget::gridZoomOut() +{ + listZoom(-0.05f); +} + +void GameListWidget::refreshGridCovers() +{ + m_model->refreshCovers(); +} + +void GameListWidget::showGameList() +{ + if (currentIndex() == 0) + return; + + QtHost::SetBaseBoolSettingValue("UI", "GameListGridView", false); + setCurrentIndex(0); + resizeTableViewColumnsToFit(); +} + +void GameListWidget::showGameGrid() +{ + if (currentIndex() == 1) + return; + + QtHost::SetBaseBoolSettingValue("UI", "GameListGridView", true); + setCurrentIndex(1); +} + +void GameListWidget::setShowCoverTitles(bool enabled) +{ + if (m_model->getShowCoverTitles() == enabled) + return; + + QtHost::SetBaseBoolSettingValue("UI", "GameListShowCoverTitles", enabled); + m_model->setShowCoverTitles(enabled); + if (isShowingGameGrid()) + m_model->refresh(); +} + +void GameListWidget::updateListFont() +{ + QFont font; + font.setPointSizeF(16.0f * m_model->getCoverScale()); + m_list_view->setFont(font); +} + +void GameListWidget::resizeEvent(QResizeEvent* event) +{ + QStackedWidget::resizeEvent(event); + resizeTableViewColumnsToFit(); +} + +void GameListWidget::resizeTableViewColumnsToFit() +{ + QtUtils::ResizeColumnsForTableView(m_table_view, { + 32, // type + 80, // code + -1, // title + -1, // file title + 50, // crc + 80, // size + 50, // region + 100 // compatibility + }); +} + +static std::string getColumnVisibilitySettingsKeyName(int column) +{ + return StringUtil::StdStringFromFormat("Show%s", + GameListModel::getColumnName(static_cast(column))); +} + +void GameListWidget::loadTableViewColumnVisibilitySettings() +{ + static constexpr std::array DEFAULT_VISIBILITY = {{ + true, // type + true, // code + true, // title + false, // file title + false, // crc + true, // size + true, // region + true // compatibility + }}; + + for (int column = 0; column < GameListModel::Column_Count; column++) + { + const bool visible = QtHost::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); + QtHost::SetBaseBoolSettingValue("GameListTableView", getColumnVisibilitySettingsKeyName(column).c_str(), visible); + } +} + +void GameListWidget::saveTableViewColumnVisibilitySettings(int column) +{ + const bool visible = !m_table_view->isColumnHidden(column); + QtHost::SetBaseBoolSettingValue("GameListTableView", getColumnVisibilitySettingsKeyName(column).c_str(), visible); +} + +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(QtHost::GetBaseStringSettingValue("GameListTableView", "SortColumn")) + .value_or(DEFAULT_SORT_COLUMN); + const bool sort_descending = + QtHost::GetBaseBoolSettingValue("GameListTableView", "SortDescending", DEFAULT_SORT_DESCENDING); + m_table_view->sortByColumn(sort_column, sort_descending ? Qt::DescendingOrder : Qt::AscendingOrder); +} + +void GameListWidget::saveTableViewColumnSortSettings() +{ + const int sort_column = m_table_view->horizontalHeader()->sortIndicatorSection(); + const bool sort_descending = (m_table_view->horizontalHeader()->sortIndicatorOrder() == Qt::DescendingOrder); + + if (sort_column >= 0 && sort_column < GameListModel::Column_Count) + { + QtHost::SetBaseStringSettingValue( + "GameListTableView", "SortColumn", GameListModel::getColumnName(static_cast(sort_column))); + } + + QtHost::SetBaseBoolSettingValue("GameListTableView", "SortDescending", sort_descending); +} + +const GameList::Entry* GameListWidget::getSelectedEntry() const +{ + if (currentIndex() == 0) + { + const QItemSelectionModel* selection_model = m_table_view->selectionModel(); + if (!selection_model->hasSelection()) + return nullptr; + + const QModelIndexList selected_rows = selection_model->selectedRows(); + if (selected_rows.empty()) + return nullptr; + + const QModelIndex source_index = m_sort_model->mapToSource(selected_rows[0]); + if (!source_index.isValid()) + return nullptr; + + return GameList::GetEntryByIndex(source_index.row()); + } + else + { + const QItemSelectionModel* selection_model = m_list_view->selectionModel(); + if (!selection_model->hasSelection()) + return nullptr; + + const QModelIndex source_index = m_sort_model->mapToSource(selection_model->currentIndex()); + if (!source_index.isValid()) + return nullptr; + + return GameList::GetEntryByIndex(source_index.row()); + } +} + +GameListGridListView::GameListGridListView(QWidget* parent /*= nullptr*/) + : QListView(parent) +{ +} + +void GameListGridListView::wheelEvent(QWheelEvent* e) +{ + if (e->modifiers() & Qt::ControlModifier) + { + int dy = e->angleDelta().y(); + if (dy != 0) + { + if (dy < 0) + zoomOut(); + else + zoomIn(); + + return; + } + } + + QListView::wheelEvent(e); +} diff --git a/pcsx2-qt/GameList/GameListWidget.h b/pcsx2-qt/GameList/GameListWidget.h new file mode 100644 index 0000000000..46567d42a1 --- /dev/null +++ b/pcsx2-qt/GameList/GameListWidget.h @@ -0,0 +1,111 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#pragma once +#include "pcsx2/Frontend/GameList.h" +#include +#include +#include + +Q_DECLARE_METATYPE(const GameList::Entry*); + +class GameListModel; +class GameListSortModel; +class GameListRefreshThread; + +class GameListGridListView : public QListView +{ + Q_OBJECT + +public: + GameListGridListView(QWidget* parent = nullptr); + +Q_SIGNALS: + void zoomOut(); + void zoomIn(); + +protected: + void wheelEvent(QWheelEvent* e); +}; + +class GameListWidget : public QStackedWidget +{ + Q_OBJECT + +public: + GameListWidget(QWidget* parent = nullptr); + ~GameListWidget(); + + __fi GameListModel* getModel() const { return m_model; } + + void initialize(); + + void refresh(bool invalidate_cache); + + bool isShowingGameList() const; + bool isShowingGameGrid() const; + + bool getShowGridCoverTitles() const; + + const GameList::Entry* getSelectedEntry() const; + +Q_SIGNALS: + void refreshProgress(const QString& status, int current, int total); + void refreshComplete(); + + void selectionChanged(); + void entryActivated(); + void entryContextMenuRequested(const QPoint& point); + +private Q_SLOTS: + void onRefreshProgress(const QString& status, int current, int total); + void onRefreshComplete(); + + void onSelectionModelCurrentChanged(const QModelIndex& current, const QModelIndex& previous); + void onTableViewItemActivated(const QModelIndex& index); + void onTableViewContextMenuRequested(const QPoint& point); + void onTableViewHeaderContextMenuRequested(const QPoint& point); + void onTableViewHeaderSortIndicatorChanged(int, Qt::SortOrder); + void onListViewItemActivated(const QModelIndex& index); + void onListViewContextMenuRequested(const QPoint& point); + +public Q_SLOTS: + void showGameList(); + void showGameGrid(); + void setShowCoverTitles(bool enabled); + void gridZoomIn(); + void gridZoomOut(); + void refreshGridCovers(); + +protected: + void resizeEvent(QResizeEvent* event); + +private: + void resizeTableViewColumnsToFit(); + void loadTableViewColumnVisibilitySettings(); + void saveTableViewColumnVisibilitySettings(); + void saveTableViewColumnVisibilitySettings(int column); + void loadTableViewColumnSortSettings(); + void saveTableViewColumnSortSettings(); + void listZoom(float delta); + void updateListFont(); + + GameListModel* m_model = nullptr; + GameListSortModel* m_sort_model = nullptr; + QTableView* m_table_view = nullptr; + GameListGridListView* m_list_view = nullptr; + + GameListRefreshThread* m_refresh_thread = nullptr; +}; diff --git a/pcsx2-qt/Main.cpp b/pcsx2-qt/Main.cpp new file mode 100644 index 0000000000..80e286f9c9 --- /dev/null +++ b/pcsx2-qt/Main.cpp @@ -0,0 +1,264 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" + +#include +#include + +#include "MainWindow.h" +#include "EmuThread.h" +#include "QtHost.h" + +#include "CDVD/CDVD.h" +#include "Frontend/GameList.h" +#include "svnrev.h" + +static void PrintCommandLineVersion() +{ + std::fprintf(stderr, "PCSX2 Version %s\n", GIT_REV); + std::fprintf(stderr, "https://pcsx2.net/\n"); + std::fprintf(stderr, "\n"); +} + +static void PrintCommandLineHelp(const char* progname) +{ + PrintCommandLineVersion(); + std::fprintf(stderr, "Usage: %s [parameters] [--] [boot filename]\n", progname); + std::fprintf(stderr, "\n"); + std::fprintf(stderr, " -help: Displays this information and exits.\n"); + std::fprintf(stderr, " -version: Displays version information and exits.\n"); + std::fprintf(stderr, " -batch: Enables batch mode (exits after powering off).\n"); + std::fprintf(stderr, " -fastboot: Force fast boot for provided filename.\n"); + std::fprintf(stderr, " -slowboot: Force slow boot for provided filename.\n"); + std::fprintf(stderr, " -resume: Load resume save state. If a boot filename is provided,\n" + " that game's resume state will be loaded, otherwise the most\n" + " recent resume save state will be loaded.\n"); + std::fprintf(stderr, " -state : Loads specified save state by index.\n"); + std::fprintf(stderr, " -statefile : Loads state from the specified filename.\n" + " No boot filename is required with this option.\n"); + std::fprintf(stderr, " -fullscreen: Enters fullscreen mode immediately after starting.\n"); + std::fprintf(stderr, " -nofullscreen: Prevents fullscreen mode from triggering if enabled.\n"); + std::fprintf(stderr, " -portable: Forces \"portable mode\", data in same directory.\n"); + std::fprintf(stderr, " -settings : Loads a custom settings configuration from the\n" + " specified filename. Default settings applied if file not found.\n"); + std::fprintf(stderr, " --: Signals that no more arguments will follow and the remaining\n" + " parameters make up the filename. Use when the filename contains\n" + " spaces or starts with a dash.\n"); + std::fprintf(stderr, "\n"); +} + +static std::shared_ptr& AutoBoot(std::shared_ptr& autoboot) +{ + if (!autoboot) + { + autoboot = std::make_shared(); + autoboot->source_type = CDVD_SourceType::NoDisc; + } + return autoboot; +} + +static bool ParseCommandLineOptions(int argc, char* argv[], std::shared_ptr& autoboot) +{ + std::optional fast_boot; + std::optional start_fullscreen; + std::optional state_index; + std::string state_filename; + bool no_more_args = false; + + for (int i = 1; i < argc; i++) + { + if (!no_more_args) + { +#define CHECK_ARG(str) !std::strcmp(argv[i], str) +#define CHECK_ARG_PARAM(str) (!std::strcmp(argv[i], str) && ((i + 1) < argc)) + + if (CHECK_ARG("-help")) + { + PrintCommandLineHelp(argv[0]); + return false; + } + else if (CHECK_ARG("-version")) + { + PrintCommandLineVersion(); + return false; + } + else if (CHECK_ARG("-batch")) + { + Console.WriteLn("Enabling batch mode."); + AutoBoot(autoboot)->batch_mode = true; + continue; + } + else if (CHECK_ARG("-fastboot")) + { + Console.WriteLn("Forcing fast boot."); + fast_boot = true; + continue; + } + else if (CHECK_ARG("-slowboot")) + { + Console.WriteLn("Forcing slow boot."); + fast_boot = false; + continue; + } + else if (CHECK_ARG("-resume")) + { + state_index = -1; + continue; + } + else if (CHECK_ARG_PARAM("-state")) + { + state_index = std::atoi(argv[++i]); + continue; + } + else if (CHECK_ARG_PARAM("-statefile")) + { + AutoBoot(autoboot)->save_state = argv[++i]; + continue; + } + else if (CHECK_ARG_PARAM("-elf")) + { + AutoBoot(autoboot)->elf_override = argv[++i]; + continue; + } + else if (CHECK_ARG("-fullscreen")) + { + Console.WriteLn("Going fullscreen after booting."); + start_fullscreen = true; + continue; + } + else if (CHECK_ARG("-nofullscreen")) + { + Console.WriteLn("Preventing fullscreen after booting."); + start_fullscreen = false; + continue; + } + else if (CHECK_ARG("-portable")) + { + Console.WriteLn("Using portable mode."); + // SetUserDirectoryToProgramDirectory(); + continue; + } + else if (CHECK_ARG("-resume")) + { + state_index = -1; + continue; + } + else if (CHECK_ARG("--")) + { + no_more_args = true; + continue; + } + else if (argv[i][0] == '-') + { + Console.Error("Unknown parameter: '%s'", argv[i]); + return false; + } + +#undef CHECK_ARG +#undef CHECK_ARG_PARAM + } + + if (!AutoBoot(autoboot)->source.empty()) + AutoBoot(autoboot)->source += ' '; + else + AutoBoot(autoboot)->source_type = CDVD_SourceType::Iso; + + AutoBoot(autoboot)->source += argv[i]; + } + + return true; +} + +int main(int argc, char* argv[]) +{ +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + QGuiApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) + QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); +#endif +#endif + + QApplication app(argc, argv); + std::shared_ptr autoboot; + if (!ParseCommandLineOptions(argc, argv, autoboot)) + return EXIT_FAILURE; + + MainWindow* main_window = new MainWindow(QApplication::style()->objectName()); + + if (!QtHost::Initialize()) + { + delete main_window; + return EXIT_FAILURE; + } + + main_window->initialize(); + EmuThread::start(); + + main_window->refreshGameList(false); + main_window->show(); + + if (autoboot) + g_emu_thread->startVM(std::move(autoboot)); + + const int result = app.exec(); + + EmuThread::stop(); + QtHost::Shutdown(); + return result; +} + +#ifdef _WIN32 + +// Apparently Qt6 got rid of this? +#include "common/RedtapeWindows.h" +#include + +/* + WinMain() - Initializes Windows and calls user's startup function main(). + NOTE: WinMain() won't be called if the application was linked as a "console" + application. +*/ + +// Convert a wchar_t to char string, equivalent to QString::toLocal8Bit() +// when passed CP_ACP. +static inline char* wideToMulti(unsigned int codePage, const wchar_t* aw) +{ + const int required = WideCharToMultiByte(codePage, 0, aw, -1, nullptr, 0, nullptr, nullptr); + char* result = new char[required]; + WideCharToMultiByte(codePage, 0, aw, -1, result, required, nullptr, nullptr); + return result; +} + +extern "C" int APIENTRY WinMain(HINSTANCE, HINSTANCE, LPSTR /*cmdParamarg*/, int /* cmdShow */) +{ + int argc = 0; + wchar_t** argvW = CommandLineToArgvW(GetCommandLineW(), &argc); + if (argvW == nullptr) + return -1; + char** argv = new char*[argc + 1]; + for (int i = 0; i != argc; ++i) + argv[i] = wideToMulti(CP_ACP, argvW[i]); + argv[argc] = nullptr; + LocalFree(argvW); + const int exitCode = main(argc, argv); + for (int i = 0; (i != argc) && (argv[i] != nullptr); ++i) + delete[] argv[i]; + delete[] argv; + return exitCode; +} + +#endif \ No newline at end of file diff --git a/pcsx2-qt/MainWindow.cpp b/pcsx2-qt/MainWindow.cpp new file mode 100644 index 0000000000..9660c1cea2 --- /dev/null +++ b/pcsx2-qt/MainWindow.cpp @@ -0,0 +1,1372 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" + +#include +#include +#include +#include +#include +#include + +#include "common/FileSystem.h" + +#include "pcsx2/CDVD/CDVDaccess.h" +#include "pcsx2/Frontend/GameList.h" +#include "pcsx2/HostDisplay.h" + +#include "AboutDialog.h" +#include "DisplayWidget.h" +#include "EmuThread.h" +#include "GameList/GameListRefreshThread.h" +#include "GameList/GameListWidget.h" +#include "MainWindow.h" +#include "QtHost.h" +#include "QtUtils.h" +#include "Settings/ControllerSettingsDialog.h" +#include "Settings/GameListSettingsWidget.h" +#include "Settings/InterfaceSettingsWidget.h" +#include "svnrev.h" + +static constexpr char DISC_IMAGE_FILTER[] = QT_TRANSLATE_NOOP( + "MainWindow", "All File Types (*.bin *.iso *.cue *.chd *.cso *.elf *.irx *.m3u);;Single-Track Raw Images (*.bin " + "*.iso);;Cue Sheets (*.cue);;MAME CHD Images (*.chd);;CSO Images (*.cso);;" + "ELF Executables (*.elf);;IRX Executables (*.irx);;Playlists (*.m3u)"); + +const char* MainWindow::DEFAULT_THEME_NAME = "darkfusion"; + +MainWindow* g_main_window = nullptr; + +MainWindow::MainWindow(const QString& unthemed_style_name) + : m_unthemed_style_name(unthemed_style_name) +{ + pxAssert(!g_main_window); + g_main_window = this; +} + +MainWindow::~MainWindow() +{ + // we compare here, since recreate destroys the window later + if (g_main_window == this) + g_main_window = nullptr; +} + +void MainWindow::initialize() +{ + setIconThemeFromSettings(); + m_ui.setupUi(this); + setupAdditionalUi(); + setStyleFromSettings(); + connectSignals(); + + restoreStateFromConfig(); + switchToGameListView(); + updateWindowTitle(); + updateSaveStateMenus(QString(), QString(), 0); +} + +void MainWindow::setupAdditionalUi() +{ + const bool toolbar_visible = QtHost::GetBaseBoolSettingValue("UI", "ShowToolbar", false); + m_ui.actionViewToolbar->setChecked(toolbar_visible); + m_ui.toolBar->setVisible(toolbar_visible); + + const bool toolbars_locked = QtHost::GetBaseBoolSettingValue("UI", "LockToolbar", false); + m_ui.actionViewLockToolbar->setChecked(toolbars_locked); + m_ui.toolBar->setMovable(!toolbars_locked); + m_ui.toolBar->setContextMenuPolicy(Qt::PreventContextMenu); + + const bool status_bar_visible = QtHost::GetBaseBoolSettingValue("UI", "ShowStatusBar", true); + m_ui.actionViewStatusBar->setChecked(status_bar_visible); + m_ui.statusBar->setVisible(status_bar_visible); + + m_game_list_widget = new GameListWidget(m_ui.mainContainer); + m_game_list_widget->initialize(); + m_ui.mainContainer->insertWidget(0, m_game_list_widget); + m_ui.mainContainer->setCurrentIndex(0); + m_ui.actionGridViewShowTitles->setChecked(m_game_list_widget->getShowGridCoverTitles()); + + m_status_progress_widget = new QProgressBar(m_ui.statusBar); + m_status_progress_widget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + m_status_progress_widget->setFixedSize(140, 16); + m_status_progress_widget->hide(); + + for (u32 scale = 0; scale <= 10; scale++) + { + QAction* action = + m_ui.menuWindowSize->addAction((scale == 0) ? tr("Internal Resolution") : tr("%1x Scale").arg(scale)); + connect(action, &QAction::triggered, [scale]() { g_emu_thread->requestDisplaySize(static_cast(scale)); }); + } + + updateEmulationActions(false, false); +} + +void MainWindow::connectSignals() +{ + connect(m_ui.actionStartFile, &QAction::triggered, this, &MainWindow::onStartFileActionTriggered); + connect(m_ui.actionStartBios, &QAction::triggered, this, &MainWindow::onStartBIOSActionTriggered); + connect(m_ui.actionChangeDisc, &QAction::triggered, [this] { m_ui.menuChangeDisc->exec(QCursor::pos()); }); + connect(m_ui.actionChangeDiscFromFile, &QAction::triggered, this, &MainWindow::onChangeDiscFromFileActionTriggered); + connect(m_ui.actionChangeDiscFromDevice, &QAction::triggered, this, + &MainWindow::onChangeDiscFromDeviceActionTriggered); + connect(m_ui.actionChangeDiscFromGameList, &QAction::triggered, this, + &MainWindow::onChangeDiscFromGameListActionTriggered); + connect(m_ui.menuChangeDisc, &QMenu::aboutToShow, this, &MainWindow::onChangeDiscMenuAboutToShow); + connect(m_ui.menuChangeDisc, &QMenu::aboutToHide, this, &MainWindow::onChangeDiscMenuAboutToHide); + connect(m_ui.actionPowerOff, &QAction::triggered, []() { g_emu_thread->shutdownVM(); }); + connect(m_ui.actionLoadState, &QAction::triggered, this, [this]() { m_ui.menuLoadState->exec(QCursor::pos()); }); + connect(m_ui.actionSaveState, &QAction::triggered, this, [this]() { m_ui.menuSaveState->exec(QCursor::pos()); }); + connect(m_ui.actionExit, &QAction::triggered, this, &MainWindow::close); + connect(m_ui.menuLoadState, &QMenu::aboutToShow, this, &MainWindow::onLoadStateMenuAboutToShow); + connect(m_ui.menuSaveState, &QMenu::aboutToShow, this, &MainWindow::onSaveStateMenuAboutToShow); + connect(m_ui.actionSettings, &QAction::triggered, [this]() { doSettings(SettingsDialog::Category::Count); }); + connect(m_ui.actionInterfaceSettings, &QAction::triggered, + [this]() { doSettings(SettingsDialog::Category::InterfaceSettings); }); + connect(m_ui.actionGameListSettings, &QAction::triggered, + [this]() { doSettings(SettingsDialog::Category::GameListSettings); }); + connect(m_ui.actionEmulationSettings, &QAction::triggered, + [this]() { doSettings(SettingsDialog::Category::EmulationSettings); }); + connect(m_ui.actionBIOSSettings, &QAction::triggered, + [this]() { doSettings(SettingsDialog::Category::BIOSSettings); }); + connect(m_ui.actionSystemSettings, &QAction::triggered, + [this]() { doSettings(SettingsDialog::Category::SystemSettings); }); + connect(m_ui.actionGraphicsSettings, &QAction::triggered, + [this]() { doSettings(SettingsDialog::Category::GraphicsSettings); }); + connect(m_ui.actionAudioSettings, &QAction::triggered, + [this]() { doSettings(SettingsDialog::Category::AudioSettings); }); + connect(m_ui.actionMemoryCardSettings, &QAction::triggered, + [this]() { doSettings(SettingsDialog::Category::MemoryCardSettings); }); + connect(m_ui.actionControllerSettings, &QAction::triggered, + [this]() { doControllerSettings(ControllerSettingsDialog::Category::GlobalSettings); }); + connect(m_ui.actionHotkeySettings, &QAction::triggered, + [this]() { doControllerSettings(ControllerSettingsDialog::Category::HotkeySettings); }); + connect(m_ui.actionAddGameDirectory, &QAction::triggered, + [this]() { getSettingsDialog()->getGameListSettingsWidget()->addSearchDirectory(this); }); + connect(m_ui.actionScanForNewGames, &QAction::triggered, [this]() { refreshGameList(false); }); + connect(m_ui.actionRescanAllGames, &QAction::triggered, [this]() { refreshGameList(true); }); + connect(m_ui.actionViewToolbar, &QAction::toggled, this, &MainWindow::onViewToolbarActionToggled); + connect(m_ui.actionViewLockToolbar, &QAction::toggled, this, &MainWindow::onViewLockToolbarActionToggled); + connect(m_ui.actionViewStatusBar, &QAction::toggled, this, &MainWindow::onViewStatusBarActionToggled); + connect(m_ui.actionViewGameList, &QAction::triggered, this, &MainWindow::onViewGameListActionTriggered); + connect(m_ui.actionViewGameGrid, &QAction::triggered, this, &MainWindow::onViewGameGridActionTriggered); + connect(m_ui.actionViewSystemDisplay, &QAction::triggered, this, &MainWindow::onViewSystemDisplayTriggered); + connect(m_ui.actionViewGameProperties, &QAction::triggered, this, &MainWindow::onViewGamePropertiesActionTriggered); + connect(m_ui.actionGitHubRepository, &QAction::triggered, this, &MainWindow::onGitHubRepositoryActionTriggered); + connect(m_ui.actionSupportForums, &QAction::triggered, this, &MainWindow::onSupportForumsActionTriggered); + connect(m_ui.actionDiscordServer, &QAction::triggered, this, &MainWindow::onDiscordServerActionTriggered); + connect(m_ui.actionAboutQt, &QAction::triggered, qApp, &QApplication::aboutQt); + connect(m_ui.actionAbout, &QAction::triggered, this, &MainWindow::onAboutActionTriggered); + connect(m_ui.actionCheckForUpdates, &QAction::triggered, this, &MainWindow::onCheckForUpdatesActionTriggered); + connect(m_ui.actionOpenDataDirectory, &QAction::triggered, this, &MainWindow::onToolsOpenDataDirectoryTriggered); + connect(m_ui.actionGridViewShowTitles, &QAction::triggered, m_game_list_widget, &GameListWidget::setShowCoverTitles); + connect(m_ui.actionGridViewZoomIn, &QAction::triggered, m_game_list_widget, [this]() { + if (isShowingGameList()) + m_game_list_widget->gridZoomIn(); + }); + connect(m_ui.actionGridViewZoomOut, &QAction::triggered, m_game_list_widget, [this]() { + if (isShowingGameList()) + m_game_list_widget->gridZoomOut(); + }); + connect(m_ui.actionGridViewRefreshCovers, &QAction::triggered, m_game_list_widget, + &GameListWidget::refreshGridCovers); + + // These need to be queued connections to stop crashing due to menus opening/closing and switching focus. + connect(m_game_list_widget, &GameListWidget::refreshProgress, this, &MainWindow::onGameListRefreshProgress); + connect(m_game_list_widget, &GameListWidget::refreshComplete, this, &MainWindow::onGameListRefreshComplete); + connect(m_game_list_widget, &GameListWidget::selectionChanged, this, &MainWindow::onGameListSelectionChanged, + Qt::QueuedConnection); + connect(m_game_list_widget, &GameListWidget::entryActivated, this, &MainWindow::onGameListEntryActivated, + Qt::QueuedConnection); + connect(m_game_list_widget, &GameListWidget::entryContextMenuRequested, this, + &MainWindow::onGameListEntryContextMenuRequested, Qt::QueuedConnection); +} + +void MainWindow::connectVMThreadSignals(EmuThread* thread) +{ + connect(thread, &EmuThread::onCreateDisplayRequested, this, &MainWindow::createDisplay, Qt::BlockingQueuedConnection); + connect(thread, &EmuThread::onUpdateDisplayRequested, this, &MainWindow::updateDisplay, Qt::BlockingQueuedConnection); + connect(thread, &EmuThread::onDestroyDisplayRequested, this, &MainWindow::destroyDisplay, + Qt::BlockingQueuedConnection); + connect(thread, &EmuThread::onResizeDisplayRequested, this, &MainWindow::displayResizeRequested); + connect(thread, &EmuThread::onVMStarting, this, &MainWindow::onVMStarting); + connect(thread, &EmuThread::onVMStarted, this, &MainWindow::onVMStarted); + connect(thread, &EmuThread::onVMPaused, this, &MainWindow::onVMPaused); + connect(thread, &EmuThread::onVMResumed, this, &MainWindow::onVMResumed); + connect(thread, &EmuThread::onVMStopped, this, &MainWindow::onVMStopped); + connect(thread, &EmuThread::onGameChanged, this, &MainWindow::onGameChanged); + + connect(m_ui.actionReset, &QAction::triggered, thread, &EmuThread::resetVM); + connect(m_ui.actionPause, &QAction::toggled, thread, &EmuThread::setVMPaused); + connect(m_ui.actionFullscreen, &QAction::triggered, thread, &EmuThread::toggleFullscreen); + connect(m_ui.actionToggleSoftwareRendering, &QAction::triggered, thread, &EmuThread::toggleSoftwareRendering); + connect(m_ui.actionReloadPatches, &QAction::triggered, thread, &EmuThread::reloadPatches); + + static constexpr GSRendererType renderers[] = { +#ifdef _WIN32 + GSRendererType::DX11, +#endif + GSRendererType::OGL, GSRendererType::VK, GSRendererType::SW, GSRendererType::Null}; + for (GSRendererType renderer : renderers) + { + connect( + m_ui.menuDebugSwitchRenderer->addAction(QString::fromUtf8(Pcsx2Config::GSOptions::GetRendererName(renderer))), + &QAction::triggered, [renderer] { g_emu_thread->switchRenderer(renderer); }); + } +} + +void MainWindow::recreate() +{ + if (m_vm_valid) + g_emu_thread->shutdownVM(true, true); + + close(); + g_main_window = nullptr; + + MainWindow* new_main_window = new MainWindow(m_unthemed_style_name); + new_main_window->initialize(); + new_main_window->refreshGameList(false); + new_main_window->show(); + deleteLater(); +} + +void MainWindow::setStyleFromSettings() +{ + const std::string theme(QtHost::GetBaseStringSettingValue("UI", "Theme", DEFAULT_THEME_NAME)); + + if (theme == "fusion") + { + qApp->setPalette(QApplication::style()->standardPalette()); + qApp->setStyleSheet(QString()); + qApp->setStyle(QStyleFactory::create("Fusion")); + } + else if (theme == "darkfusion") + { + // adapted from https://gist.github.com/QuantumCD/6245215 + qApp->setStyle(QStyleFactory::create("Fusion")); + + const QColor lighterGray(75, 75, 75); + const QColor darkGray(53, 53, 53); + const QColor gray(128, 128, 128); + const QColor black(25, 25, 25); + const QColor blue(198, 238, 255); + + QPalette darkPalette; + darkPalette.setColor(QPalette::Window, darkGray); + darkPalette.setColor(QPalette::WindowText, Qt::white); + darkPalette.setColor(QPalette::Base, black); + darkPalette.setColor(QPalette::AlternateBase, darkGray); + darkPalette.setColor(QPalette::ToolTipBase, darkGray); + darkPalette.setColor(QPalette::ToolTipText, Qt::white); + darkPalette.setColor(QPalette::Text, Qt::white); + darkPalette.setColor(QPalette::Button, darkGray); + darkPalette.setColor(QPalette::ButtonText, Qt::white); + darkPalette.setColor(QPalette::Link, blue); + darkPalette.setColor(QPalette::Highlight, lighterGray); + darkPalette.setColor(QPalette::HighlightedText, Qt::white); + + darkPalette.setColor(QPalette::Active, QPalette::Button, gray.darker()); + darkPalette.setColor(QPalette::Disabled, QPalette::ButtonText, gray); + darkPalette.setColor(QPalette::Disabled, QPalette::WindowText, gray); + darkPalette.setColor(QPalette::Disabled, QPalette::Text, gray); + darkPalette.setColor(QPalette::Disabled, QPalette::Light, darkGray); + + qApp->setPalette(darkPalette); + + qApp->setStyleSheet("QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }"); + } + else if (theme == "darkfusionblue") + { + // adapted from https://gist.github.com/QuantumCD/6245215 + qApp->setStyle(QStyleFactory::create("Fusion")); + + const QColor lighterGray(75, 75, 75); + const QColor darkGray(53, 53, 53); + const QColor gray(128, 128, 128); + const QColor black(25, 25, 25); + const QColor blue(198, 238, 255); + const QColor blue2(0, 88, 208); + + QPalette darkPalette; + darkPalette.setColor(QPalette::Window, darkGray); + darkPalette.setColor(QPalette::WindowText, Qt::white); + darkPalette.setColor(QPalette::Base, black); + darkPalette.setColor(QPalette::AlternateBase, darkGray); + darkPalette.setColor(QPalette::ToolTipBase, blue2); + darkPalette.setColor(QPalette::ToolTipText, Qt::white); + darkPalette.setColor(QPalette::Text, Qt::white); + darkPalette.setColor(QPalette::Button, darkGray); + darkPalette.setColor(QPalette::ButtonText, Qt::white); + darkPalette.setColor(QPalette::Link, blue); + darkPalette.setColor(QPalette::Highlight, blue2); + darkPalette.setColor(QPalette::HighlightedText, Qt::white); + + darkPalette.setColor(QPalette::Active, QPalette::Button, gray.darker()); + darkPalette.setColor(QPalette::Disabled, QPalette::ButtonText, gray); + darkPalette.setColor(QPalette::Disabled, QPalette::WindowText, gray); + darkPalette.setColor(QPalette::Disabled, QPalette::Text, gray); + darkPalette.setColor(QPalette::Disabled, QPalette::Light, darkGray); + + qApp->setPalette(darkPalette); + + qApp->setStyleSheet("QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }"); + } + else + { + qApp->setPalette(QApplication::style()->standardPalette()); + qApp->setStyleSheet(QString()); + qApp->setStyle(m_unthemed_style_name); + } +} + +void MainWindow::setIconThemeFromSettings() +{ + const std::string theme(QtHost::GetBaseStringSettingValue("UI", "Theme", DEFAULT_THEME_NAME)); + QString icon_theme; + + if (theme == "darkfusion" || theme == "darkfusionblue") + icon_theme = QStringLiteral("white"); + else + icon_theme = QStringLiteral("black"); + + QIcon::setThemeName(icon_theme); +} + +void MainWindow::saveStateToConfig() +{ + { + const QByteArray geometry = saveGeometry(); + const QByteArray geometry_b64 = geometry.toBase64(); + const std::string old_geometry_b64 = QtHost::GetBaseStringSettingValue("UI", "MainWindowGeometry"); + if (old_geometry_b64 != geometry_b64.constData()) + QtHost::SetBaseStringSettingValue("UI", "MainWindowGeometry", geometry_b64.constData()); + } + + { + const QByteArray state = saveState(); + const QByteArray state_b64 = state.toBase64(); + const std::string old_state_b64 = QtHost::GetBaseStringSettingValue("UI", "MainWindowState"); + if (old_state_b64 != state_b64.constData()) + QtHost::SetBaseStringSettingValue("UI", "MainWindowState", state_b64.constData()); + } +} + +void MainWindow::restoreStateFromConfig() +{ + { + const std::string geometry_b64 = QtHost::GetBaseStringSettingValue("UI", "MainWindowGeometry"); + const QByteArray geometry = QByteArray::fromBase64(QByteArray::fromStdString(geometry_b64)); + if (!geometry.isEmpty()) + restoreGeometry(geometry); + } + + { + const std::string state_b64 = QtHost::GetBaseStringSettingValue("UI", "MainWindowState"); + const QByteArray state = QByteArray::fromBase64(QByteArray::fromStdString(state_b64)); + if (!state.isEmpty()) + restoreState(state); + + { + QSignalBlocker sb(m_ui.actionViewToolbar); + m_ui.actionViewToolbar->setChecked(!m_ui.toolBar->isHidden()); + } + { + QSignalBlocker sb(m_ui.actionViewStatusBar); + m_ui.actionViewStatusBar->setChecked(!m_ui.statusBar->isHidden()); + } + } +} + +void MainWindow::updateEmulationActions(bool starting, bool running) +{ + const bool starting_or_running = starting || running; + + m_ui.actionStartFile->setDisabled(starting_or_running); + m_ui.actionStartDisc->setDisabled(starting_or_running); + m_ui.actionStartBios->setDisabled(starting_or_running); + + m_ui.actionPowerOff->setEnabled(running); + m_ui.actionReset->setEnabled(running); + m_ui.actionPause->setEnabled(running); + m_ui.actionChangeDisc->setEnabled(running); + m_ui.actionCheats->setEnabled(running); + m_ui.actionScreenshot->setEnabled(running); + m_ui.actionViewSystemDisplay->setEnabled(starting_or_running); + m_ui.menuChangeDisc->setEnabled(running); + m_ui.menuCheats->setEnabled(running); + + m_ui.actionSaveState->setEnabled(running); + m_ui.menuSaveState->setEnabled(running); + m_ui.menuWindowSize->setEnabled(starting_or_running); + + m_ui.actionFullscreen->setEnabled(starting_or_running); + m_ui.actionViewGameProperties->setEnabled(running); + + m_game_list_widget->setDisabled(starting && !running); +} + +void MainWindow::updateWindowTitle() +{ + QString title; + if (!m_vm_valid || m_current_game_name.isEmpty()) + { +#if defined(_DEBUG) + title = QStringLiteral("PCSX2 [Debug] %1").arg(GIT_REV); +#else + title = QStringLiteral("PCSX2 %1").arg(GIT_REV); +#endif + } + else + { +#if defined(_DEBUG) + title = QStringLiteral("%1 [Debug]").arg(m_current_game_name); +#else + title = m_current_game_name; +#endif + } + + if (windowTitle() != title) + setWindowTitle(title); +} + +void MainWindow::setProgressBar(int current, int total) +{ + m_status_progress_widget->setValue(current); + m_status_progress_widget->setMaximum(total); + + if (m_status_progress_widget->isVisible()) + return; + + m_status_progress_widget->show(); + m_ui.statusBar->addPermanentWidget(m_status_progress_widget); +} + +void MainWindow::clearProgressBar() +{ + if (!m_status_progress_widget->isVisible()) + return; + + m_status_progress_widget->hide(); + m_ui.statusBar->removeWidget(m_status_progress_widget); +} + +bool MainWindow::isShowingGameList() const +{ + return m_ui.mainContainer->currentIndex() == 0; +} + +void MainWindow::switchToGameListView() +{ + if ((m_display_widget && !m_display_widget->parent()) || m_ui.mainContainer->currentIndex() == 0) + return; + + if (m_vm_valid) + { + m_was_focused_on_container_switch = m_vm_paused; + if (!m_vm_paused) + g_emu_thread->setVMPaused(true); + } + + m_ui.mainContainer->setCurrentIndex(0); + m_game_list_widget->setFocus(); +} + +void MainWindow::switchToEmulationView() +{ + if (!m_display_widget || !m_display_widget->parent() || m_ui.mainContainer->currentIndex() == 1) + return; + + if (m_vm_valid) + { + m_ui.mainContainer->setCurrentIndex(1); + if (m_vm_paused && !m_was_focused_on_container_switch) + g_emu_thread->setVMPaused(false); + } + + m_display_widget->setFocus(); +} + +void MainWindow::refreshGameList(bool invalidate_cache) +{ + m_game_list_widget->refresh(invalidate_cache); +} + +void MainWindow::invalidateSaveStateCache() +{ + m_save_states_invalidated = true; +} + +void MainWindow::reportError(const QString& title, const QString& message) +{ + QMessageBox::critical(this, title, message); +} + +void Host::InvalidateSaveStateCache() +{ + QMetaObject::invokeMethod(g_main_window, &MainWindow::invalidateSaveStateCache, Qt::QueuedConnection); +} + +void MainWindow::onGameListRefreshProgress(const QString& status, int current, int total) +{ + m_ui.statusBar->showMessage(status); + setProgressBar(current, total); +} + +void MainWindow::onGameListRefreshComplete() +{ + clearProgressBar(); +} + +void MainWindow::onGameListSelectionChanged() +{ + auto lock = GameList::GetLock(); + const GameList::Entry* entry = m_game_list_widget->getSelectedEntry(); + if (!entry) + return; + + m_ui.statusBar->showMessage(QString::fromStdString(entry->path)); +} + +void MainWindow::onGameListEntryActivated() +{ + auto lock = GameList::GetLock(); + const GameList::Entry* entry = m_game_list_widget->getSelectedEntry(); + if (!entry) + return; + + if (m_vm_valid) + { + // change disc on double click + g_emu_thread->changeDisc(QString::fromStdString(entry->path)); + switchToEmulationView(); + return; + } + + // only resume if the option is enabled, and we have one for this game + const bool resume = + (VMManager::ShouldSaveResumeState() && VMManager::HasSaveStateInSlot(entry->serial.c_str(), entry->crc, -1)); + startGameListEntry(entry, resume ? std::optional(-1) : std::optional(), std::nullopt); +} + +void MainWindow::onGameListEntryContextMenuRequested(const QPoint& point) +{ + auto lock = GameList::GetLock(); + const GameList::Entry* entry = m_game_list_widget->getSelectedEntry(); + + QMenu menu; + + if (entry) + { + QAction* action = menu.addAction(tr("Properties...")); + // connect(action, &QAction::triggered, [this, entry]() { GamePropertiesDialog::showForEntry(entry, this); }); + + action = menu.addAction(tr("Open Containing Directory...")); + connect(action, &QAction::triggered, [this, entry]() { + const QFileInfo fi(QString::fromStdString(entry->path)); + QtUtils::OpenURL(this, QUrl::fromLocalFile(fi.absolutePath())); + }); + + action = menu.addAction(tr("Set Cover Image...")); + connect(action, &QAction::triggered, [this, entry]() { setGameListEntryCoverImage(entry); }); + + connect(menu.addAction(tr("Exclude From List")), &QAction::triggered, + [this, entry]() { getSettingsDialog()->getGameListSettingsWidget()->addExcludedPath(entry->path); }); + + menu.addSeparator(); + + if (!m_vm_valid) + { + action = menu.addAction(tr("Default Boot")); + connect(action, &QAction::triggered, [this, entry]() { startGameListEntry(entry); }); + + // Make bold to indicate it's the default choice when double-clicking + if (!VMManager::ShouldSaveResumeState() || !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); }); + + action = menu.addAction(tr("Full Boot")); + connect(action, &QAction::triggered, [this, entry]() { startGameListEntry(entry, std::nullopt, false); }); + + if (m_ui.menuDebug->menuAction()->isVisible()) + { + // TODO: Hook this up once it's implemented. + action = menu.addAction(tr("Boot and Debug")); + } + + menu.addSeparator(); + populateLoadStateMenu(&menu, QString::fromStdString(entry->path), QString::fromStdString(entry->serial), + entry->crc); + } + else + { + action = menu.addAction(tr("Change Disc")); + connect(action, &QAction::triggered, [this, entry]() { + g_emu_thread->changeDisc(QString::fromStdString(entry->path)); + switchToEmulationView(); + }); + QtUtils::MarkActionAsDefault(action); + } + + menu.addSeparator(); + } + + connect(menu.addAction(tr("Add Search Directory...")), &QAction::triggered, + [this]() { getSettingsDialog()->getGameListSettingsWidget()->addSearchDirectory(this); }); + + menu.exec(point); +} + +void MainWindow::onStartFileActionTriggered() +{ + QString filename = QDir::toNativeSeparators( + QFileDialog::getOpenFileName(this, tr("Select Disc Image"), QString(), tr(DISC_IMAGE_FILTER), nullptr)); + if (filename.isEmpty()) + return; + + std::shared_ptr params = std::make_shared(); + VMManager::SetBootParametersForPath(filename.toStdString(), params.get()); + g_emu_thread->startVM(std::move(params)); +} + +void MainWindow::onStartBIOSActionTriggered() +{ + std::shared_ptr params = std::make_shared(); + params->source_type = CDVD_SourceType::NoDisc; + g_emu_thread->startVM(std::move(params)); +} + +void MainWindow::onChangeDiscFromFileActionTriggered() +{ + ScopedVMPause pauser(m_vm_paused); + + QString filename = + QFileDialog::getOpenFileName(this, tr("Select Disc Image"), QString(), tr(DISC_IMAGE_FILTER), nullptr); + if (filename.isEmpty()) + return; + + g_emu_thread->changeDisc(filename); +} + +void MainWindow::onChangeDiscFromGameListActionTriggered() +{ + switchToGameListView(); +} + +void MainWindow::onChangeDiscFromDeviceActionTriggered() +{ + // TODO +} + +void MainWindow::onChangeDiscMenuAboutToShow() +{ + // TODO: This is where we would populate the playlist if there is one. +} + +void MainWindow::onChangeDiscMenuAboutToHide() +{ +} + +void MainWindow::onLoadStateMenuAboutToShow() +{ + if (m_save_states_invalidated) + updateSaveStateMenus(m_current_disc_path, m_current_game_serial, m_current_game_crc); +} + +void MainWindow::onSaveStateMenuAboutToShow() +{ + if (m_save_states_invalidated) + updateSaveStateMenus(m_current_disc_path, m_current_game_serial, m_current_game_crc); +} + +void MainWindow::onViewToolbarActionToggled(bool checked) +{ + QtHost::SetBaseBoolSettingValue("UI", "ShowToolbar", checked); + m_ui.toolBar->setVisible(checked); +} + +void MainWindow::onViewLockToolbarActionToggled(bool checked) +{ + QtHost::SetBaseBoolSettingValue("UI", "LockToolbar", checked); + m_ui.toolBar->setMovable(!checked); +} + +void MainWindow::onViewStatusBarActionToggled(bool checked) +{ + QtHost::SetBaseBoolSettingValue("UI", "ShowStatusBar", checked); + m_ui.statusBar->setVisible(checked); +} + +void MainWindow::onViewGameListActionTriggered() +{ + switchToGameListView(); + m_game_list_widget->showGameList(); +} + +void MainWindow::onViewGameGridActionTriggered() +{ + switchToGameListView(); + m_game_list_widget->showGameGrid(); +} + +void MainWindow::onViewSystemDisplayTriggered() +{ + if (m_vm_valid) + switchToEmulationView(); +} + +void MainWindow::onViewGamePropertiesActionTriggered() +{ + if (!m_vm_valid) + return; +} + +void MainWindow::onGitHubRepositoryActionTriggered() +{ + QtUtils::OpenURL(this, AboutDialog::getGitHubRepositoryUrl()); +} + +void MainWindow::onSupportForumsActionTriggered() +{ + QtUtils::OpenURL(this, AboutDialog::getSupportForumsUrl()); +} + +void MainWindow::onDiscordServerActionTriggered() +{ + QtUtils::OpenURL(this, AboutDialog::getDiscordServerUrl()); +} + +void MainWindow::onAboutActionTriggered() +{ + AboutDialog about(this); + about.exec(); +} + +void MainWindow::onCheckForUpdatesActionTriggered() +{ +} + +void MainWindow::onToolsOpenDataDirectoryTriggered() +{ + const QString path(QtUtils::WxStringToQString(EmuFolders::DataRoot.ToString())); + QtUtils::OpenURL(this, QUrl::fromLocalFile(path)); +} + +void MainWindow::onThemeChanged() +{ + setStyleFromSettings(); + setIconThemeFromSettings(); + recreate(); +} + +void MainWindow::onThemeChangedFromSettings() +{ + // reopen the settings dialog after recreating + onThemeChanged(); + g_main_window->doSettings(SettingsDialog::Category::InterfaceSettings); +} + +void MainWindow::onVMStarting() +{ + m_vm_valid = true; + updateEmulationActions(true, false); + updateWindowTitle(); + + // prevent loading state until we're fully initialized + updateSaveStateMenus(QString(), QString(), 0); +} + +void MainWindow::onVMStarted() +{ + m_vm_valid = true; + updateEmulationActions(true, true); + updateWindowTitle(); +} + +void MainWindow::onVMPaused() +{ + // update UI + { + QSignalBlocker sb(m_ui.actionPause); + m_ui.actionPause->setChecked(true); + } + + m_vm_paused = true; + updateWindowTitle(); +} + +void MainWindow::onVMResumed() +{ + // update UI + { + QSignalBlocker sb(m_ui.actionPause); + m_ui.actionPause->setChecked(false); + } + + m_vm_paused = false; + updateWindowTitle(); +} + +void MainWindow::onVMStopped() +{ + m_vm_valid = false; + m_vm_paused = false; + updateEmulationActions(false, false); + updateWindowTitle(); + switchToGameListView(); +} + +void MainWindow::onGameChanged(const QString& path, const QString& serial, const QString& name, quint32 crc) +{ + m_current_disc_path = path; + m_current_game_serial = serial; + m_current_game_name = name; + m_current_game_crc = crc; + updateWindowTitle(); + updateSaveStateMenus(path, serial, crc); +} + +void MainWindow::closeEvent(QCloseEvent* event) +{ + g_emu_thread->shutdownVM(true, true); + saveStateToConfig(); + QMainWindow::closeEvent(event); +} + +DisplayWidget* MainWindow::createDisplay(bool fullscreen, bool render_to_main) +{ + pxAssertRel(!fullscreen || !render_to_main, "Not rendering to main and fullscreen"); + + HostDisplay* host_display = Host::GetHostDisplay(); + if (!host_display) + return nullptr; + + const std::string fullscreen_mode(QtHost::GetBaseStringSettingValue("EmuCore/GS", "FullscreenMode", "")); + const bool is_exclusive_fullscreen = (fullscreen && !fullscreen_mode.empty() && host_display->SupportsFullscreen()); + + QWidget* container; + if (DisplayContainer::IsNeeded(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; + } + else + { + m_display_widget = new DisplayWidget((!fullscreen && render_to_main) ? m_ui.mainContainer : nullptr); + container = m_display_widget; + } + + container->setWindowTitle(windowTitle()); + container->setWindowIcon(windowIcon()); + + if (fullscreen) + { + if (!is_exclusive_fullscreen) + container->showFullScreen(); + else + container->showNormal(); + + // updateMouseMode(System::IsPaused()); + } + else if (!render_to_main) + { + restoreDisplayWindowGeometryFromConfig(); + container->showNormal(); + } + else + { + m_ui.mainContainer->insertWidget(1, m_display_widget); + switchToEmulationView(); + } + + // we need the surface visible.. this might be able to be replaced with something else + QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); + + std::optional wi = m_display_widget->getWindowInfo(); + if (!wi.has_value()) + { + QMessageBox::critical(this, tr("Error"), tr("Failed to get window info from widget")); + destroyDisplayWidget(); + return nullptr; + } + + if (!host_display->CreateRenderDevice(wi.value(), Host::GetStringSettingValue("EmuCore/GS", "Adapter", ""), + Host::GetBoolSettingValue("EmuCore/GS", "ThreadedPresentation", false), + Host::GetBoolSettingValue("EmuCore/GS", "UseDebugDevice", false))) + { + QMessageBox::critical(this, tr("Error"), tr("Failed to create host display device context.")); + destroyDisplayWidget(); + return nullptr; + } + + if (is_exclusive_fullscreen) + setDisplayFullscreen(fullscreen_mode); + + host_display->DoneRenderContextCurrent(); + return m_display_widget; +} + +DisplayWidget* MainWindow::updateDisplay(bool fullscreen, bool render_to_main) +{ + HostDisplay* host_display = Host::GetHostDisplay(); + const bool is_fullscreen = m_display_widget->isFullScreen(); + const bool is_rendering_to_main = (!is_fullscreen && m_display_widget->parent()); + const std::string fullscreen_mode(QtHost::GetBaseStringSettingValue("EmuCore/GS", "FullscreenMode", "")); + const bool is_exclusive_fullscreen = (fullscreen && !fullscreen_mode.empty() && host_display->SupportsFullscreen()); + if (fullscreen == is_fullscreen && is_rendering_to_main == render_to_main) + return m_display_widget; + + // Skip recreating the surface if we're just transitioning between fullscreen and windowed with render-to-main off. + const bool has_container = (m_display_container != nullptr); + const bool needs_container = DisplayContainer::IsNeeded(fullscreen, render_to_main); + if (!is_rendering_to_main && !render_to_main && !is_exclusive_fullscreen && has_container == needs_container) + { + qDebug() << "Toggling to" << (fullscreen ? "fullscreen" : "windowed") << "without recreating surface"; + if (host_display->IsFullscreen()) + host_display->SetFullscreen(false, 0, 0, 0.0f); + + if (fullscreen) + { + m_display_widget->showFullScreen(); + } + else + { + restoreDisplayWindowGeometryFromConfig(); + m_display_widget->showNormal(); + } + + QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); + // updateMouseMode(System::IsPaused()); + return m_display_widget; + } + + host_display->DestroyRenderSurface(); + + destroyDisplayWidget(); + + QWidget* container; + if (DisplayContainer::IsNeeded(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; + } + else + { + m_display_widget = new DisplayWidget((!fullscreen && render_to_main) ? m_ui.mainContainer : nullptr); + container = m_display_widget; + } + + container->setWindowTitle(windowTitle()); + container->setWindowIcon(windowIcon()); + + if (fullscreen) + { + if (!is_exclusive_fullscreen) + container->showFullScreen(); + else + container->showNormal(); + + // updateMouseMode(System::IsPaused()); + } + else if (!render_to_main) + { + restoreDisplayWindowGeometryFromConfig(); + container->showNormal(); + } + else + { + m_ui.mainContainer->insertWidget(1, m_display_widget); + switchToEmulationView(); + } + + // we need the surface visible.. this might be able to be replaced with something else + QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); + + std::optional wi = m_display_widget->getWindowInfo(); + if (!wi.has_value()) + { + QMessageBox::critical(this, tr("Error"), tr("Failed to get new window info from widget")); + destroyDisplayWidget(); + return nullptr; + } + + if (!host_display->ChangeRenderWindow(wi.value())) + pxFailRel("Failed to recreate surface on new widget."); + + if (is_exclusive_fullscreen) + setDisplayFullscreen(fullscreen_mode); + + m_display_widget->setFocus(); + + QSignalBlocker blocker(m_ui.actionFullscreen); + m_ui.actionFullscreen->setChecked(fullscreen); + return m_display_widget; +} + +void MainWindow::displayResizeRequested(qint32 width, qint32 height) +{ + if (!m_display_widget) + return; + + // unapply the pixel scaling factor for hidpi + const float dpr = devicePixelRatioF(); + width = static_cast(std::max(static_cast(std::lroundf(static_cast(width) / dpr)), 1)); + height = static_cast(std::max(static_cast(std::lroundf(static_cast(height) / dpr)), 1)); + + if (m_display_container || !m_display_widget->parent()) + { + // no parent - rendering to separate window. easy. + getDisplayContainer()->resize(QSize(std::max(width, 1), std::max(height, 1))); + 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(); + resize(QSize(std::max(width, 1), std::max(height + extra_height, 1))); +} + +void MainWindow::destroyDisplay() +{ + destroyDisplayWidget(); +} + +void MainWindow::focusDisplayWidget() +{ + if (m_ui.mainContainer->currentIndex() != 1) + return; + + m_display_widget->setFocus(); +} + +QWidget* MainWindow::getDisplayContainer() const +{ + return (m_display_container ? static_cast(m_display_container) : static_cast(m_display_widget)); +} + +void MainWindow::saveDisplayWindowGeometryToConfig() +{ + const QByteArray geometry = getDisplayContainer()->saveGeometry(); + const QByteArray geometry_b64 = geometry.toBase64(); + const std::string old_geometry_b64 = QtHost::GetBaseStringSettingValue("UI", "DisplayWindowGeometry"); + if (old_geometry_b64 != geometry_b64.constData()) + QtHost::SetBaseStringSettingValue("UI", "DisplayWindowGeometry", geometry_b64.constData()); +} + +void MainWindow::restoreDisplayWindowGeometryFromConfig() +{ + const std::string geometry_b64 = QtHost::GetBaseStringSettingValue("UI", "DisplayWindowGeometry"); + const QByteArray geometry = QByteArray::fromBase64(QByteArray::fromStdString(geometry_b64)); + QWidget* container = getDisplayContainer(); + if (!geometry.isEmpty()) + container->restoreGeometry(geometry); + else + container->resize(640, 480); +} + +void MainWindow::destroyDisplayWidget() +{ + if (!m_display_widget) + return; + + if (m_display_container || (!m_display_widget->parent() && !m_display_widget->isFullScreen())) + saveDisplayWindowGeometryToConfig(); + + if (m_display_container) + m_display_container->removeDisplayWidget(); + + if (m_display_widget->parent()) + { + m_ui.mainContainer->removeWidget(m_display_widget); + m_ui.mainContainer->setCurrentIndex(0); + m_game_list_widget->setFocus(); + } + + delete m_display_widget; + m_display_widget = nullptr; + + delete m_display_container; + m_display_container = nullptr; +} + +void MainWindow::setDisplayFullscreen(const std::string& fullscreen_mode) +{ + u32 width, height; + float refresh_rate; + if (HostDisplay::ParseFullscreenMode(fullscreen_mode, &width, &height, &refresh_rate)) + { + if (Host::GetHostDisplay()->SetFullscreen(true, width, height, refresh_rate)) + { + Host::AddOSDMessage("Acquired exclusive fullscreen.", 10.0f); + } + else + { + Host::AddOSDMessage("Failed to acquire exclusive fullscreen.", 10.0f); + } + } +} + +SettingsDialog* MainWindow::getSettingsDialog() +{ + if (!m_settings_dialog) + { + m_settings_dialog = new SettingsDialog(this); + connect(m_settings_dialog->getInterfaceSettingsWidget(), &InterfaceSettingsWidget::themeChanged, this, + &MainWindow::onThemeChangedFromSettings); + } + + return m_settings_dialog; +} + +void MainWindow::doSettings(SettingsDialog::Category category) +{ + SettingsDialog* dlg = getSettingsDialog(); + if (!dlg->isVisible()) + { + dlg->setModal(false); + dlg->show(); + } + + if (category != SettingsDialog::Category::Count) + dlg->setCategory(category); +} + +ControllerSettingsDialog* MainWindow::getControllerSettingsDialog() +{ + if (!m_controller_settings_dialog) + m_controller_settings_dialog = new ControllerSettingsDialog(this); + + return m_controller_settings_dialog; +} + +void MainWindow::doControllerSettings(ControllerSettingsDialog::Category category) +{ + ControllerSettingsDialog* dlg = getControllerSettingsDialog(); + if (!dlg->isVisible()) + { + dlg->setModal(false); + dlg->show(); + } + + if (category != ControllerSettingsDialog::Category::Count) + dlg->setCategory(category); +} + +void MainWindow::startGameListEntry(const GameList::Entry* entry, std::optional save_slot, + std::optional fast_boot) +{ + std::shared_ptr params = std::make_shared(); + params->fast_boot = fast_boot; + + GameList::FillBootParametersForEntry(params.get(), entry); + + if (save_slot.has_value() && !entry->serial.empty()) + { + std::string state_filename = VMManager::GetSaveStateFileName(entry->serial.c_str(), entry->crc, save_slot.value()); + if (!FileSystem::FileExists(state_filename.c_str())) + { + QMessageBox::critical(this, tr("Error"), tr("This save state does not exist.")); + return; + } + + params->save_state = std::move(state_filename); + } + + g_emu_thread->startVM(std::move(params)); +} + +void MainWindow::setGameListEntryCoverImage(const GameList::Entry* entry) +{ + const QString filename(QFileDialog::getOpenFileName(this, tr("Select Cover Image"), QString(), + tr("All Cover Image Types (*.jpg *.jpeg *.png)"))); + if (filename.isEmpty()) + return; + + if (!GameList::GetCoverImagePathForEntry(entry).empty()) + { + if (QMessageBox::question(this, tr("Cover Already Exists"), + tr("A cover image for this game already exists, do you wish to replace it?"), + QMessageBox::Yes, QMessageBox::No) != QMessageBox::Yes) + { + return; + } + } + + const QString new_filename(QString::fromStdString(GameList::GetNewCoverImagePathForEntry(entry, filename.toUtf8().constData()))); + if (new_filename.isEmpty()) + return; + + if (QFile::exists(new_filename) && !QFile::remove(new_filename)) + { + QMessageBox::critical(this, tr("Copy Error"), tr("Failed to remove existing cover '%1'").arg(new_filename)); + return; + } + + if (!QFile::copy(filename, new_filename)) + { + QMessageBox::critical(this, tr("Copy Error"), tr("Failed to copy '%1' to '%2'").arg(filename).arg(new_filename)); + return; + } + + m_game_list_widget->refreshGridCovers(); +} + +void MainWindow::loadSaveStateSlot(s32 slot) +{ + if (m_vm_valid) + { + // easy when we're running + g_emu_thread->loadStateFromSlot(slot); + return; + } + 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) + return; + + startGameListEntry(entry, slot, std::nullopt); + } +} + +void MainWindow::loadSaveStateFile(const QString& filename, const QString& state_filename) +{ + if (m_vm_valid) + { + g_emu_thread->loadState(filename); + } + else + { + std::shared_ptr params = std::make_shared(); + VMManager::SetBootParametersForPath(filename.toStdString(), params.get()); + params->save_state = state_filename.toStdString(); + g_emu_thread->startVM(std::move(params)); + } +} + +static QString formatTimestampForSaveStateMenu(time_t timestamp) +{ + const QDateTime qtime(QDateTime::fromSecsSinceEpoch(static_cast(timestamp))); + return qtime.toString(QLocale::system().dateTimeFormat(QLocale::ShortFormat)); +} + +void MainWindow::populateLoadStateMenu(QMenu* menu, const QString& filename, const QString& serial, quint32 crc) +{ + if (serial.isEmpty()) + return; + + const bool is_right_click_menu = (menu != m_ui.menuLoadState); + + QAction* action = menu->addAction(is_right_click_menu ? tr("Load State File...") : tr("Load From File...")); + connect(action, &QAction::triggered, [this, filename]() { + const QString path( + QFileDialog::getOpenFileName(this, tr("Select Save State File"), QString(), tr("Save States (*.p2s)"))); + if (path.isEmpty()) + return; + + loadSaveStateFile(filename, path); + }); + + // don't include undo in the right click menu + if (!is_right_click_menu) + { + QAction* load_undo_state = menu->addAction(tr("Undo Load State")); + load_undo_state->setEnabled(false); // CanUndoLoadState() + // connect(load_undo_state, &QAction::triggered, this, &QtHostInterface::undoLoadState); + menu->addSeparator(); + } + + const QByteArray game_serial_utf8(serial.toUtf8()); + std::string state_filename; + FILESYSTEM_STAT_DATA sd; + if (is_right_click_menu) + { + state_filename = VMManager::GetSaveStateFileName(game_serial_utf8.constData(), crc, -1); + if (FileSystem::StatFile(state_filename.c_str(), &sd)) + { + action = menu->addAction(tr("Resume (%2)").arg(formatTimestampForSaveStateMenu(sd.ModificationTime))); + connect(action, &QAction::triggered, [this]() { loadSaveStateSlot(-1); }); + + // Make bold to indicate it's the default choice when double-clicking + if (VMManager::ShouldSaveResumeState()) + QtUtils::MarkActionAsDefault(action); + } + } + + for (s32 i = 1; i <= NUM_SAVE_STATE_SLOTS; i++) + { + FILESYSTEM_STAT_DATA sd; + state_filename = VMManager::GetSaveStateFileName(game_serial_utf8.constData(), crc, i); + if (!FileSystem::StatFile(state_filename.c_str(), &sd)) + continue; + + action = menu->addAction(tr("Save Slot %1 (%2)").arg(i).arg(formatTimestampForSaveStateMenu(sd.ModificationTime))); + connect(action, &QAction::triggered, [this, i]() { loadSaveStateSlot(i); }); + } +} + +void MainWindow::populateSaveStateMenu(QMenu* menu, const QString& serial, quint32 crc) +{ + if (serial.isEmpty()) + return; + + connect(menu->addAction(tr("Save To File...")), &QAction::triggered, [this]() { + const QString path( + QFileDialog::getSaveFileName(this, tr("Select Save State File"), QString(), tr("Save States (*.p2s)"))); + if (path.isEmpty()) + return; + + g_emu_thread->saveState(path); + }); + + menu->addSeparator(); + + const QByteArray game_serial_utf8(serial.toUtf8()); + for (s32 i = 1; i <= NUM_SAVE_STATE_SLOTS; i++) + { + std::string filename(VMManager::GetSaveStateFileName(game_serial_utf8.constData(), crc, i)); + FILESYSTEM_STAT_DATA sd; + QString timestamp; + if (FileSystem::StatFile(filename.c_str(), &sd)) + timestamp = formatTimestampForSaveStateMenu(sd.ModificationTime); + else + timestamp = tr("Empty"); + + QString title(tr("Save Slot %1 (%2)").arg(i).arg(timestamp)); + connect(menu->addAction(title), &QAction::triggered, [this, i]() { g_emu_thread->saveStateToSlot(i); }); + } +} + +void MainWindow::updateSaveStateMenus(const QString& filename, const QString& serial, quint32 crc) +{ + const bool load_enabled = !serial.isEmpty(); + const bool save_enabled = !serial.isEmpty() && m_vm_valid; + m_ui.menuLoadState->clear(); + m_ui.menuLoadState->setEnabled(load_enabled); + m_ui.actionLoadState->setEnabled(load_enabled); + m_ui.menuSaveState->clear(); + m_ui.menuSaveState->setEnabled(save_enabled); + m_ui.actionSaveState->setEnabled(save_enabled); + m_save_states_invalidated = false; + if (load_enabled) + populateLoadStateMenu(m_ui.menuLoadState, filename, serial, crc); + if (save_enabled) + populateSaveStateMenu(m_ui.menuSaveState, serial, crc); +} diff --git a/pcsx2-qt/MainWindow.h b/pcsx2-qt/MainWindow.h new file mode 100644 index 0000000000..b6bd5db766 --- /dev/null +++ b/pcsx2-qt/MainWindow.h @@ -0,0 +1,177 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#pragma once + +#include +#include + +#include "Settings/ControllerSettingsDialog.h" +#include "Settings/SettingsDialog.h" +#include "ui_MainWindow.h" + +class QProgressBar; + +class DisplayWidget; +class DisplayContainer; +class GameListWidget; +class ControllerSettingsDialog; + +class EmuThread; + +namespace GameList +{ + struct Entry; +} + +class MainWindow final : public QMainWindow +{ + Q_OBJECT + +public: + static const char* DEFAULT_THEME_NAME; + +public: + explicit MainWindow(const QString& unthemed_style_name); + ~MainWindow(); + + void initialize(); + void connectVMThreadSignals(EmuThread* thread); + +public Q_SLOTS: + void refreshGameList(bool invalidate_cache); + void invalidateSaveStateCache(); + void reportError(const QString& title, const QString& message); + +private Q_SLOTS: + DisplayWidget* createDisplay(bool fullscreen, bool render_to_main); + DisplayWidget* updateDisplay(bool fullscreen, bool render_to_main); + void displayResizeRequested(qint32 width, qint32 height); + void destroyDisplay(); + void focusDisplayWidget(); + + void onGameListRefreshComplete(); + void onGameListRefreshProgress(const QString& status, int current, int total); + void onGameListSelectionChanged(); + void onGameListEntryActivated(); + void onGameListEntryContextMenuRequested(const QPoint& point); + + void onStartFileActionTriggered(); + void onStartBIOSActionTriggered(); + void onChangeDiscFromFileActionTriggered(); + void onChangeDiscFromGameListActionTriggered(); + void onChangeDiscFromDeviceActionTriggered(); + void onChangeDiscMenuAboutToShow(); + void onChangeDiscMenuAboutToHide(); + void onLoadStateMenuAboutToShow(); + void onSaveStateMenuAboutToShow(); + void onViewToolbarActionToggled(bool checked); + void onViewLockToolbarActionToggled(bool checked); + void onViewStatusBarActionToggled(bool checked); + void onViewGameListActionTriggered(); + void onViewGameGridActionTriggered(); + void onViewSystemDisplayTriggered(); + void onViewGamePropertiesActionTriggered(); + void onGitHubRepositoryActionTriggered(); + void onSupportForumsActionTriggered(); + void onDiscordServerActionTriggered(); + void onAboutActionTriggered(); + void onCheckForUpdatesActionTriggered(); + void onToolsOpenDataDirectoryTriggered(); + void onThemeChanged(); + void onThemeChangedFromSettings(); + + void onVMStarting(); + void onVMStarted(); + void onVMPaused(); + void onVMResumed(); + void onVMStopped(); + + void onGameChanged(const QString& path, const QString& serial, const QString& name, quint32 crc); + + void recreate(); + +protected: + void closeEvent(QCloseEvent* event); + +private: + enum : s32 + { + NUM_SAVE_STATE_SLOTS = 10, + }; + + void setupAdditionalUi(); + void connectSignals(); + void setStyleFromSettings(); + void setIconThemeFromSettings(); + + void saveStateToConfig(); + void restoreStateFromConfig(); + + void updateEmulationActions(bool starting, bool running); + void updateWindowTitle(); + void setProgressBar(int current, int total); + void clearProgressBar(); + + bool isShowingGameList() const; + void switchToGameListView(); + void switchToEmulationView(); + + QWidget* getDisplayContainer() const; + void saveDisplayWindowGeometryToConfig(); + void restoreDisplayWindowGeometryFromConfig(); + void destroyDisplayWidget(); + void setDisplayFullscreen(const std::string& fullscreen_mode); + + SettingsDialog* getSettingsDialog(); + void doSettings(SettingsDialog::Category category = SettingsDialog::Category::Count); + + ControllerSettingsDialog* getControllerSettingsDialog(); + void doControllerSettings(ControllerSettingsDialog::Category category = ControllerSettingsDialog::Category::Count); + + void startGameListEntry(const GameList::Entry* entry, std::optional save_slot = std::nullopt, + std::optional fast_boot = std::nullopt); + void setGameListEntryCoverImage(const GameList::Entry* entry); + + void loadSaveStateSlot(s32 slot); + void loadSaveStateFile(const QString& filename, const QString& state_filename); + void populateLoadStateMenu(QMenu* menu, const QString& filename, const QString& serial, quint32 crc); + void populateSaveStateMenu(QMenu* menu, const QString& serial, quint32 crc); + void updateSaveStateMenus(const QString& filename, const QString& serial, quint32 crc); + + Ui::MainWindow m_ui; + + QString m_unthemed_style_name; + + GameListWidget* m_game_list_widget = nullptr; + DisplayWidget* m_display_widget = nullptr; + DisplayContainer* m_display_container = nullptr; + + SettingsDialog* m_settings_dialog = nullptr; + ControllerSettingsDialog* m_controller_settings_dialog = nullptr; + + QProgressBar* m_status_progress_widget = nullptr; + + QString m_current_disc_path; + QString m_current_game_serial; + QString m_current_game_name; + quint32 m_current_game_crc; + bool m_vm_valid = false; + bool m_vm_paused = false; + bool m_save_states_invalidated = false; + bool m_was_focused_on_container_switch = false; +}; + +extern MainWindow* g_main_window; diff --git a/pcsx2-qt/MainWindow.ui b/pcsx2-qt/MainWindow.ui new file mode 100644 index 0000000000..1d9240603e --- /dev/null +++ b/pcsx2-qt/MainWindow.ui @@ -0,0 +1,648 @@ + + + MainWindow + + + + 0 + 0 + 800 + 700 + + + + true + + + PCSX2 + + + + :/icons/AppIcon.png:/icons/AppIcon.png + + + + 0 + + + + + + + + 0 + 0 + 800 + 22 + + + + + &System + + + + Change Disc + + + + + + + + + + + + + + Cheats + + + + + + + + Load State + + + + + + + + Save State + + + + + + + + + + + + + + + + + + + + + + + + + S&ettings + + + + + + + + + + + + + + + + + + + &Help + + + + + + + + + + + + + &Debug + + + + Switch Renderer + + + + + + + + + + + &View + + + + &Window Size + + + + + + + + + + + + + + + + + + + + + + + + + &Tools + + + + + + + + + + + + + toolBar + + + + 32 + 32 + + + + Qt::ToolButtonTextUnderIcon + + + TopToolBarArea + + + false + + + false + + + + + + + + + + + + + + + + + + + + + + + + Start &File... + + + + + + + + Start &Disc... + + + + + + + + Start &BIOS + + + + + + + + &Scan For New Games + + + + + + + + &Rescan All Games + + + + + + + + Shut &Down + + + + + + + + &Reset + + + + + + + + true + + + &Pause + + + + + + + + &Load State + + + + + + + + &Save State + + + + + + + + E&xit + + + + + + + + &BIOS + + + + + + + + System + + + + + + + + Emulation + + + + + + + + &Controllers + + + + + + + + &Hotkeys + + + + + + + + &Graphics + + + + + + + + &Post-Processing Settings... + + + + + Fullscreen + + + + + + + + Resolution Scale + + + + + &GitHub Repository... + + + + + Support &Forums... + + + + + &Discord Server... + + + + + + + + Check for &Updates... + + + + + + :/icons/QT.png:/icons/QT.png + + + About &Qt... + + + + + &About PCSX2... + + + + + Change Disc... + + + + + + + + Cheats... + + + + + + + + &Audio + + + + + + + + Game List + + + + + + + + Interface + + + + + + + + Add Game Directory... + + + + + + + + &Settings... + + + + + + + + From File... + + + + + From Device... + + + + + From Game List... + + + + + Remove Disc + + + + + Global State + + + + + &Screenshot + + + + + + + + &Memory Cards + + + + + + + + true + + + true + + + &Toolbar + + + + + true + + + false + + + Lock Toolbar + + + + + true + + + true + + + &Status Bar + + + + + Game &List + + + + + + + + false + + + System &Display + + + + + + + + false + + + Game &Properties + + + + + + + + Game &Grid + + + + + + + + true + + + true + + + Show Titles (Grid View) + + + + + Zoom &In (Grid View) + + + Ctrl++ + + + + + Zoom &Out (Grid View) + + + Ctrl+- + + + + + Refresh &Covers (Grid View) + + + + + Open Memory Card Directory... + + + + + Open Data Directory... + + + + + Toggle Software Rendering + + + + + Reload Cheats/Patches + + + + + + + + diff --git a/pcsx2-qt/PrecompiledHeader.cpp b/pcsx2-qt/PrecompiledHeader.cpp new file mode 100644 index 0000000000..a11c9e9a29 --- /dev/null +++ b/pcsx2-qt/PrecompiledHeader.cpp @@ -0,0 +1,16 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" diff --git a/pcsx2-qt/PrecompiledHeader.h b/pcsx2-qt/PrecompiledHeader.h new file mode 100644 index 0000000000..518d3f9cc2 --- /dev/null +++ b/pcsx2-qt/PrecompiledHeader.h @@ -0,0 +1,21 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "pcsx2/PrecompiledHeader.h" + +// Needed because of moc shenanigans with pch. +#include + +#include \ No newline at end of file diff --git a/pcsx2-qt/QtHost.cpp b/pcsx2-qt/QtHost.cpp new file mode 100644 index 0000000000..e44dafb43f --- /dev/null +++ b/pcsx2-qt/QtHost.cpp @@ -0,0 +1,506 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" + +#include "common/Assertions.h" +#include "common/Console.h" +#include "common/FileSystem.h" +#include "common/SettingsWrapper.h" +#include "common/StringUtil.h" + +#include "pcsx2/Frontend/GameList.h" +#include "pcsx2/Frontend/INISettingsInterface.h" +#include "pcsx2/HostSettings.h" +#include "pcsx2/PAD/Host/PAD.h" + +#include +#include + +#include "EmuThread.h" +#include "GameList/GameListWidget.h" +#include "MainWindow.h" +#include "QtHost.h" + +#include "pcsx2/DebugTools/Debug.h" + +static constexpr u32 SETTINGS_VERSION = 1; +static constexpr u32 SETTINGS_SAVE_DELAY = 1000; + +////////////////////////////////////////////////////////////////////////// +// Local function declarations +////////////////////////////////////////////////////////////////////////// +static void InitializeWxRubbish(); +static bool InitializeConfig(); +static void SetDefaultConfig(); +static void QueueSettingsSave(); +static void SaveSettings(); + +////////////////////////////////////////////////////////////////////////// +// Local variable declarations +////////////////////////////////////////////////////////////////////////// +static std::unique_ptr s_settings_save_timer; +static std::unique_ptr s_base_settings_interface; + +////////////////////////////////////////////////////////////////////////// +// Initialization/Shutdown +////////////////////////////////////////////////////////////////////////// + +bool QtHost::Initialize() +{ + qRegisterMetaType>(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + + InitializeWxRubbish(); + if (!InitializeConfig()) + { + Console.WriteLn("Failed to initialize config."); + return false; + } + + return true; +} + +void QtHost::Shutdown() {} + +static bool SetCriticalFolders() +{ + std::string program_path(FileSystem::GetProgramPath()); + EmuFolders::AppRoot = wxDirName(wxFileName(StringUtil::UTF8StringToWxString(program_path))); + EmuFolders::DataRoot = EmuFolders::AppRoot; +#ifndef _WIN32 + const char* homedir = getenv("HOME"); + if (homedir) + EmuFolders::DataRoot = Path::Combine(wxString::FromUTF8(homedir), wxString(L"PCSX2")); +#endif + + EmuFolders::Settings = EmuFolders::DataRoot.Combine(wxDirName(L"inis")); + EmuFolders::Resources = EmuFolders::AppRoot.Combine(wxDirName(L"resources")); + + // the resources directory should exist, bail out if not + if (!EmuFolders::Resources.Exists()) + { + QMessageBox::critical(nullptr, QStringLiteral("Error"), + QStringLiteral("Resources directory is missing, your installation is incomplete.")); + return false; + } + + return true; +} + +void QtHost::UpdateFolders() +{ + // TODO: This should happen with the VM thread paused. + auto lock = Host::GetSettingsLock(); + EmuFolders::LoadConfig(*s_base_settings_interface.get()); + EmuFolders::EnsureFoldersExist(); +} + +bool InitializeConfig() +{ + if (!SetCriticalFolders()) + return false; + + const std::string path(Path::CombineStdString(EmuFolders::Settings, "PCSX2.ini")); + s_base_settings_interface = std::make_unique(std::move(path)); + Host::Internal::SetBaseSettingsLayer(s_base_settings_interface.get()); + + uint settings_version; + if (!s_base_settings_interface->Load() || + !s_base_settings_interface->GetUIntValue("UI", "SettingsVersion", &settings_version) || + settings_version != SETTINGS_VERSION) + { + QMessageBox::critical( + g_main_window, qApp->translate("QtHost", "Settings Reset"), + qApp->translate("QtHost", "Settings do not exist or are the incorrect version, resetting to defaults.")); + SetDefaultConfig(); + s_base_settings_interface->Save(); + } + + // TODO: Handle reset to defaults if load fails. + EmuFolders::LoadConfig(*s_base_settings_interface.get()); + EmuFolders::EnsureFoldersExist(); + QtHost::UpdateLogging(); + return true; +} + +void SetDefaultConfig() +{ + EmuConfig = Pcsx2Config(); + EmuFolders::SetDefaults(); + + SettingsInterface& si = *s_base_settings_interface.get(); + si.SetUIntValue("UI", "SettingsVersion", SETTINGS_VERSION); + + { + SettingsSaveWrapper wrapper(si); + EmuConfig.LoadSave(wrapper); + } + + EmuFolders::Save(si); + PAD::SetDefaultConfig(si); +} + +std::string QtHost::GetBaseStringSettingValue(const char* section, const char* key, const char* default_value /*= ""*/) +{ + auto lock = Host::GetSettingsLock(); + return s_base_settings_interface->GetStringValue(section, key, default_value); +} + +bool QtHost::GetBaseBoolSettingValue(const char* section, const char* key, bool default_value /*= false*/) +{ + auto lock = Host::GetSettingsLock(); + return s_base_settings_interface->GetBoolValue(section, key, default_value); +} + +int QtHost::GetBaseIntSettingValue(const char* section, const char* key, int default_value /*= 0*/) +{ + auto lock = Host::GetSettingsLock(); + return s_base_settings_interface->GetIntValue(section, key, default_value); +} + +float QtHost::GetBaseFloatSettingValue(const char* section, const char* key, float default_value /*= 0.0f*/) +{ + auto lock = Host::GetSettingsLock(); + return s_base_settings_interface->GetFloatValue(section, key, default_value); +} + +std::vector QtHost::GetBaseStringListSetting(const char* section, const char* key) +{ + auto lock = Host::GetSettingsLock(); + return s_base_settings_interface->GetStringList(section, key); +} + +void QtHost::SetBaseBoolSettingValue(const char* section, const char* key, bool value) +{ + auto lock = Host::GetSettingsLock(); + s_base_settings_interface->SetBoolValue(section, key, value); + QueueSettingsSave(); +} + +void QtHost::SetBaseIntSettingValue(const char* section, const char* key, int value) +{ + auto lock = Host::GetSettingsLock(); + s_base_settings_interface->SetIntValue(section, key, value); + QueueSettingsSave(); +} + +void QtHost::SetBaseFloatSettingValue(const char* section, const char* key, float value) +{ + auto lock = Host::GetSettingsLock(); + s_base_settings_interface->SetFloatValue(section, key, value); + QueueSettingsSave(); +} + +void QtHost::SetBaseStringSettingValue(const char* section, const char* key, const char* value) +{ + auto lock = Host::GetSettingsLock(); + s_base_settings_interface->SetStringValue(section, key, value); + QueueSettingsSave(); +} + +void QtHost::SetBaseStringListSettingValue(const char* section, const char* key, const std::vector& values) +{ + auto lock = Host::GetSettingsLock(); + s_base_settings_interface->SetStringList(section, key, values); + QueueSettingsSave(); +} + +bool QtHost::AddBaseValueToStringList(const char* section, const char* key, const char* value) +{ + auto lock = Host::GetSettingsLock(); + if (!s_base_settings_interface->AddToStringList(section, key, value)) + return false; + + QueueSettingsSave(); + return true; +} + +bool QtHost::RemoveBaseValueFromStringList(const char* section, const char* key, const char* value) +{ + auto lock = Host::GetSettingsLock(); + if (!s_base_settings_interface->RemoveFromStringList(section, key, value)) + return false; + + QueueSettingsSave(); + return true; +} + +void QtHost::RemoveBaseSettingValue(const char* section, const char* key) +{ + auto lock = Host::GetSettingsLock(); + s_base_settings_interface->DeleteValue(section, key); + QueueSettingsSave(); +} + +void SaveSettings() +{ + pxAssertRel(!g_emu_thread->isOnEmuThread(), "Saving should happen on the UI thread."); + + { + auto lock = Host::GetSettingsLock(); + if (!s_base_settings_interface->Save()) + Console.Error("Failed to save settings."); + } + + s_settings_save_timer->deleteLater(); + s_settings_save_timer.release(); +} + +void QueueSettingsSave() +{ + if (s_settings_save_timer) + return; + + s_settings_save_timer = std::make_unique(); + s_settings_save_timer->connect(s_settings_save_timer.get(), &QTimer::timeout, SaveSettings); + s_settings_save_timer->setSingleShot(true); + s_settings_save_timer->start(SETTINGS_SAVE_DELAY); +} + +std::optional> Host::ReadResourceFile(const char* filename) +{ + const std::string path(Path::CombineStdString(EmuFolders::Resources, filename)); + std::optional> ret(FileSystem::ReadBinaryFile(path.c_str())); + if (!ret.has_value()) + Console.Error("Failed to read resource file '%s'", filename); + return ret; +} + +std::optional Host::ReadResourceFileToString(const char* filename) +{ + const std::string path(Path::CombineStdString(EmuFolders::Resources, filename)); + std::optional ret(FileSystem::ReadFileToString(path.c_str())); + if (!ret.has_value()) + Console.Error("Failed to read resource file to string '%s'", filename); + return ret; +} + +void Host::ReportErrorAsync(const std::string_view& title, const std::string_view& message) +{ + if (!title.empty() && !message.empty()) + { + Console.Error("ReportErrorAsync: %*s: %*s", + static_cast(title.size()), title.data(), + static_cast(message.size()), message.data()); + } + else if (!message.empty()) + { + Console.Error("ReportErrorAsync: %*s", + static_cast(message.size()), message.data()); + } + + QMetaObject::invokeMethod(g_main_window, "reportError", Qt::QueuedConnection, + Q_ARG(const QString&, title.empty() ? QString() : QString::fromUtf8(title.data(), title.size())), + Q_ARG(const QString&, message.empty() ? QString() : QString::fromUtf8(message.data(), message.size()))); +} + +void Host::OnInputDeviceConnected(const std::string_view& identifier, const std::string_view& device_name) +{ + emit g_emu_thread->onInputDeviceConnected( + identifier.empty() ? QString() : QString::fromUtf8(identifier.data(), identifier.size()), + device_name.empty() ? QString() : QString::fromUtf8(device_name.data(), device_name.size())); +} + +void Host::OnInputDeviceDisconnected(const std::string_view& identifier) +{ + emit g_emu_thread->onInputDeviceDisconnected( + identifier.empty() ? QString() : QString::fromUtf8(identifier.data(), identifier.size())); +} + +////////////////////////////////////////////////////////////////////////// +// Interface Stuff +////////////////////////////////////////////////////////////////////////// + +const IConsoleWriter* PatchesCon = &Console; + +void LoadAllPatchesAndStuff(const Pcsx2Config& cfg) +{ + // FIXME +} + +void PatchesVerboseReset() +{ + // FIXME +} + +// Replacement for Console so we actually get output to our console window on Windows. +#ifdef _WIN32 +#include "common/RedtapeWindows.h" + +static bool s_debugger_attached = false; +static bool s_console_handle_set = false; +static HANDLE s_console_handle = nullptr; + +static void __concall ConsoleWinQt_SetTitle(const wxString& title) +{ + SetConsoleTitleW(title.wc_str()); +} + +static void __concall ConsoleWinQt_DoSetColor(ConsoleColors color) +{ + if (!s_console_handle) + return; + + static constexpr WORD colors[ConsoleColors_Count] = { + FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE, // default + FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE, // black + FOREGROUND_GREEN, // green + FOREGROUND_RED, // red + FOREGROUND_BLUE, // blue + FOREGROUND_RED | FOREGROUND_BLUE, // magenta + FOREGROUND_RED | FOREGROUND_GREEN, // orange + FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE, // gray + FOREGROUND_BLUE | FOREGROUND_INTENSITY, // cyan + FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY, // yellow + FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY, // white + FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE, // strong black + FOREGROUND_RED | FOREGROUND_INTENSITY, // strong red + FOREGROUND_GREEN | FOREGROUND_INTENSITY, // strong green + FOREGROUND_BLUE | FOREGROUND_INTENSITY, // strong blue + FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY, // strong magenta + FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY, // strong orange + FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE, // strong gray + FOREGROUND_BLUE | FOREGROUND_INTENSITY, // strong cyan + FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY, // strong yellow + FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY, // strong white + }; + + SetConsoleTextAttribute(s_console_handle, colors[static_cast(color)]); +} + +static void __concall ConsoleWinQt_Newline() +{ + if (!s_console_handle) + return; + + if (s_debugger_attached) + OutputDebugStringW(L"\n"); + + DWORD written; + WriteConsoleW(s_console_handle, L"\n", 1, &written, nullptr); +} + +static void __concall ConsoleWinQt_DoWrite(const wxString& fmt) +{ + if (!s_console_handle) + return; + + if (s_debugger_attached) + OutputDebugStringW(fmt.wc_str()); + + DWORD written; + WriteConsoleW(s_console_handle, fmt.wc_str(), static_cast(fmt.size()), &written, nullptr); +} + +static void __concall ConsoleWinQt_DoWriteLn(const wxString& fmt) +{ + if (!s_console_handle) + return; + + if (s_debugger_attached) + { + OutputDebugStringW(fmt.wc_str()); + OutputDebugStringW(L"\n"); + } + + DWORD written; + WriteConsoleW(s_console_handle, fmt.wc_str(), static_cast(fmt.size()), &written, nullptr); + WriteConsoleW(s_console_handle, L"\n", 1, &written, nullptr); +} + +static const IConsoleWriter ConsoleWriter_WinQt = + { + ConsoleWinQt_DoWrite, + ConsoleWinQt_DoWriteLn, + ConsoleWinQt_DoSetColor, + + ConsoleWinQt_DoWrite, + ConsoleWinQt_Newline, + ConsoleWinQt_SetTitle, +}; +#endif + +void QtHost::UpdateLogging() +{ + // TODO: Make this an actual option. + bool console_logging_enabled = false; + +#if defined(_DEBUG) || defined(PCSX2_DEVBUILD) + console_logging_enabled = true; +#endif + + DevConWriterEnabled = console_logging_enabled; + SysConsole.eeConsole.Enabled = console_logging_enabled; + SysConsole.iopConsole.Enabled = console_logging_enabled; + + if (console_logging_enabled) + { +#ifdef _WIN32 + s_debugger_attached = IsDebuggerPresent(); + if (!s_console_handle_set) + { + bool handle_valid = (GetConsoleWindow() != NULL); + if (!handle_valid) + { + AllocConsole(); + handle_valid = (freopen("CONIN$", "r", stdin) != nullptr); + handle_valid = (freopen("CONOUT$", "w", stdout) != nullptr); + handle_valid = (freopen("CONOUT$", "w", stderr) != nullptr); + } + + if (handle_valid) + { + s_console_handle = GetStdHandle(STD_OUTPUT_HANDLE); + s_console_handle_set = true; + } + } + + if (!s_console_handle && !s_debugger_attached) + Console_SetActiveHandler(ConsoleWriter_Null); + else + Console_SetActiveHandler(ConsoleWriter_WinQt); +#else + Console_SetActiveHandler(ConsoleWriter_Stdout); +#endif + } + else + { + Console_SetActiveHandler(ConsoleWriter_Null); + } +} + +#include + +#ifdef _WIN32 +extern "C" HINSTANCE wxGetInstance(); +extern void wxSetInstance(HINSTANCE hInst); +#endif + +void InitializeWxRubbish() +{ + wxLog::DoCreateOnDemand(); + wxLog::GetActiveTarget(); + +#ifdef _WIN32 + if (!wxGetInstance()) + wxSetInstance(::GetModuleHandle(NULL)); +#endif // _WIN32 + + wxModule::RegisterModules(); + wxModule::InitializeModules(); +} diff --git a/pcsx2-qt/QtHost.h b/pcsx2-qt/QtHost.h new file mode 100644 index 0000000000..91108cde87 --- /dev/null +++ b/pcsx2-qt/QtHost.h @@ -0,0 +1,54 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#pragma once +#include "pcsx2/Host.h" +#include "pcsx2/HostDisplay.h" +#include "pcsx2/HostSettings.h" +#include "pcsx2/Frontend/InputManager.h" +#include "pcsx2/VMManager.h" +#include + +class SettingsInterface; + +class EmuThread; + +Q_DECLARE_METATYPE(GSRendererType); +Q_DECLARE_METATYPE(std::shared_ptr); +Q_DECLARE_METATYPE(InputBindingKey); + +namespace QtHost +{ + bool Initialize(); + void Shutdown(); + + void UpdateFolders(); + void UpdateLogging(); + + /// Thread-safe settings access. + std::string GetBaseStringSettingValue(const char* section, const char* key, const char* default_value = ""); + bool GetBaseBoolSettingValue(const char* section, const char* key, bool default_value = false); + int GetBaseIntSettingValue(const char* section, const char* key, int default_value = 0); + float GetBaseFloatSettingValue(const char* section, const char* key, float default_value = 0.0f); + std::vector GetBaseStringListSetting(const char* section, const char* key); + void SetBaseBoolSettingValue(const char* section, const char* key, bool value); + void SetBaseIntSettingValue(const char* section, const char* key, int value); + void SetBaseFloatSettingValue(const char* section, const char* key, float value); + void SetBaseStringSettingValue(const char* section, const char* key, const char* value); + void SetBaseStringListSettingValue(const char* section, const char* key, const std::vector& values); + bool AddBaseValueToStringList(const char* section, const char* key, const char* value); + bool RemoveBaseValueFromStringList(const char* section, const char* key, const char* value); + void RemoveBaseSettingValue(const char* section, const char* key); +} // namespace QtHost diff --git a/pcsx2-qt/QtKeyCodes.cpp b/pcsx2-qt/QtKeyCodes.cpp new file mode 100644 index 0000000000..525652cef1 --- /dev/null +++ b/pcsx2-qt/QtKeyCodes.cpp @@ -0,0 +1,486 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" + +#include "pcsx2/Frontend/InputManager.h" + +struct KeyCodeName +{ + int code; + const char* name; +}; + +static constexpr KeyCodeName s_qt_key_names[] = { + {Qt::Key_Escape, "Escape"}, + {Qt::Key_Tab, "Tab"}, + {Qt::Key_Backtab, "Backtab"}, + {Qt::Key_Backspace, "Backspace"}, + {Qt::Key_Return, "Return"}, + {Qt::Key_Enter, "Enter"}, + {Qt::Key_Insert, "Insert"}, + {Qt::Key_Delete, "Delete"}, + {Qt::Key_Pause, "Pause"}, + {Qt::Key_Print, "Print"}, + {Qt::Key_SysReq, "SysReq"}, + {Qt::Key_Clear, "Clear"}, + {Qt::Key_Home, "Home"}, + {Qt::Key_End, "End"}, + {Qt::Key_Left, "Left"}, + {Qt::Key_Up, "Up"}, + {Qt::Key_Right, "Right"}, + {Qt::Key_Down, "Down"}, + {Qt::Key_PageUp, "PageUp"}, + {Qt::Key_PageDown, "PageDown"}, + {Qt::Key_Shift, "Shift"}, + {Qt::Key_Control, "Control"}, + {Qt::Key_Meta, "Meta"}, + {Qt::Key_Alt, "Alt"}, + {Qt::Key_CapsLock, "CapsLock"}, + {Qt::Key_NumLock, "NumLock"}, + {Qt::Key_ScrollLock, "ScrollLock"}, + {Qt::Key_F1, "F1"}, + {Qt::Key_F2, "F2"}, + {Qt::Key_F3, "F3"}, + {Qt::Key_F4, "F4"}, + {Qt::Key_F5, "F5"}, + {Qt::Key_F6, "F6"}, + {Qt::Key_F7, "F7"}, + {Qt::Key_F8, "F8"}, + {Qt::Key_F9, "F9"}, + {Qt::Key_F10, "F10"}, + {Qt::Key_F11, "F11"}, + {Qt::Key_F12, "F12"}, + {Qt::Key_F13, "F13"}, + {Qt::Key_F14, "F14"}, + {Qt::Key_F15, "F15"}, + {Qt::Key_F16, "F16"}, + {Qt::Key_F17, "F17"}, + {Qt::Key_F18, "F18"}, + {Qt::Key_F19, "F19"}, + {Qt::Key_F20, "F20"}, + {Qt::Key_F21, "F21"}, + {Qt::Key_F22, "F22"}, + {Qt::Key_F23, "F23"}, + {Qt::Key_F24, "F24"}, + {Qt::Key_F25, "F25"}, + {Qt::Key_F26, "F26"}, + {Qt::Key_F27, "F27"}, + {Qt::Key_F28, "F28"}, + {Qt::Key_F29, "F29"}, + {Qt::Key_F30, "F30"}, + {Qt::Key_F31, "F31"}, + {Qt::Key_F32, "F32"}, + {Qt::Key_F33, "F33"}, + {Qt::Key_F34, "F34"}, + {Qt::Key_F35, "F35"}, + {Qt::Key_Super_L, "Super_L"}, + {Qt::Key_Super_R, "Super_R"}, + {Qt::Key_Menu, "Menu"}, + {Qt::Key_Hyper_L, "Hyper_L"}, + {Qt::Key_Hyper_R, "Hyper_R"}, + {Qt::Key_Help, "Help"}, + {Qt::Key_Direction_L, "Direction_L"}, + {Qt::Key_Direction_R, "Direction_R"}, + {Qt::Key_Space, "Space"}, + {Qt::Key_Any, "Any"}, + {Qt::Key_Exclam, "Exclam"}, + {Qt::Key_QuoteDbl, "QuoteDbl"}, + {Qt::Key_NumberSign, "NumberSign"}, + {Qt::Key_Dollar, "Dollar"}, + {Qt::Key_Percent, "Percent"}, + {Qt::Key_Ampersand, "Ampersand"}, + {Qt::Key_Apostrophe, "Apostrophe"}, + {Qt::Key_ParenLeft, "ParenLeft"}, + {Qt::Key_ParenRight, "ParenRight"}, + {Qt::Key_Asterisk, "Asterisk"}, + {Qt::Key_Plus, "Plus"}, + {Qt::Key_Comma, "Comma"}, + {Qt::Key_Minus, "Minus"}, + {Qt::Key_Period, "Period"}, + {Qt::Key_Slash, "Slash"}, + {Qt::Key_0, "0"}, + {Qt::Key_1, "1"}, + {Qt::Key_2, "2"}, + {Qt::Key_3, "3"}, + {Qt::Key_4, "4"}, + {Qt::Key_5, "5"}, + {Qt::Key_6, "6"}, + {Qt::Key_7, "7"}, + {Qt::Key_8, "8"}, + {Qt::Key_9, "9"}, + {Qt::Key_Colon, "Colon"}, + {Qt::Key_Semicolon, "Semicolon"}, + {Qt::Key_Less, "Less"}, + {Qt::Key_Equal, "Equal"}, + {Qt::Key_Greater, "Greater"}, + {Qt::Key_Question, "Question"}, + {Qt::Key_At, "At"}, + {Qt::Key_A, "A"}, + {Qt::Key_B, "B"}, + {Qt::Key_C, "C"}, + {Qt::Key_D, "D"}, + {Qt::Key_E, "E"}, + {Qt::Key_F, "F"}, + {Qt::Key_G, "G"}, + {Qt::Key_H, "H"}, + {Qt::Key_I, "I"}, + {Qt::Key_J, "J"}, + {Qt::Key_K, "K"}, + {Qt::Key_L, "L"}, + {Qt::Key_M, "M"}, + {Qt::Key_N, "N"}, + {Qt::Key_O, "O"}, + {Qt::Key_P, "P"}, + {Qt::Key_Q, "Q"}, + {Qt::Key_R, "R"}, + {Qt::Key_S, "S"}, + {Qt::Key_T, "T"}, + {Qt::Key_U, "U"}, + {Qt::Key_V, "V"}, + {Qt::Key_W, "W"}, + {Qt::Key_X, "X"}, + {Qt::Key_Y, "Y"}, + {Qt::Key_Z, "Z"}, + {Qt::Key_BracketLeft, "BracketLeft"}, + {Qt::Key_Backslash, "Backslash"}, + {Qt::Key_BracketRight, "BracketRight"}, + {Qt::Key_AsciiCircum, "AsciiCircum"}, + {Qt::Key_Underscore, "Underscore"}, + {Qt::Key_QuoteLeft, "QuoteLeft"}, + {Qt::Key_BraceLeft, "BraceLeft"}, + {Qt::Key_Bar, "Bar"}, + {Qt::Key_BraceRight, "BraceRight"}, + {Qt::Key_AsciiTilde, "AsciiTilde"}, + {Qt::Key_nobreakspace, "nobreakspace"}, + {Qt::Key_exclamdown, "exclamdown"}, + {Qt::Key_cent, "cent"}, + {Qt::Key_sterling, "sterling"}, + {Qt::Key_currency, "currency"}, + {Qt::Key_yen, "yen"}, + {Qt::Key_brokenbar, "brokenbar"}, + {Qt::Key_section, "section"}, + {Qt::Key_diaeresis, "diaeresis"}, + {Qt::Key_copyright, "copyright"}, + {Qt::Key_ordfeminine, "ordfeminine"}, + {Qt::Key_guillemotleft, "guillemotleft"}, + {Qt::Key_notsign, "notsign"}, + {Qt::Key_hyphen, "hyphen"}, + {Qt::Key_registered, "registered"}, + {Qt::Key_macron, "macron"}, + {Qt::Key_degree, "degree"}, + {Qt::Key_plusminus, "plusminus"}, + {Qt::Key_twosuperior, "twosuperior"}, + {Qt::Key_threesuperior, "threesuperior"}, + {Qt::Key_acute, "acute"}, + {Qt::Key_mu, "mu"}, + {Qt::Key_paragraph, "paragraph"}, + {Qt::Key_periodcentered, "periodcentered"}, + {Qt::Key_cedilla, "cedilla"}, + {Qt::Key_onesuperior, "onesuperior"}, + {Qt::Key_masculine, "masculine"}, + {Qt::Key_guillemotright, "guillemotright"}, + {Qt::Key_onequarter, "onequarter"}, + {Qt::Key_onehalf, "onehalf"}, + {Qt::Key_threequarters, "threequarters"}, + {Qt::Key_questiondown, "questiondown"}, + {Qt::Key_Agrave, "Agrave"}, + {Qt::Key_Aacute, "Aacute"}, + {Qt::Key_Acircumflex, "Acircumflex"}, + {Qt::Key_Atilde, "Atilde"}, + {Qt::Key_Adiaeresis, "Adiaeresis"}, + {Qt::Key_Aring, "Aring"}, + {Qt::Key_AE, "AE"}, + {Qt::Key_Ccedilla, "Ccedilla"}, + {Qt::Key_Egrave, "Egrave"}, + {Qt::Key_Eacute, "Eacute"}, + {Qt::Key_Ecircumflex, "Ecircumflex"}, + {Qt::Key_Ediaeresis, "Ediaeresis"}, + {Qt::Key_Igrave, "Igrave"}, + {Qt::Key_Iacute, "Iacute"}, + {Qt::Key_Icircumflex, "Icircumflex"}, + {Qt::Key_Idiaeresis, "Idiaeresis"}, + {Qt::Key_ETH, "ETH"}, + {Qt::Key_Ntilde, "Ntilde"}, + {Qt::Key_Ograve, "Ograve"}, + {Qt::Key_Oacute, "Oacute"}, + {Qt::Key_Ocircumflex, "Ocircumflex"}, + {Qt::Key_Otilde, "Otilde"}, + {Qt::Key_Odiaeresis, "Odiaeresis"}, + {Qt::Key_multiply, "multiply"}, + {Qt::Key_Ooblique, "Ooblique"}, + {Qt::Key_Ugrave, "Ugrave"}, + {Qt::Key_Uacute, "Uacute"}, + {Qt::Key_Ucircumflex, "Ucircumflex"}, + {Qt::Key_Udiaeresis, "Udiaeresis"}, + {Qt::Key_Yacute, "Yacute"}, + {Qt::Key_THORN, "THORN"}, + {Qt::Key_ssharp, "ssharp"}, + {Qt::Key_division, "division"}, + {Qt::Key_ydiaeresis, "ydiaeresis"}, + {Qt::Key_AltGr, "AltGr"}, + {Qt::Key_Multi_key, "Multi_key"}, + {Qt::Key_Codeinput, "Codeinput"}, + {Qt::Key_SingleCandidate, "SingleCandidate"}, + {Qt::Key_MultipleCandidate, "MultipleCandidate"}, + {Qt::Key_PreviousCandidate, "PreviousCandidate"}, + {Qt::Key_Mode_switch, "Mode_switch"}, + {Qt::Key_Kanji, "Kanji"}, + {Qt::Key_Muhenkan, "Muhenkan"}, + {Qt::Key_Henkan, "Henkan"}, + {Qt::Key_Romaji, "Romaji"}, + {Qt::Key_Hiragana, "Hiragana"}, + {Qt::Key_Katakana, "Katakana"}, + {Qt::Key_Hiragana_Katakana, "Hiragana_Katakana"}, + {Qt::Key_Zenkaku, "Zenkaku"}, + {Qt::Key_Hankaku, "Hankaku"}, + {Qt::Key_Zenkaku_Hankaku, "Zenkaku_Hankaku"}, + {Qt::Key_Touroku, "Touroku"}, + {Qt::Key_Massyo, "Massyo"}, + {Qt::Key_Kana_Lock, "Kana_Lock"}, + {Qt::Key_Kana_Shift, "Kana_Shift"}, + {Qt::Key_Eisu_Shift, "Eisu_Shift"}, + {Qt::Key_Eisu_toggle, "Eisu_toggle"}, + {Qt::Key_Hangul, "Hangul"}, + {Qt::Key_Hangul_Start, "Hangul_Start"}, + {Qt::Key_Hangul_End, "Hangul_End"}, + {Qt::Key_Hangul_Hanja, "Hangul_Hanja"}, + {Qt::Key_Hangul_Jamo, "Hangul_Jamo"}, + {Qt::Key_Hangul_Romaja, "Hangul_Romaja"}, + {Qt::Key_Hangul_Jeonja, "Hangul_Jeonja"}, + {Qt::Key_Hangul_Banja, "Hangul_Banja"}, + {Qt::Key_Hangul_PreHanja, "Hangul_PreHanja"}, + {Qt::Key_Hangul_PostHanja, "Hangul_PostHanja"}, + {Qt::Key_Hangul_Special, "Hangul_Special"}, + {Qt::Key_Dead_Grave, "Dead_Grave"}, + {Qt::Key_Dead_Acute, "Dead_Acute"}, + {Qt::Key_Dead_Circumflex, "Dead_Circumflex"}, + {Qt::Key_Dead_Tilde, "Dead_Tilde"}, + {Qt::Key_Dead_Macron, "Dead_Macron"}, + {Qt::Key_Dead_Breve, "Dead_Breve"}, + {Qt::Key_Dead_Abovedot, "Dead_Abovedot"}, + {Qt::Key_Dead_Diaeresis, "Dead_Diaeresis"}, + {Qt::Key_Dead_Abovering, "Dead_Abovering"}, + {Qt::Key_Dead_Doubleacute, "Dead_Doubleacute"}, + {Qt::Key_Dead_Caron, "Dead_Caron"}, + {Qt::Key_Dead_Cedilla, "Dead_Cedilla"}, + {Qt::Key_Dead_Ogonek, "Dead_Ogonek"}, + {Qt::Key_Dead_Iota, "Dead_Iota"}, + {Qt::Key_Dead_Voiced_Sound, "Dead_Voiced_Sound"}, + {Qt::Key_Dead_Semivoiced_Sound, "Dead_Semivoiced_Sound"}, + {Qt::Key_Dead_Belowdot, "Dead_Belowdot"}, + {Qt::Key_Dead_Hook, "Dead_Hook"}, + {Qt::Key_Dead_Horn, "Dead_Horn"}, + {Qt::Key_Back, "Back"}, + {Qt::Key_Forward, "Forward"}, + {Qt::Key_Stop, "Stop"}, + {Qt::Key_Refresh, "Refresh"}, + {Qt::Key_VolumeDown, "VolumeDown"}, + {Qt::Key_VolumeMute, "VolumeMute"}, + {Qt::Key_VolumeUp, "VolumeUp"}, + {Qt::Key_BassBoost, "BassBoost"}, + {Qt::Key_BassUp, "BassUp"}, + {Qt::Key_BassDown, "BassDown"}, + {Qt::Key_TrebleUp, "TrebleUp"}, + {Qt::Key_TrebleDown, "TrebleDown"}, + {Qt::Key_MediaPlay, "MediaPlay"}, + {Qt::Key_MediaStop, "MediaStop"}, + {Qt::Key_MediaPrevious, "MediaPrevious"}, + {Qt::Key_MediaNext, "MediaNext"}, + {Qt::Key_MediaRecord, "MediaRecord"}, + {Qt::Key_MediaPause, "MediaPause"}, + {Qt::Key_MediaTogglePlayPause, "MediaTogglePlayPause"}, + {Qt::Key_HomePage, "HomePage"}, + {Qt::Key_Favorites, "Favorites"}, + {Qt::Key_Search, "Search"}, + {Qt::Key_Standby, "Standby"}, + {Qt::Key_OpenUrl, "OpenUrl"}, + {Qt::Key_LaunchMail, "LaunchMail"}, + {Qt::Key_LaunchMedia, "LaunchMedia"}, + {Qt::Key_Launch0, "Launch0"}, + {Qt::Key_Launch1, "Launch1"}, + {Qt::Key_Launch2, "Launch2"}, + {Qt::Key_Launch3, "Launch3"}, + {Qt::Key_Launch4, "Launch4"}, + {Qt::Key_Launch5, "Launch5"}, + {Qt::Key_Launch6, "Launch6"}, + {Qt::Key_Launch7, "Launch7"}, + {Qt::Key_Launch8, "Launch8"}, + {Qt::Key_Launch9, "Launch9"}, + {Qt::Key_LaunchA, "LaunchA"}, + {Qt::Key_LaunchB, "LaunchB"}, + {Qt::Key_LaunchC, "LaunchC"}, + {Qt::Key_LaunchD, "LaunchD"}, + {Qt::Key_LaunchE, "LaunchE"}, + {Qt::Key_LaunchF, "LaunchF"}, + {Qt::Key_MonBrightnessUp, "MonBrightnessUp"}, + {Qt::Key_MonBrightnessDown, "MonBrightnessDown"}, + {Qt::Key_KeyboardLightOnOff, "KeyboardLightOnOff"}, + {Qt::Key_KeyboardBrightnessUp, "KeyboardBrightnessUp"}, + {Qt::Key_KeyboardBrightnessDown, "KeyboardBrightnessDown"}, + {Qt::Key_PowerOff, "PowerOff"}, + {Qt::Key_WakeUp, "WakeUp"}, + {Qt::Key_Eject, "Eject"}, + {Qt::Key_ScreenSaver, "ScreenSaver"}, + {Qt::Key_WWW, "WWW"}, + {Qt::Key_Memo, "Memo"}, + {Qt::Key_LightBulb, "LightBulb"}, + {Qt::Key_Shop, "Shop"}, + {Qt::Key_History, "History"}, + {Qt::Key_AddFavorite, "AddFavorite"}, + {Qt::Key_HotLinks, "HotLinks"}, + {Qt::Key_BrightnessAdjust, "BrightnessAdjust"}, + {Qt::Key_Finance, "Finance"}, + {Qt::Key_Community, "Community"}, + {Qt::Key_AudioRewind, "AudioRewind"}, + {Qt::Key_BackForward, "BackForward"}, + {Qt::Key_ApplicationLeft, "ApplicationLeft"}, + {Qt::Key_ApplicationRight, "ApplicationRight"}, + {Qt::Key_Book, "Book"}, + {Qt::Key_CD, "CD"}, + {Qt::Key_Calculator, "Calculator"}, + {Qt::Key_ToDoList, "ToDoList"}, + {Qt::Key_ClearGrab, "ClearGrab"}, + {Qt::Key_Close, "Close"}, + {Qt::Key_Copy, "Copy"}, + {Qt::Key_Cut, "Cut"}, + {Qt::Key_Display, "Display"}, + {Qt::Key_DOS, "DOS"}, + {Qt::Key_Documents, "Documents"}, + {Qt::Key_Excel, "Excel"}, + {Qt::Key_Explorer, "Explorer"}, + {Qt::Key_Game, "Game"}, + {Qt::Key_Go, "Go"}, + {Qt::Key_iTouch, "iTouch"}, + {Qt::Key_LogOff, "LogOff"}, + {Qt::Key_Market, "Market"}, + {Qt::Key_Meeting, "Meeting"}, + {Qt::Key_MenuKB, "MenuKB"}, + {Qt::Key_MenuPB, "MenuPB"}, + {Qt::Key_MySites, "MySites"}, + {Qt::Key_News, "News"}, + {Qt::Key_OfficeHome, "OfficeHome"}, + {Qt::Key_Option, "Option"}, + {Qt::Key_Paste, "Paste"}, + {Qt::Key_Phone, "Phone"}, + {Qt::Key_Calendar, "Calendar"}, + {Qt::Key_Reply, "Reply"}, + {Qt::Key_Reload, "Reload"}, + {Qt::Key_RotateWindows, "RotateWindows"}, + {Qt::Key_RotationPB, "RotationPB"}, + {Qt::Key_RotationKB, "RotationKB"}, + {Qt::Key_Save, "Save"}, + {Qt::Key_Send, "Send"}, + {Qt::Key_Spell, "Spell"}, + {Qt::Key_SplitScreen, "SplitScreen"}, + {Qt::Key_Support, "Support"}, + {Qt::Key_TaskPane, "TaskPane"}, + {Qt::Key_Terminal, "Terminal"}, + {Qt::Key_Tools, "Tools"}, + {Qt::Key_Travel, "Travel"}, + {Qt::Key_Video, "Video"}, + {Qt::Key_Word, "Word"}, + {Qt::Key_Xfer, "Xfer"}, + {Qt::Key_ZoomIn, "ZoomIn"}, + {Qt::Key_ZoomOut, "ZoomOut"}, + {Qt::Key_Away, "Away"}, + {Qt::Key_Messenger, "Messenger"}, + {Qt::Key_WebCam, "WebCam"}, + {Qt::Key_MailForward, "MailForward"}, + {Qt::Key_Pictures, "Pictures"}, + {Qt::Key_Music, "Music"}, + {Qt::Key_Battery, "Battery"}, + {Qt::Key_Bluetooth, "Bluetooth"}, + {Qt::Key_WLAN, "WLAN"}, + {Qt::Key_UWB, "UWB"}, + {Qt::Key_AudioForward, "AudioForward"}, + {Qt::Key_AudioRepeat, "AudioRepeat"}, + {Qt::Key_AudioRandomPlay, "AudioRandomPlay"}, + {Qt::Key_Subtitle, "Subtitle"}, + {Qt::Key_AudioCycleTrack, "AudioCycleTrack"}, + {Qt::Key_Time, "Time"}, + {Qt::Key_Hibernate, "Hibernate"}, + {Qt::Key_View, "View"}, + {Qt::Key_TopMenu, "TopMenu"}, + {Qt::Key_PowerDown, "PowerDown"}, + {Qt::Key_Suspend, "Suspend"}, + {Qt::Key_ContrastAdjust, "ContrastAdjust"}, + {Qt::Key_LaunchG, "LaunchG"}, + {Qt::Key_LaunchH, "LaunchH"}, + {Qt::Key_TouchpadToggle, "TouchpadToggle"}, + {Qt::Key_TouchpadOn, "TouchpadOn"}, + {Qt::Key_TouchpadOff, "TouchpadOff"}, + {Qt::Key_MicMute, "MicMute"}, + {Qt::Key_Red, "Red"}, + {Qt::Key_Green, "Green"}, + {Qt::Key_Yellow, "Yellow"}, + {Qt::Key_Blue, "Blue"}, + {Qt::Key_ChannelUp, "ChannelUp"}, + {Qt::Key_ChannelDown, "ChannelDown"}, + {Qt::Key_Guide, "Guide"}, + {Qt::Key_Info, "Info"}, + {Qt::Key_Settings, "Settings"}, + {Qt::Key_MicVolumeUp, "MicVolumeUp"}, + {Qt::Key_MicVolumeDown, "MicVolumeDown"}, + {Qt::Key_New, "New"}, + {Qt::Key_Open, "Open"}, + {Qt::Key_Find, "Find"}, + {Qt::Key_Undo, "Undo"}, + {Qt::Key_Redo, "Redo"}, + {Qt::Key_MediaLast, "MediaLast"}, + {Qt::Key_Select, "Select"}, + {Qt::Key_Yes, "Yes"}, + {Qt::Key_No, "No"}, + {Qt::Key_Cancel, "Cancel"}, + {Qt::Key_Printer, "Printer"}, + {Qt::Key_Execute, "Execute"}, + {Qt::Key_Sleep, "Sleep"}, + {Qt::Key_Play, "Play"}, + {Qt::Key_Zoom, "Zoom"}, + {Qt::Key_Exit, "Exit"}, + {Qt::Key_Context1, "Context1"}, + {Qt::Key_Context2, "Context2"}, + {Qt::Key_Context3, "Context3"}, + {Qt::Key_Context4, "Context4"}, + {Qt::Key_Call, "Call"}, + {Qt::Key_Hangup, "Hangup"}, + {Qt::Key_Flip, "Flip"}, + {Qt::Key_ToggleCallHangup, "ToggleCallHangup"}, + {Qt::Key_VoiceDial, "VoiceDial"}, + {Qt::Key_LastNumberRedial, "LastNumberRedial"}, + {Qt::Key_Camera, "Camera"}, + {Qt::Key_CameraFocus, "CameraFocus"}}; + +std::optional InputManager::ConvertHostKeyboardStringToCode(const std::string_view& str) +{ + for (const KeyCodeName& name : s_qt_key_names) + { + if (str == name.name) + return static_cast(name.code); + } + + return std::nullopt; +} + +std::optional InputManager::ConvertHostKeyboardCodeToString(u32 code) +{ + for (const KeyCodeName& name : s_qt_key_names) + { + if (static_cast(code) == name.code) + return std::string(name.name); + } + + return std::nullopt; +} diff --git a/pcsx2-qt/QtUtils.cpp b/pcsx2-qt/QtUtils.cpp new file mode 100644 index 0000000000..c01a5e474e --- /dev/null +++ b/pcsx2-qt/QtUtils.cpp @@ -0,0 +1,703 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" + +#include +#include +#include +#include + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +#include +#else +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "QtUtils.h" + +namespace QtUtils +{ + void MarkActionAsDefault(QAction* action) + { + QFont new_font(action->font()); + new_font.setBold(true); + action->setFont(new_font); + } + + QFrame* CreateHorizontalLine(QWidget* parent) + { + QFrame* line = new QFrame(parent); + line->setFrameShape(QFrame::HLine); + line->setFrameShadow(QFrame::Sunken); + return line; + } + + QWidget* GetRootWidget(QWidget* widget, bool stop_at_window_or_dialog) + { + QWidget* next_parent = widget->parentWidget(); + while (next_parent) + { + if (stop_at_window_or_dialog && (widget->metaObject()->inherits(&QMainWindow::staticMetaObject) || + widget->metaObject()->inherits(&QDialog::staticMetaObject))) + { + break; + } + + widget = next_parent; + next_parent = widget->parentWidget(); + } + + return widget; + } + + template + static void ResizeColumnsForView(T* view, const std::initializer_list& widths) + { + QHeaderView* header; + if constexpr (std::is_same_v) + header = view->horizontalHeader(); + else + header = view->header(); + + const int min_column_width = header->minimumSectionSize(); + const int scrollbar_width = ((view->verticalScrollBar() && view->verticalScrollBar()->isVisible()) || + view->verticalScrollBarPolicy() == Qt::ScrollBarAlwaysOn) ? + view->verticalScrollBar()->width() : + 0; + int num_flex_items = 0; + int total_width = 0; + int column_index = 0; + for (const int spec_width : widths) + { + if (!view->isColumnHidden(column_index)) + { + if (spec_width < 0) + num_flex_items++; + else + total_width += std::max(spec_width, min_column_width); + } + + column_index++; + } + + const int flex_width = + (num_flex_items > 0) ? + std::max((view->contentsRect().width() - total_width - scrollbar_width) / num_flex_items, 1) : + 0; + + column_index = 0; + for (const int spec_width : widths) + { + if (view->isColumnHidden(column_index)) + { + column_index++; + continue; + } + + const int width = spec_width < 0 ? flex_width : (std::max(spec_width, min_column_width)); + view->setColumnWidth(column_index, width); + column_index++; + } + } + + void ResizeColumnsForTableView(QTableView* view, const std::initializer_list& widths) + { + ResizeColumnsForView(view, widths); + } + + void ResizeColumnsForTreeView(QTreeView* view, const std::initializer_list& widths) + { + ResizeColumnsForView(view, widths); + } + + static const std::map s_qt_key_names = { + {Qt::Key_Escape, QStringLiteral("Escape")}, + {Qt::Key_Tab, QStringLiteral("Tab")}, + {Qt::Key_Backtab, QStringLiteral("Backtab")}, + {Qt::Key_Backspace, QStringLiteral("Backspace")}, + {Qt::Key_Return, QStringLiteral("Return")}, + {Qt::Key_Enter, QStringLiteral("Enter")}, + {Qt::Key_Insert, QStringLiteral("Insert")}, + {Qt::Key_Delete, QStringLiteral("Delete")}, + {Qt::Key_Pause, QStringLiteral("Pause")}, + {Qt::Key_Print, QStringLiteral("Print")}, + {Qt::Key_SysReq, QStringLiteral("SysReq")}, + {Qt::Key_Clear, QStringLiteral("Clear")}, + {Qt::Key_Home, QStringLiteral("Home")}, + {Qt::Key_End, QStringLiteral("End")}, + {Qt::Key_Left, QStringLiteral("Left")}, + {Qt::Key_Up, QStringLiteral("Up")}, + {Qt::Key_Right, QStringLiteral("Right")}, + {Qt::Key_Down, QStringLiteral("Down")}, + {Qt::Key_PageUp, QStringLiteral("PageUp")}, + {Qt::Key_PageDown, QStringLiteral("PageDown")}, + {Qt::Key_Shift, QStringLiteral("Shift")}, + {Qt::Key_Control, QStringLiteral("Control")}, + {Qt::Key_Meta, QStringLiteral("Meta")}, + {Qt::Key_Alt, QStringLiteral("Alt")}, + {Qt::Key_CapsLock, QStringLiteral("CapsLock")}, + {Qt::Key_NumLock, QStringLiteral("NumLock")}, + {Qt::Key_ScrollLock, QStringLiteral("ScrollLock")}, + {Qt::Key_F1, QStringLiteral("F1")}, + {Qt::Key_F2, QStringLiteral("F2")}, + {Qt::Key_F3, QStringLiteral("F3")}, + {Qt::Key_F4, QStringLiteral("F4")}, + {Qt::Key_F5, QStringLiteral("F5")}, + {Qt::Key_F6, QStringLiteral("F6")}, + {Qt::Key_F7, QStringLiteral("F7")}, + {Qt::Key_F8, QStringLiteral("F8")}, + {Qt::Key_F9, QStringLiteral("F9")}, + {Qt::Key_F10, QStringLiteral("F10")}, + {Qt::Key_F11, QStringLiteral("F11")}, + {Qt::Key_F12, QStringLiteral("F12")}, + {Qt::Key_F13, QStringLiteral("F13")}, + {Qt::Key_F14, QStringLiteral("F14")}, + {Qt::Key_F15, QStringLiteral("F15")}, + {Qt::Key_F16, QStringLiteral("F16")}, + {Qt::Key_F17, QStringLiteral("F17")}, + {Qt::Key_F18, QStringLiteral("F18")}, + {Qt::Key_F19, QStringLiteral("F19")}, + {Qt::Key_F20, QStringLiteral("F20")}, + {Qt::Key_F21, QStringLiteral("F21")}, + {Qt::Key_F22, QStringLiteral("F22")}, + {Qt::Key_F23, QStringLiteral("F23")}, + {Qt::Key_F24, QStringLiteral("F24")}, + {Qt::Key_F25, QStringLiteral("F25")}, + {Qt::Key_F26, QStringLiteral("F26")}, + {Qt::Key_F27, QStringLiteral("F27")}, + {Qt::Key_F28, QStringLiteral("F28")}, + {Qt::Key_F29, QStringLiteral("F29")}, + {Qt::Key_F30, QStringLiteral("F30")}, + {Qt::Key_F31, QStringLiteral("F31")}, + {Qt::Key_F32, QStringLiteral("F32")}, + {Qt::Key_F33, QStringLiteral("F33")}, + {Qt::Key_F34, QStringLiteral("F34")}, + {Qt::Key_F35, QStringLiteral("F35")}, + {Qt::Key_Super_L, QStringLiteral("Super_L")}, + {Qt::Key_Super_R, QStringLiteral("Super_R")}, + {Qt::Key_Menu, QStringLiteral("Menu")}, + {Qt::Key_Hyper_L, QStringLiteral("Hyper_L")}, + {Qt::Key_Hyper_R, QStringLiteral("Hyper_R")}, + {Qt::Key_Help, QStringLiteral("Help")}, + {Qt::Key_Direction_L, QStringLiteral("Direction_L")}, + {Qt::Key_Direction_R, QStringLiteral("Direction_R")}, + {Qt::Key_Space, QStringLiteral("Space")}, + {Qt::Key_Any, QStringLiteral("Any")}, + {Qt::Key_Exclam, QStringLiteral("Exclam")}, + {Qt::Key_QuoteDbl, QStringLiteral("QuoteDbl")}, + {Qt::Key_NumberSign, QStringLiteral("NumberSign")}, + {Qt::Key_Dollar, QStringLiteral("Dollar")}, + {Qt::Key_Percent, QStringLiteral("Percent")}, + {Qt::Key_Ampersand, QStringLiteral("Ampersand")}, + {Qt::Key_Apostrophe, QStringLiteral("Apostrophe")}, + {Qt::Key_ParenLeft, QStringLiteral("ParenLeft")}, + {Qt::Key_ParenRight, QStringLiteral("ParenRight")}, + {Qt::Key_Asterisk, QStringLiteral("Asterisk")}, + {Qt::Key_Plus, QStringLiteral("Plus")}, + {Qt::Key_Comma, QStringLiteral("Comma")}, + {Qt::Key_Minus, QStringLiteral("Minus")}, + {Qt::Key_Period, QStringLiteral("Period")}, + {Qt::Key_Slash, QStringLiteral("Slash")}, + {Qt::Key_0, QStringLiteral("0")}, + {Qt::Key_1, QStringLiteral("1")}, + {Qt::Key_2, QStringLiteral("2")}, + {Qt::Key_3, QStringLiteral("3")}, + {Qt::Key_4, QStringLiteral("4")}, + {Qt::Key_5, QStringLiteral("5")}, + {Qt::Key_6, QStringLiteral("6")}, + {Qt::Key_7, QStringLiteral("7")}, + {Qt::Key_8, QStringLiteral("8")}, + {Qt::Key_9, QStringLiteral("9")}, + {Qt::Key_Colon, QStringLiteral("Colon")}, + {Qt::Key_Semicolon, QStringLiteral("Semicolon")}, + {Qt::Key_Less, QStringLiteral("Less")}, + {Qt::Key_Equal, QStringLiteral("Equal")}, + {Qt::Key_Greater, QStringLiteral("Greater")}, + {Qt::Key_Question, QStringLiteral("Question")}, + {Qt::Key_At, QStringLiteral("At")}, + {Qt::Key_A, QStringLiteral("A")}, + {Qt::Key_B, QStringLiteral("B")}, + {Qt::Key_C, QStringLiteral("C")}, + {Qt::Key_D, QStringLiteral("D")}, + {Qt::Key_E, QStringLiteral("E")}, + {Qt::Key_F, QStringLiteral("F")}, + {Qt::Key_G, QStringLiteral("G")}, + {Qt::Key_H, QStringLiteral("H")}, + {Qt::Key_I, QStringLiteral("I")}, + {Qt::Key_J, QStringLiteral("J")}, + {Qt::Key_K, QStringLiteral("K")}, + {Qt::Key_L, QStringLiteral("L")}, + {Qt::Key_M, QStringLiteral("M")}, + {Qt::Key_N, QStringLiteral("N")}, + {Qt::Key_O, QStringLiteral("O")}, + {Qt::Key_P, QStringLiteral("P")}, + {Qt::Key_Q, QStringLiteral("Q")}, + {Qt::Key_R, QStringLiteral("R")}, + {Qt::Key_S, QStringLiteral("S")}, + {Qt::Key_T, QStringLiteral("T")}, + {Qt::Key_U, QStringLiteral("U")}, + {Qt::Key_V, QStringLiteral("V")}, + {Qt::Key_W, QStringLiteral("W")}, + {Qt::Key_X, QStringLiteral("X")}, + {Qt::Key_Y, QStringLiteral("Y")}, + {Qt::Key_Z, QStringLiteral("Z")}, + {Qt::Key_BracketLeft, QStringLiteral("BracketLeft")}, + {Qt::Key_Backslash, QStringLiteral("Backslash")}, + {Qt::Key_BracketRight, QStringLiteral("BracketRight")}, + {Qt::Key_AsciiCircum, QStringLiteral("AsciiCircum")}, + {Qt::Key_Underscore, QStringLiteral("Underscore")}, + {Qt::Key_QuoteLeft, QStringLiteral("QuoteLeft")}, + {Qt::Key_BraceLeft, QStringLiteral("BraceLeft")}, + {Qt::Key_Bar, QStringLiteral("Bar")}, + {Qt::Key_BraceRight, QStringLiteral("BraceRight")}, + {Qt::Key_AsciiTilde, QStringLiteral("AsciiTilde")}, + {Qt::Key_nobreakspace, QStringLiteral("nobreakspace")}, + {Qt::Key_exclamdown, QStringLiteral("exclamdown")}, + {Qt::Key_cent, QStringLiteral("cent")}, + {Qt::Key_sterling, QStringLiteral("sterling")}, + {Qt::Key_currency, QStringLiteral("currency")}, + {Qt::Key_yen, QStringLiteral("yen")}, + {Qt::Key_brokenbar, QStringLiteral("brokenbar")}, + {Qt::Key_section, QStringLiteral("section")}, + {Qt::Key_diaeresis, QStringLiteral("diaeresis")}, + {Qt::Key_copyright, QStringLiteral("copyright")}, + {Qt::Key_ordfeminine, QStringLiteral("ordfeminine")}, + {Qt::Key_guillemotleft, QStringLiteral("guillemotleft")}, + {Qt::Key_notsign, QStringLiteral("notsign")}, + {Qt::Key_hyphen, QStringLiteral("hyphen")}, + {Qt::Key_registered, QStringLiteral("registered")}, + {Qt::Key_macron, QStringLiteral("macron")}, + {Qt::Key_degree, QStringLiteral("degree")}, + {Qt::Key_plusminus, QStringLiteral("plusminus")}, + {Qt::Key_twosuperior, QStringLiteral("twosuperior")}, + {Qt::Key_threesuperior, QStringLiteral("threesuperior")}, + {Qt::Key_acute, QStringLiteral("acute")}, + {Qt::Key_mu, QStringLiteral("mu")}, + {Qt::Key_paragraph, QStringLiteral("paragraph")}, + {Qt::Key_periodcentered, QStringLiteral("periodcentered")}, + {Qt::Key_cedilla, QStringLiteral("cedilla")}, + {Qt::Key_onesuperior, QStringLiteral("onesuperior")}, + {Qt::Key_masculine, QStringLiteral("masculine")}, + {Qt::Key_guillemotright, QStringLiteral("guillemotright")}, + {Qt::Key_onequarter, QStringLiteral("onequarter")}, + {Qt::Key_onehalf, QStringLiteral("onehalf")}, + {Qt::Key_threequarters, QStringLiteral("threequarters")}, + {Qt::Key_questiondown, QStringLiteral("questiondown")}, + {Qt::Key_Agrave, QStringLiteral("Agrave")}, + {Qt::Key_Aacute, QStringLiteral("Aacute")}, + {Qt::Key_Acircumflex, QStringLiteral("Acircumflex")}, + {Qt::Key_Atilde, QStringLiteral("Atilde")}, + {Qt::Key_Adiaeresis, QStringLiteral("Adiaeresis")}, + {Qt::Key_Aring, QStringLiteral("Aring")}, + {Qt::Key_AE, QStringLiteral("AE")}, + {Qt::Key_Ccedilla, QStringLiteral("Ccedilla")}, + {Qt::Key_Egrave, QStringLiteral("Egrave")}, + {Qt::Key_Eacute, QStringLiteral("Eacute")}, + {Qt::Key_Ecircumflex, QStringLiteral("Ecircumflex")}, + {Qt::Key_Ediaeresis, QStringLiteral("Ediaeresis")}, + {Qt::Key_Igrave, QStringLiteral("Igrave")}, + {Qt::Key_Iacute, QStringLiteral("Iacute")}, + {Qt::Key_Icircumflex, QStringLiteral("Icircumflex")}, + {Qt::Key_Idiaeresis, QStringLiteral("Idiaeresis")}, + {Qt::Key_ETH, QStringLiteral("ETH")}, + {Qt::Key_Ntilde, QStringLiteral("Ntilde")}, + {Qt::Key_Ograve, QStringLiteral("Ograve")}, + {Qt::Key_Oacute, QStringLiteral("Oacute")}, + {Qt::Key_Ocircumflex, QStringLiteral("Ocircumflex")}, + {Qt::Key_Otilde, QStringLiteral("Otilde")}, + {Qt::Key_Odiaeresis, QStringLiteral("Odiaeresis")}, + {Qt::Key_multiply, QStringLiteral("multiply")}, + {Qt::Key_Ooblique, QStringLiteral("Ooblique")}, + {Qt::Key_Ugrave, QStringLiteral("Ugrave")}, + {Qt::Key_Uacute, QStringLiteral("Uacute")}, + {Qt::Key_Ucircumflex, QStringLiteral("Ucircumflex")}, + {Qt::Key_Udiaeresis, QStringLiteral("Udiaeresis")}, + {Qt::Key_Yacute, QStringLiteral("Yacute")}, + {Qt::Key_THORN, QStringLiteral("THORN")}, + {Qt::Key_ssharp, QStringLiteral("ssharp")}, + {Qt::Key_division, QStringLiteral("division")}, + {Qt::Key_ydiaeresis, QStringLiteral("ydiaeresis")}, + {Qt::Key_AltGr, QStringLiteral("AltGr")}, + {Qt::Key_Multi_key, QStringLiteral("Multi_key")}, + {Qt::Key_Codeinput, QStringLiteral("Codeinput")}, + {Qt::Key_SingleCandidate, QStringLiteral("SingleCandidate")}, + {Qt::Key_MultipleCandidate, QStringLiteral("MultipleCandidate")}, + {Qt::Key_PreviousCandidate, QStringLiteral("PreviousCandidate")}, + {Qt::Key_Mode_switch, QStringLiteral("Mode_switch")}, + {Qt::Key_Kanji, QStringLiteral("Kanji")}, + {Qt::Key_Muhenkan, QStringLiteral("Muhenkan")}, + {Qt::Key_Henkan, QStringLiteral("Henkan")}, + {Qt::Key_Romaji, QStringLiteral("Romaji")}, + {Qt::Key_Hiragana, QStringLiteral("Hiragana")}, + {Qt::Key_Katakana, QStringLiteral("Katakana")}, + {Qt::Key_Hiragana_Katakana, QStringLiteral("Hiragana_Katakana")}, + {Qt::Key_Zenkaku, QStringLiteral("Zenkaku")}, + {Qt::Key_Hankaku, QStringLiteral("Hankaku")}, + {Qt::Key_Zenkaku_Hankaku, QStringLiteral("Zenkaku_Hankaku")}, + {Qt::Key_Touroku, QStringLiteral("Touroku")}, + {Qt::Key_Massyo, QStringLiteral("Massyo")}, + {Qt::Key_Kana_Lock, QStringLiteral("Kana_Lock")}, + {Qt::Key_Kana_Shift, QStringLiteral("Kana_Shift")}, + {Qt::Key_Eisu_Shift, QStringLiteral("Eisu_Shift")}, + {Qt::Key_Eisu_toggle, QStringLiteral("Eisu_toggle")}, + {Qt::Key_Hangul, QStringLiteral("Hangul")}, + {Qt::Key_Hangul_Start, QStringLiteral("Hangul_Start")}, + {Qt::Key_Hangul_End, QStringLiteral("Hangul_End")}, + {Qt::Key_Hangul_Hanja, QStringLiteral("Hangul_Hanja")}, + {Qt::Key_Hangul_Jamo, QStringLiteral("Hangul_Jamo")}, + {Qt::Key_Hangul_Romaja, QStringLiteral("Hangul_Romaja")}, + {Qt::Key_Hangul_Jeonja, QStringLiteral("Hangul_Jeonja")}, + {Qt::Key_Hangul_Banja, QStringLiteral("Hangul_Banja")}, + {Qt::Key_Hangul_PreHanja, QStringLiteral("Hangul_PreHanja")}, + {Qt::Key_Hangul_PostHanja, QStringLiteral("Hangul_PostHanja")}, + {Qt::Key_Hangul_Special, QStringLiteral("Hangul_Special")}, + {Qt::Key_Dead_Grave, QStringLiteral("Dead_Grave")}, + {Qt::Key_Dead_Acute, QStringLiteral("Dead_Acute")}, + {Qt::Key_Dead_Circumflex, QStringLiteral("Dead_Circumflex")}, + {Qt::Key_Dead_Tilde, QStringLiteral("Dead_Tilde")}, + {Qt::Key_Dead_Macron, QStringLiteral("Dead_Macron")}, + {Qt::Key_Dead_Breve, QStringLiteral("Dead_Breve")}, + {Qt::Key_Dead_Abovedot, QStringLiteral("Dead_Abovedot")}, + {Qt::Key_Dead_Diaeresis, QStringLiteral("Dead_Diaeresis")}, + {Qt::Key_Dead_Abovering, QStringLiteral("Dead_Abovering")}, + {Qt::Key_Dead_Doubleacute, QStringLiteral("Dead_Doubleacute")}, + {Qt::Key_Dead_Caron, QStringLiteral("Dead_Caron")}, + {Qt::Key_Dead_Cedilla, QStringLiteral("Dead_Cedilla")}, + {Qt::Key_Dead_Ogonek, QStringLiteral("Dead_Ogonek")}, + {Qt::Key_Dead_Iota, QStringLiteral("Dead_Iota")}, + {Qt::Key_Dead_Voiced_Sound, QStringLiteral("Dead_Voiced_Sound")}, + {Qt::Key_Dead_Semivoiced_Sound, QStringLiteral("Dead_Semivoiced_Sound")}, + {Qt::Key_Dead_Belowdot, QStringLiteral("Dead_Belowdot")}, + {Qt::Key_Dead_Hook, QStringLiteral("Dead_Hook")}, + {Qt::Key_Dead_Horn, QStringLiteral("Dead_Horn")}, + {Qt::Key_Back, QStringLiteral("Back")}, + {Qt::Key_Forward, QStringLiteral("Forward")}, + {Qt::Key_Stop, QStringLiteral("Stop")}, + {Qt::Key_Refresh, QStringLiteral("Refresh")}, + {Qt::Key_VolumeDown, QStringLiteral("VolumeDown")}, + {Qt::Key_VolumeMute, QStringLiteral("VolumeMute")}, + {Qt::Key_VolumeUp, QStringLiteral("VolumeUp")}, + {Qt::Key_BassBoost, QStringLiteral("BassBoost")}, + {Qt::Key_BassUp, QStringLiteral("BassUp")}, + {Qt::Key_BassDown, QStringLiteral("BassDown")}, + {Qt::Key_TrebleUp, QStringLiteral("TrebleUp")}, + {Qt::Key_TrebleDown, QStringLiteral("TrebleDown")}, + {Qt::Key_MediaPlay, QStringLiteral("MediaPlay")}, + {Qt::Key_MediaStop, QStringLiteral("MediaStop")}, + {Qt::Key_MediaPrevious, QStringLiteral("MediaPrevious")}, + {Qt::Key_MediaNext, QStringLiteral("MediaNext")}, + {Qt::Key_MediaRecord, QStringLiteral("MediaRecord")}, + {Qt::Key_MediaPause, QStringLiteral("MediaPause")}, + {Qt::Key_MediaTogglePlayPause, QStringLiteral("MediaTogglePlayPause")}, + {Qt::Key_HomePage, QStringLiteral("HomePage")}, + {Qt::Key_Favorites, QStringLiteral("Favorites")}, + {Qt::Key_Search, QStringLiteral("Search")}, + {Qt::Key_Standby, QStringLiteral("Standby")}, + {Qt::Key_OpenUrl, QStringLiteral("OpenUrl")}, + {Qt::Key_LaunchMail, QStringLiteral("LaunchMail")}, + {Qt::Key_LaunchMedia, QStringLiteral("LaunchMedia")}, + {Qt::Key_Launch0, QStringLiteral("Launch0")}, + {Qt::Key_Launch1, QStringLiteral("Launch1")}, + {Qt::Key_Launch2, QStringLiteral("Launch2")}, + {Qt::Key_Launch3, QStringLiteral("Launch3")}, + {Qt::Key_Launch4, QStringLiteral("Launch4")}, + {Qt::Key_Launch5, QStringLiteral("Launch5")}, + {Qt::Key_Launch6, QStringLiteral("Launch6")}, + {Qt::Key_Launch7, QStringLiteral("Launch7")}, + {Qt::Key_Launch8, QStringLiteral("Launch8")}, + {Qt::Key_Launch9, QStringLiteral("Launch9")}, + {Qt::Key_LaunchA, QStringLiteral("LaunchA")}, + {Qt::Key_LaunchB, QStringLiteral("LaunchB")}, + {Qt::Key_LaunchC, QStringLiteral("LaunchC")}, + {Qt::Key_LaunchD, QStringLiteral("LaunchD")}, + {Qt::Key_LaunchE, QStringLiteral("LaunchE")}, + {Qt::Key_LaunchF, QStringLiteral("LaunchF")}, + {Qt::Key_MonBrightnessUp, QStringLiteral("MonBrightnessUp")}, + {Qt::Key_MonBrightnessDown, QStringLiteral("MonBrightnessDown")}, + {Qt::Key_KeyboardLightOnOff, QStringLiteral("KeyboardLightOnOff")}, + {Qt::Key_KeyboardBrightnessUp, QStringLiteral("KeyboardBrightnessUp")}, + {Qt::Key_KeyboardBrightnessDown, QStringLiteral("KeyboardBrightnessDown")}, + {Qt::Key_PowerOff, QStringLiteral("PowerOff")}, + {Qt::Key_WakeUp, QStringLiteral("WakeUp")}, + {Qt::Key_Eject, QStringLiteral("Eject")}, + {Qt::Key_ScreenSaver, QStringLiteral("ScreenSaver")}, + {Qt::Key_WWW, QStringLiteral("WWW")}, + {Qt::Key_Memo, QStringLiteral("Memo")}, + {Qt::Key_LightBulb, QStringLiteral("LightBulb")}, + {Qt::Key_Shop, QStringLiteral("Shop")}, + {Qt::Key_History, QStringLiteral("History")}, + {Qt::Key_AddFavorite, QStringLiteral("AddFavorite")}, + {Qt::Key_HotLinks, QStringLiteral("HotLinks")}, + {Qt::Key_BrightnessAdjust, QStringLiteral("BrightnessAdjust")}, + {Qt::Key_Finance, QStringLiteral("Finance")}, + {Qt::Key_Community, QStringLiteral("Community")}, + {Qt::Key_AudioRewind, QStringLiteral("AudioRewind")}, + {Qt::Key_BackForward, QStringLiteral("BackForward")}, + {Qt::Key_ApplicationLeft, QStringLiteral("ApplicationLeft")}, + {Qt::Key_ApplicationRight, QStringLiteral("ApplicationRight")}, + {Qt::Key_Book, QStringLiteral("Book")}, + {Qt::Key_CD, QStringLiteral("CD")}, + {Qt::Key_Calculator, QStringLiteral("Calculator")}, + {Qt::Key_ToDoList, QStringLiteral("ToDoList")}, + {Qt::Key_ClearGrab, QStringLiteral("ClearGrab")}, + {Qt::Key_Close, QStringLiteral("Close")}, + {Qt::Key_Copy, QStringLiteral("Copy")}, + {Qt::Key_Cut, QStringLiteral("Cut")}, + {Qt::Key_Display, QStringLiteral("Display")}, + {Qt::Key_DOS, QStringLiteral("DOS")}, + {Qt::Key_Documents, QStringLiteral("Documents")}, + {Qt::Key_Excel, QStringLiteral("Excel")}, + {Qt::Key_Explorer, QStringLiteral("Explorer")}, + {Qt::Key_Game, QStringLiteral("Game")}, + {Qt::Key_Go, QStringLiteral("Go")}, + {Qt::Key_iTouch, QStringLiteral("iTouch")}, + {Qt::Key_LogOff, QStringLiteral("LogOff")}, + {Qt::Key_Market, QStringLiteral("Market")}, + {Qt::Key_Meeting, QStringLiteral("Meeting")}, + {Qt::Key_MenuKB, QStringLiteral("MenuKB")}, + {Qt::Key_MenuPB, QStringLiteral("MenuPB")}, + {Qt::Key_MySites, QStringLiteral("MySites")}, + {Qt::Key_News, QStringLiteral("News")}, + {Qt::Key_OfficeHome, QStringLiteral("OfficeHome")}, + {Qt::Key_Option, QStringLiteral("Option")}, + {Qt::Key_Paste, QStringLiteral("Paste")}, + {Qt::Key_Phone, QStringLiteral("Phone")}, + {Qt::Key_Calendar, QStringLiteral("Calendar")}, + {Qt::Key_Reply, QStringLiteral("Reply")}, + {Qt::Key_Reload, QStringLiteral("Reload")}, + {Qt::Key_RotateWindows, QStringLiteral("RotateWindows")}, + {Qt::Key_RotationPB, QStringLiteral("RotationPB")}, + {Qt::Key_RotationKB, QStringLiteral("RotationKB")}, + {Qt::Key_Save, QStringLiteral("Save")}, + {Qt::Key_Send, QStringLiteral("Send")}, + {Qt::Key_Spell, QStringLiteral("Spell")}, + {Qt::Key_SplitScreen, QStringLiteral("SplitScreen")}, + {Qt::Key_Support, QStringLiteral("Support")}, + {Qt::Key_TaskPane, QStringLiteral("TaskPane")}, + {Qt::Key_Terminal, QStringLiteral("Terminal")}, + {Qt::Key_Tools, QStringLiteral("Tools")}, + {Qt::Key_Travel, QStringLiteral("Travel")}, + {Qt::Key_Video, QStringLiteral("Video")}, + {Qt::Key_Word, QStringLiteral("Word")}, + {Qt::Key_Xfer, QStringLiteral("Xfer")}, + {Qt::Key_ZoomIn, QStringLiteral("ZoomIn")}, + {Qt::Key_ZoomOut, QStringLiteral("ZoomOut")}, + {Qt::Key_Away, QStringLiteral("Away")}, + {Qt::Key_Messenger, QStringLiteral("Messenger")}, + {Qt::Key_WebCam, QStringLiteral("WebCam")}, + {Qt::Key_MailForward, QStringLiteral("MailForward")}, + {Qt::Key_Pictures, QStringLiteral("Pictures")}, + {Qt::Key_Music, QStringLiteral("Music")}, + {Qt::Key_Battery, QStringLiteral("Battery")}, + {Qt::Key_Bluetooth, QStringLiteral("Bluetooth")}, + {Qt::Key_WLAN, QStringLiteral("WLAN")}, + {Qt::Key_UWB, QStringLiteral("UWB")}, + {Qt::Key_AudioForward, QStringLiteral("AudioForward")}, + {Qt::Key_AudioRepeat, QStringLiteral("AudioRepeat")}, + {Qt::Key_AudioRandomPlay, QStringLiteral("AudioRandomPlay")}, + {Qt::Key_Subtitle, QStringLiteral("Subtitle")}, + {Qt::Key_AudioCycleTrack, QStringLiteral("AudioCycleTrack")}, + {Qt::Key_Time, QStringLiteral("Time")}, + {Qt::Key_Hibernate, QStringLiteral("Hibernate")}, + {Qt::Key_View, QStringLiteral("View")}, + {Qt::Key_TopMenu, QStringLiteral("TopMenu")}, + {Qt::Key_PowerDown, QStringLiteral("PowerDown")}, + {Qt::Key_Suspend, QStringLiteral("Suspend")}, + {Qt::Key_ContrastAdjust, QStringLiteral("ContrastAdjust")}, + {Qt::Key_LaunchG, QStringLiteral("LaunchG")}, + {Qt::Key_LaunchH, QStringLiteral("LaunchH")}, + {Qt::Key_TouchpadToggle, QStringLiteral("TouchpadToggle")}, + {Qt::Key_TouchpadOn, QStringLiteral("TouchpadOn")}, + {Qt::Key_TouchpadOff, QStringLiteral("TouchpadOff")}, + {Qt::Key_MicMute, QStringLiteral("MicMute")}, + {Qt::Key_Red, QStringLiteral("Red")}, + {Qt::Key_Green, QStringLiteral("Green")}, + {Qt::Key_Yellow, QStringLiteral("Yellow")}, + {Qt::Key_Blue, QStringLiteral("Blue")}, + {Qt::Key_ChannelUp, QStringLiteral("ChannelUp")}, + {Qt::Key_ChannelDown, QStringLiteral("ChannelDown")}, + {Qt::Key_Guide, QStringLiteral("Guide")}, + {Qt::Key_Info, QStringLiteral("Info")}, + {Qt::Key_Settings, QStringLiteral("Settings")}, + {Qt::Key_MicVolumeUp, QStringLiteral("MicVolumeUp")}, + {Qt::Key_MicVolumeDown, QStringLiteral("MicVolumeDown")}, + {Qt::Key_New, QStringLiteral("New")}, + {Qt::Key_Open, QStringLiteral("Open")}, + {Qt::Key_Find, QStringLiteral("Find")}, + {Qt::Key_Undo, QStringLiteral("Undo")}, + {Qt::Key_Redo, QStringLiteral("Redo")}, + {Qt::Key_MediaLast, QStringLiteral("MediaLast")}, + {Qt::Key_Select, QStringLiteral("Select")}, + {Qt::Key_Yes, QStringLiteral("Yes")}, + {Qt::Key_No, QStringLiteral("No")}, + {Qt::Key_Cancel, QStringLiteral("Cancel")}, + {Qt::Key_Printer, QStringLiteral("Printer")}, + {Qt::Key_Execute, QStringLiteral("Execute")}, + {Qt::Key_Sleep, QStringLiteral("Sleep")}, + {Qt::Key_Play, QStringLiteral("Play")}, + {Qt::Key_Zoom, QStringLiteral("Zoom")}, + {Qt::Key_Exit, QStringLiteral("Exit")}, + {Qt::Key_Context1, QStringLiteral("Context1")}, + {Qt::Key_Context2, QStringLiteral("Context2")}, + {Qt::Key_Context3, QStringLiteral("Context3")}, + {Qt::Key_Context4, QStringLiteral("Context4")}, + {Qt::Key_Call, QStringLiteral("Call")}, + {Qt::Key_Hangup, QStringLiteral("Hangup")}, + {Qt::Key_Flip, QStringLiteral("Flip")}, + {Qt::Key_ToggleCallHangup, QStringLiteral("ToggleCallHangup")}, + {Qt::Key_VoiceDial, QStringLiteral("VoiceDial")}, + {Qt::Key_LastNumberRedial, QStringLiteral("LastNumberRedial")}, + {Qt::Key_Camera, QStringLiteral("Camera")}, + {Qt::Key_CameraFocus, QStringLiteral("CameraFocus")}}; + + struct QtKeyModifierEntry + { + Qt::KeyboardModifier mod; + Qt::Key key; + QString name; + }; + + static const std::array s_qt_key_modifiers = { + {{Qt::ShiftModifier, Qt::Key_Shift, QStringLiteral("Shift")}, + {Qt::ControlModifier, Qt::Key_Control, QStringLiteral("Control")}, + {Qt::AltModifier, Qt::Key_Alt, QStringLiteral("Alt")}, + {Qt::MetaModifier, Qt::Key_Meta, QStringLiteral("Meta")}, + {Qt::KeypadModifier, static_cast(0), QStringLiteral("Keypad")}}}; + + QString GetKeyIdentifier(int key) + { + const auto it = s_qt_key_names.find(key); + return it == s_qt_key_names.end() ? QString() : it->second; + } + + std::optional GetKeyIdForIdentifier(const QString& key_identifier) + { + for (const auto& it : s_qt_key_names) + { + if (it.second == key_identifier) + return it.first; + } + + return std::nullopt; + } + + QString KeyEventToString(int key, Qt::KeyboardModifiers mods) + { + QString key_name = GetKeyIdentifier(key); + if (key_name.isEmpty()) + return {}; + + QString ret; + for (const QtKeyModifierEntry& mod : s_qt_key_modifiers) + { + if (mods & mod.mod && key != mod.key) + { + ret.append(mod.name); + ret.append('+'); + } + } + + ret.append(key_name); + return ret; + } + + std::optional ParseKeyString(const QString& key_str) + { + const QStringList sections = key_str.split('+'); + std::optional key_id = GetKeyIdForIdentifier(sections.last()); + if (!key_id) + return std::nullopt; + + int ret = key_id.value(); + + if (sections.size() > 1) + { + const int num_modifiers = sections.size() - 1; + for (int i = 0; i < num_modifiers; i++) + { + for (const QtKeyModifierEntry& mod : s_qt_key_modifiers) + { + if (sections[i] == mod.name) + { + ret |= static_cast(mod.mod); + break; + } + } + } + } + + return ret; + } + + int KeyEventToInt(int key, Qt::KeyboardModifiers mods) + { + int val = key; + if (mods != 0) + { + for (const QtKeyModifierEntry& mod : s_qt_key_modifiers) + { + if (mods & mod.mod && key != mod.key) + val |= static_cast(mod.mod); + } + } + + return val; + } + + void OpenURL(QWidget* parent, const QUrl& qurl) + { + if (!QDesktopServices::openUrl(qurl)) + { + QMessageBox::critical(parent, QObject::tr("Failed to open URL"), + QObject::tr("Failed to open URL.\n\nThe URL was: %1").arg(qurl.toString())); + } + } + + void OpenURL(QWidget* parent, const char* url) + { + return OpenURL(parent, QUrl::fromEncoded(QByteArray(url, static_cast(std::strlen(url))))); + } + + void OpenURL(QWidget* parent, const QString& url) + { + return OpenURL(parent, QUrl(url)); + } + + wxString QStringToWxString(const QString& str) + { + return wxString(str.toStdWString()); + } + + QString WxStringToQString(const wxString& str) + { + return QString::fromStdWString(str.ToStdWstring()); + } + +} // namespace QtUtils diff --git a/pcsx2-qt/QtUtils.h b/pcsx2-qt/QtUtils.h new file mode 100644 index 0000000000..668eca5fbf --- /dev/null +++ b/pcsx2-qt/QtUtils.h @@ -0,0 +1,85 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#pragma once +#include +#include +#include +#include +#include +#include + +Q_DECLARE_METATYPE(std::optional); +Q_DECLARE_METATYPE(std::function); + +class ByteStream; + +class QAction; +class QComboBox; +class QFrame; +class QKeyEvent; +class QTableView; +class QTreeView; +class QVariant; +class QWidget; +class QUrl; + +// TODO: Get rid of wx interoperability later on. +#include + +namespace QtUtils +{ + /// Marks an action as the "default" - i.e. makes the text bold. + void MarkActionAsDefault(QAction* action); + + /// Creates a horizontal line widget. + QFrame* CreateHorizontalLine(QWidget* parent); + + /// Returns the greatest parent of a widget, i.e. its dialog/window. + QWidget* GetRootWidget(QWidget* widget, bool stop_at_window_or_dialog = true); + + /// Resizes columns of the table view to at the specified widths. A negative width will stretch the column to use the + /// remaining space. + void ResizeColumnsForTableView(QTableView* view, const std::initializer_list& widths); + void ResizeColumnsForTreeView(QTreeView* view, const std::initializer_list& widths); + + /// Returns a string identifier for a Qt key ID. + QString GetKeyIdentifier(int key); + + /// Returns the integer Qt key ID for an identifier. + std::optional GetKeyIdForIdentifier(const QString& key_identifier); + + /// Stringizes a key event. + QString KeyEventToString(int key, Qt::KeyboardModifiers mods); + + /// Returns an integer id for a stringized key event. Modifiers are in the upper bits. + std::optional ParseKeyString(const QString& key_str); + + /// Returns a key id for a key event, including any modifiers. + int KeyEventToInt(int key, Qt::KeyboardModifiers mods); + + /// Opens a URL with the default handler. + void OpenURL(QWidget* parent, const QUrl& qurl); + + /// Opens a URL string with the default handler. + void OpenURL(QWidget* parent, const char* url); + + /// Opens a URL string with the default handler. + void OpenURL(QWidget* parent, const QString& url); + + // TODO: Get rid of wx interoperability later on. + wxString QStringToWxString(const QString& str); + QString WxStringToQString(const wxString& str); +} // namespace QtUtils \ No newline at end of file diff --git a/pcsx2-qt/SettingWidgetBinder.h b/pcsx2-qt/SettingWidgetBinder.h new file mode 100644 index 0000000000..f64621faca --- /dev/null +++ b/pcsx2-qt/SettingWidgetBinder.h @@ -0,0 +1,417 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#pragma once +#include +#include +#include + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +#include +#else +#include +#endif + +#include +#include +#include +#include +#include +#include + +#include "EmuThread.h" +#include "QtHost.h" + +namespace SettingWidgetBinder +{ + template + struct SettingAccessor + { + static bool getBoolValue(const T* widget); + static void setBoolValue(T* widget, bool value); + + static int getIntValue(const T* widget); + static void setIntValue(T* widget, int value); + + static int getFloatValue(const T* widget); + static void setFloatValue(T* widget, int value); + + static QString getStringValue(const T* widget); + static void setStringValue(T* widget, const QString& value); + + template + static void connectValueChanged(T* widget, F func); + }; + + template <> + struct SettingAccessor + { + static bool getBoolValue(const QLineEdit* widget) { return widget->text().toInt() != 0; } + static void setBoolValue(QLineEdit* widget, bool value) + { + widget->setText(value ? QStringLiteral("1") : QStringLiteral("0")); + } + + static int getIntValue(const QLineEdit* widget) { return widget->text().toInt(); } + static void setIntValue(QLineEdit* widget, int value) { widget->setText(QString::number(value)); } + + static float getFloatValue(const QLineEdit* widget) { return widget->text().toFloat(); } + static void setFloatValue(QLineEdit* widget, float value) { widget->setText(QString::number(value)); } + + static QString getStringValue(const QLineEdit* widget) { return widget->text(); } + static void setStringValue(QLineEdit* widget, const QString& value) { widget->setText(value); } + + template + static void connectValueChanged(QLineEdit* widget, F func) + { + widget->connect(widget, &QLineEdit::textChanged, func); + } + }; + + template <> + struct SettingAccessor + { + static bool getBoolValue(const QComboBox* widget) { return widget->currentIndex() > 0; } + static void setBoolValue(QComboBox* widget, bool value) { widget->setCurrentIndex(value ? 1 : 0); } + + static int getIntValue(const QComboBox* widget) { return widget->currentIndex(); } + static void setIntValue(QComboBox* widget, int value) { widget->setCurrentIndex(value); } + + static float getFloatValue(const QComboBox* widget) { return static_cast(widget->currentIndex()); } + static void setFloatValue(QComboBox* widget, float value) { widget->setCurrentIndex(static_cast(value)); } + + static QString getStringValue(const QComboBox* widget) + { + const QVariant currentData(widget->currentData()); +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + if (currentData.type() == QVariant::String) + return currentData.toString(); +#else + if (currentData.metaType().id() == QMetaType::QString) + return currentData.toString(); +#endif + + return widget->currentText(); + } + + static void setStringValue(QComboBox* widget, const QString& value) + { + const int index = widget->findData(value); + if (index >= 0) + { + widget->setCurrentIndex(index); + return; + } + + widget->setCurrentText(value); + } + + template + static void connectValueChanged(QComboBox* widget, F func) + { + widget->connect(widget, static_cast(&QComboBox::currentIndexChanged), func); + } + }; + + template <> + struct SettingAccessor + { + static bool getBoolValue(const QCheckBox* widget) { return widget->isChecked(); } + static void setBoolValue(QCheckBox* widget, bool value) { widget->setChecked(value); } + + static int getIntValue(const QCheckBox* widget) { return widget->isChecked() ? 1 : 0; } + static void setIntValue(QCheckBox* widget, int value) { widget->setChecked(value != 0); } + + static float getFloatValue(const QCheckBox* widget) { return widget->isChecked() ? 1.0f : 0.0f; } + static void setFloatValue(QCheckBox* widget, float value) { widget->setChecked(value != 0.0f); } + + static QString getStringValue(const QCheckBox* widget) + { + return widget->isChecked() ? QStringLiteral("1") : QStringLiteral("0"); + } + static void setStringValue(QCheckBox* widget, const QString& value) { widget->setChecked(value.toInt() != 0); } + + template + static void connectValueChanged(QCheckBox* widget, F func) + { + widget->connect(widget, &QCheckBox::stateChanged, func); + } + }; + + template <> + struct SettingAccessor + { + static bool getBoolValue(const QSlider* widget) { return widget->value() > 0; } + static void setBoolValue(QSlider* widget, bool value) { widget->setValue(value ? 1 : 0); } + + static int getIntValue(const QSlider* widget) { return widget->value(); } + static void setIntValue(QSlider* widget, int value) { widget->setValue(value); } + + static float getFloatValue(const QSlider* widget) { return static_cast(widget->value()); } + static void setFloatValue(QSlider* widget, float value) { widget->setValue(static_cast(value)); } + + static QString getStringValue(const QSlider* widget) { return QString::number(widget->value()); } + static void setStringValue(QSlider* widget, const QString& value) { widget->setValue(value.toInt()); } + + template + static void connectValueChanged(QSlider* widget, F func) + { + widget->connect(widget, &QSlider::valueChanged, func); + } + }; + + template <> + struct SettingAccessor + { + static bool getBoolValue(const QSpinBox* widget) { return widget->value() > 0; } + static void setBoolValue(QSpinBox* widget, bool value) { widget->setValue(value ? 1 : 0); } + + static int getIntValue(const QSpinBox* widget) { return widget->value(); } + static void setIntValue(QSpinBox* widget, int value) { widget->setValue(value); } + + static float getFloatValue(const QSpinBox* widget) { return static_cast(widget->value()); } + static void setFloatValue(QSpinBox* widget, float value) { widget->setValue(static_cast(value)); } + + static QString getStringValue(const QSpinBox* widget) { return QString::number(widget->value()); } + static void setStringValue(QSpinBox* widget, const QString& value) { widget->setValue(value.toInt()); } + + template + static void connectValueChanged(QSpinBox* widget, F func) + { + widget->connect(widget, QOverload::of(&QSpinBox::valueChanged), func); + } + }; + + template <> + struct SettingAccessor + { + static bool getBoolValue(const QDoubleSpinBox* widget) { return widget->value() > 0.0; } + static void setBoolValue(QDoubleSpinBox* widget, bool value) { widget->setValue(value ? 1.0 : 0.0); } + + static int getIntValue(const QDoubleSpinBox* widget) { return static_cast(widget->value()); } + static void setIntValue(QDoubleSpinBox* widget, int value) { widget->setValue(static_cast(value)); } + + static float getFloatValue(const QDoubleSpinBox* widget) { return static_cast(widget->value()); } + static void setFloatValue(QDoubleSpinBox* widget, float value) { widget->setValue(static_cast(value)); } + + static QString getStringValue(const QDoubleSpinBox* widget) { return QString::number(widget->value()); } + static void setStringValue(QDoubleSpinBox* widget, const QString& value) { widget->setValue(value.toDouble()); } + + template + static void connectValueChanged(QDoubleSpinBox* widget, F func) + { + widget->connect(widget, QOverload::of(&QDoubleSpinBox::valueChanged), func); + } + }; + + template <> + struct SettingAccessor + { + static bool getBoolValue(const QAction* widget) { return widget->isChecked(); } + static void setBoolValue(QAction* widget, bool value) { widget->setChecked(value); } + + static int getIntValue(const QAction* widget) { return widget->isChecked() ? 1 : 0; } + static void setIntValue(QAction* widget, int value) { widget->setChecked(value != 0); } + + static float getFloatValue(const QAction* widget) { return widget->isChecked() ? 1.0f : 0.0f; } + static void setFloatValue(QAction* widget, float value) { widget->setChecked(value != 0.0f); } + + static QString getStringValue(const QAction* widget) + { + return widget->isChecked() ? QStringLiteral("1") : QStringLiteral("0"); + } + static void setStringValue(QAction* widget, const QString& value) { widget->setChecked(value.toInt() != 0); } + + template + static void connectValueChanged(QAction* widget, F func) + { + widget->connect(widget, &QAction::toggled, func); + } + }; + + /// Binds a widget's value to a setting, updating it when the value changes. + + template + static void BindWidgetToBoolSetting(WidgetType* widget, std::string section, std::string key, bool default_value) + { + using Accessor = SettingAccessor; + + bool value = QtHost::GetBaseBoolSettingValue(section.c_str(), key.c_str(), default_value); + + Accessor::setBoolValue(widget, value); + + Accessor::connectValueChanged(widget, [widget, section, key]() { + const bool new_value = Accessor::getBoolValue(widget); + QtHost::SetBaseBoolSettingValue(section.c_str(), key.c_str(), new_value); + g_emu_thread->applySettings(); + }); + } + + template + static void BindWidgetToIntSetting(WidgetType* widget, std::string section, std::string key, int default_value, int option_offset = 0) + { + using Accessor = SettingAccessor; + + s32 value = QtHost::GetBaseIntSettingValue(section.c_str(), key.c_str(), static_cast(default_value)) - option_offset; + + Accessor::setIntValue(widget, static_cast(value)); + + Accessor::connectValueChanged(widget, [widget, section, key, option_offset]() { + const int new_value = Accessor::getIntValue(widget); + QtHost::SetBaseIntSettingValue(section.c_str(), key.c_str(), new_value + option_offset); + g_emu_thread->applySettings(); + }); + } + + template + static void BindWidgetToFloatSetting(WidgetType* widget, std::string section, std::string key, float default_value) + { + using Accessor = SettingAccessor; + + float value = QtHost::GetBaseFloatSettingValue(section.c_str(), key.c_str(), default_value); + + Accessor::setFloatValue(widget, value); + + Accessor::connectValueChanged(widget, [widget, section, key]() { + const float new_value = Accessor::getFloatValue(widget); + QtHost::SetBaseFloatSettingValue(section.c_str(), key.c_str(), new_value); + g_emu_thread->applySettings(); + }); + } + + template + static void BindWidgetToNormalizedSetting(WidgetType* widget, std::string section, std::string key, float range, + float default_value) + { + using Accessor = SettingAccessor; + + float value = QtHost::GetBaseFloatSettingValue(section.c_str(), key.c_str(), default_value); + + Accessor::setIntValue(widget, static_cast(value * range)); + + Accessor::connectValueChanged(widget, [widget, section, key, range]() { + const float new_value = (static_cast(Accessor::getIntValue(widget)) / range); + QtHost::SetBaseFloatSettingValue(section.c_str(), key.c_str(), new_value); + g_emu_thread->applySettings(); + }); + } + + template + static void BindWidgetToStringSetting(WidgetType* widget, std::string section, std::string key, + std::string default_value = std::string()) + { + using Accessor = SettingAccessor; + + std::string value = QtHost::GetBaseStringSettingValue(section.c_str(), key.c_str(), default_value.c_str()); + + Accessor::setStringValue(widget, QString::fromStdString(value)); + + Accessor::connectValueChanged(widget, [widget, section, key]() { + const QString new_value = Accessor::getStringValue(widget); + if (!new_value.isEmpty()) + QtHost::SetBaseStringSettingValue(section.c_str(), key.c_str(), new_value.toUtf8().constData()); + else + QtHost::RemoveBaseSettingValue(section.c_str(), key.c_str()); + + g_emu_thread->applySettings(); + }); + } + + template + static void BindWidgetToEnumSetting(WidgetType* widget, std::string section, std::string key, + std::optional (*from_string_function)(const char* str), + const char* (*to_string_function)(DataType value), DataType default_value) + { + using Accessor = SettingAccessor; + using UnderlyingType = std::underlying_type_t; + + // TODO: Clean this up? + const std::string old_setting_string_value = + QtHost::GetBaseStringSettingValue(section.c_str(), key.c_str(), to_string_function(default_value)); + const std::optional old_setting_value = from_string_function(old_setting_string_value.c_str()); + if (old_setting_value.has_value()) + Accessor::setIntValue(widget, static_cast(static_cast(old_setting_value.value()))); + else + Accessor::setIntValue(widget, static_cast(static_cast(default_value))); + + Accessor::connectValueChanged(widget, [widget, section, key, to_string_function]() { + const DataType value = static_cast(static_cast(Accessor::getIntValue(widget))); + const char* string_value = to_string_function(value); + QtHost::SetBaseStringSettingValue(section.c_str(), key.c_str(), string_value); + g_emu_thread->applySettings(); + }); + } + + template + static void BindWidgetToEnumSetting(WidgetType* widget, std::string section, std::string key, const char** enum_names, + DataType default_value) + { + using Accessor = SettingAccessor; + using UnderlyingType = std::underlying_type_t; + + const std::string old_setting_string_value = QtHost::GetBaseStringSettingValue( + section.c_str(), key.c_str(), enum_names[static_cast(default_value)]); + + UnderlyingType enum_index = static_cast(default_value); + for (UnderlyingType i = 0; enum_names[i] != nullptr; i++) + { + if (old_setting_string_value == enum_names[i]) + { + enum_index = i; + break; + } + } + Accessor::setIntValue(widget, static_cast(enum_index)); + + Accessor::connectValueChanged(widget, [widget, section, key, enum_names]() { + const UnderlyingType value = static_cast(Accessor::getIntValue(widget)); + const char* string_value = enum_names[value]; + QtHost::SetBaseStringSettingValue(section.c_str(), key.c_str(), string_value); + g_emu_thread->applySettings(); + }); + } + + template + static void BindWidgetToEnumSetting(WidgetType* widget, std::string section, std::string key, const char** enum_names, + const char** enum_values, const char* default_value) + { + using Accessor = SettingAccessor; + + const std::string old_setting_string_value = + QtHost::GetBaseStringSettingValue(section.c_str(), key.c_str(), default_value); + + for (int i = 0; enum_names[i] != nullptr; i++) + widget->addItem(QString::fromUtf8(enum_names[i])); + + int enum_index = -1; + for (int i = 0; enum_values[i] != nullptr; i++) + { + if (old_setting_string_value == enum_values[i]) + { + enum_index = i; + break; + } + } + if (enum_index >= 0) + Accessor::setIntValue(widget, enum_index); + + Accessor::connectValueChanged(widget, [widget, section, key, enum_values]() { + const int value = Accessor::getIntValue(widget); + QtHost::SetBaseStringSettingValue(section.c_str(), key.c_str(), enum_values[value]); + g_emu_thread->applySettings(); + }); + } + +} // namespace SettingWidgetBinder diff --git a/pcsx2-qt/Settings/AdvancedSystemSettingsWidget.cpp b/pcsx2-qt/Settings/AdvancedSystemSettingsWidget.cpp new file mode 100644 index 0000000000..65b11ab88c --- /dev/null +++ b/pcsx2-qt/Settings/AdvancedSystemSettingsWidget.cpp @@ -0,0 +1,50 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" + +#include +#include + +#include "AdvancedSystemSettingsWidget.h" +#include "EmuThread.h" +#include "QtUtils.h" +#include "SettingWidgetBinder.h" +#include "SettingsDialog.h" + +AdvancedSystemSettingsWidget::AdvancedSystemSettingsWidget(QWidget* parent, SettingsDialog* dialog) + : QWidget(parent) +{ + m_ui.setupUi(this); + + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.eeRecompiler, "EmuCore/CPU/Recompiler", "EnableEE", true); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.eeCache, "EmuCore/CPU/Recompiler", "EnableEECache", false); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.eeINTCSpinDetection, "EmuCore/Speedhacks", "IntcStat", true); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.eeWaitLoopDetection, "EmuCore/Speedhacks", "WaitLoop", true); + + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.vu0Recompiler, "EmuCore/CPU/Recompiler", "EnableVU0", true); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.vu1Recompiler, "EmuCore/CPU/Recompiler", "EnableVU1", true); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.vuFlagHack, "EmuCore/Speedhacks", "vuFlagHack", true); + + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.iopRecompiler, "EmuCore/CPU/Recompiler", "EnableIOP", true); + + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.gameFixes, "", "EnableGameFixes", true); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.patches, "EmuCore", "EnablePatches", true); + + SettingWidgetBinder::BindWidgetToFloatSetting(m_ui.ntscFrameRate, "EmuCore/GS", "FramerateNTSC", 59.94f); + SettingWidgetBinder::BindWidgetToFloatSetting(m_ui.palFrameRate, "EmuCore/GS", "FrameratePAL", 50.00f); +} + +AdvancedSystemSettingsWidget::~AdvancedSystemSettingsWidget() = default; diff --git a/pcsx2-qt/Settings/AdvancedSystemSettingsWidget.h b/pcsx2-qt/Settings/AdvancedSystemSettingsWidget.h new file mode 100644 index 0000000000..6eacbf81c6 --- /dev/null +++ b/pcsx2-qt/Settings/AdvancedSystemSettingsWidget.h @@ -0,0 +1,34 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#pragma once + +#include + +#include "ui_AdvancedSystemSettingsWidget.h" + +class SettingsDialog; + +class AdvancedSystemSettingsWidget : public QWidget +{ + Q_OBJECT + +public: + AdvancedSystemSettingsWidget(QWidget* parent, SettingsDialog* dialog); + ~AdvancedSystemSettingsWidget(); + +private: + Ui::AdvancedSystemSettingsWidget m_ui; +}; diff --git a/pcsx2-qt/Settings/AdvancedSystemSettingsWidget.ui b/pcsx2-qt/Settings/AdvancedSystemSettingsWidget.ui new file mode 100644 index 0000000000..665a3c6e47 --- /dev/null +++ b/pcsx2-qt/Settings/AdvancedSystemSettingsWidget.ui @@ -0,0 +1,197 @@ + + + AdvancedSystemSettingsWidget + + + + 0 + 0 + 648 + 481 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + EmotionEngine (MIPS-IV) + + + + + + Enable Recompiler + + + + + + + Wait Loop Detection + + + + + + + Enable Cache (Slow) + + + + + + + INTC Spin Detection + + + + + + + + + + Vector Units (VU) + + + + + + mVU Flag Hack + + + + + + + Enable VU0 Recompiler (Micro Mode) + + + + + + + Enable VU1 Recompiler + + + + + + + + + + I/O Processor (IOP, MIPS-I) + + + + + + Enable Recompiler + + + + + + + + + + Game Settings + + + + + + Enable Game Fixes + + + + + + + Enable Compatibility Patches + + + + + + + + + + Frame Rate Control + + + + + + hz + + + 0.010000000000000 + + + + + + + hz + + + 0.010000000000000 + + + + + + + PAL Frame Rate: + + + + + + + NTSC Frame Rate: + + + + + + + + + + Qt::Vertical + + + + 20 + 3 + + + + + + + + + + + diff --git a/pcsx2-qt/Settings/BIOSSettingsWidget.cpp b/pcsx2-qt/Settings/BIOSSettingsWidget.cpp new file mode 100644 index 0000000000..f566e82ca1 --- /dev/null +++ b/pcsx2-qt/Settings/BIOSSettingsWidget.cpp @@ -0,0 +1,179 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" + +#include +#include +#include + +#include "pcsx2/ps2/BiosTools.h" + +#include "BIOSSettingsWidget.h" +#include "QtUtils.h" +#include "SettingWidgetBinder.h" +#include "SettingsDialog.h" + +BIOSSettingsWidget::BIOSSettingsWidget(QWidget* parent, SettingsDialog* dialog) + : QWidget(parent) +{ + m_ui.setupUi(this); + + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.fastBoot, "EmuCore", "EnableFastBoot", true); + + dialog->registerWidgetHelp(m_ui.fastBoot, tr("Fast Boot"), tr("Unchecked"), + tr("Patches the BIOS to skip the console's boot animation.")); + + updateSearchDirectory(); + refreshList(); + + connect(m_ui.searchDirectory, &QLineEdit::textChanged, [this](const QString& text) { + QtHost::SetBaseStringSettingValue("Folders", "Bios", text.toUtf8().constData()); + QtHost::UpdateFolders(); + refreshList(); + }); + connect(m_ui.resetSearchDirectory, &QPushButton::clicked, [this]() { + QtHost::RemoveBaseSettingValue("Folders", "Bios"); + QtHost::UpdateFolders(); + updateSearchDirectory(); + refreshList(); + }); + connect(m_ui.browseSearchDirectory, &QPushButton::clicked, this, &BIOSSettingsWidget::browseSearchDirectory); + connect(m_ui.openSearchDirectory, &QPushButton::clicked, this, &BIOSSettingsWidget::openSearchDirectory); + connect(m_ui.refresh, &QPushButton::clicked, this, &BIOSSettingsWidget::refreshList); + connect(m_ui.fileList, &QTreeWidget::currentItemChanged, this, &BIOSSettingsWidget::listItemChanged); +} + +BIOSSettingsWidget::~BIOSSettingsWidget() +{ + if (m_refresh_thread) + m_refresh_thread->wait(); +} + +void BIOSSettingsWidget::refreshList() +{ + if (m_refresh_thread) + { + m_refresh_thread->wait(); + delete m_refresh_thread; + } + + QSignalBlocker blocker(m_ui.fileList); + m_ui.fileList->clear(); + m_ui.fileList->setEnabled(false); + + m_refresh_thread = new RefreshThread(this, m_ui.searchDirectory->text()); + m_refresh_thread->start(); +} + +void BIOSSettingsWidget::browseSearchDirectory() +{ + QString directory = QDir::toNativeSeparators(QFileDialog::getExistingDirectory( + QtUtils::GetRootWidget(this), tr("Select Directory"), m_ui.searchDirectory->text())); + if (directory.isEmpty()) + return; + + m_ui.searchDirectory->setText(directory); +} + +void BIOSSettingsWidget::openSearchDirectory() +{ + QtUtils::OpenURL(this, QUrl::fromLocalFile(m_ui.searchDirectory->text())); +} + +void BIOSSettingsWidget::updateSearchDirectory() +{ + // this will generate a full path + m_ui.searchDirectory->setText(QtUtils::WxStringToQString(EmuFolders::Bios.ToString())); +} + +void BIOSSettingsWidget::listRefreshed(const QVector& items) +{ + const std::string selected_bios(QtHost::GetBaseStringSettingValue("Filenames", "BIOS")); + + QSignalBlocker sb(m_ui.fileList); + for (const BIOSInfo& bi : items) + { + QTreeWidgetItem* item = new QTreeWidgetItem(); + item->setText(0, QString::fromStdString(bi.filename)); + item->setText(1, QString::fromStdString(bi.description)); + + switch (bi.region) + { + case 2: // Japan + item->setIcon(0, QIcon(QStringLiteral(":/icons/flag-jp.png"))); + break; + + case 3: // USA + item->setIcon(0, QIcon(QStringLiteral(":/icons/flag-us.png"))); + break; + + case 4: // Europe + item->setIcon(0, QIcon(QStringLiteral(":/icons/flag-eu.png"))); + break; + + case 5: // HK + case 6: // Free + case 7: // China + case 0: // T10K + case 1: // Test + default: + item->setIcon(0, QIcon(QStringLiteral(":/icons/flag-jp.png"))); + break; + } + + m_ui.fileList->addTopLevelItem(item); + + if (bi.filename == selected_bios) + item->setSelected(true); + } + m_ui.fileList->setEnabled(true); +} + +void BIOSSettingsWidget::listItemChanged(const QTreeWidgetItem* current, const QTreeWidgetItem* previous) +{ + QtHost::SetBaseStringSettingValue("Filenames", "BIOS", current->text(0).toUtf8().constData()); +} + +BIOSSettingsWidget::RefreshThread::RefreshThread(BIOSSettingsWidget* parent, const QString& directory) + : QThread(parent) + , m_parent(parent) + , m_directory(directory) +{ +} + +BIOSSettingsWidget::RefreshThread::~RefreshThread() = default; + +void BIOSSettingsWidget::RefreshThread::run() +{ + QVector items; + + QDir dir(m_directory); + if (dir.exists()) + { + for (const QFileInfo& info : dir.entryInfoList(QDir::Files)) + { + BIOSInfo bi; + QString full_path(info.absoluteFilePath()); + if (!IsBIOS(full_path.toUtf8().constData(), bi.version, bi.description, bi.region, bi.zone)) + continue; + + bi.filename = info.fileName().toStdString(); + items.push_back(std::move(bi)); + } + } + + QMetaObject::invokeMethod(m_parent, "listRefreshed", Qt::QueuedConnection, Q_ARG(const QVector&, items)); +} diff --git a/pcsx2-qt/Settings/BIOSSettingsWidget.h b/pcsx2-qt/Settings/BIOSSettingsWidget.h new file mode 100644 index 0000000000..a76d2321e9 --- /dev/null +++ b/pcsx2-qt/Settings/BIOSSettingsWidget.h @@ -0,0 +1,75 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#pragma once +#include +#include +#include +#include +#include +#include + +#include "ui_BIOSSettingsWidget.h" + +class SettingsDialog; +class QThread; + +// TODO: Move to core. +struct BIOSInfo +{ + std::string filename; + std::string description; + std::string zone; + u32 version; + u32 region; +}; +Q_DECLARE_METATYPE(BIOSInfo); + +class BIOSSettingsWidget : public QWidget +{ + Q_OBJECT + +public: + BIOSSettingsWidget(QWidget* parent, SettingsDialog* dialog); + ~BIOSSettingsWidget(); + +private Q_SLOTS: + void refreshList(); + void browseSearchDirectory(); + void openSearchDirectory(); + void updateSearchDirectory(); + + void listItemChanged(const QTreeWidgetItem* current, const QTreeWidgetItem* previous); + void listRefreshed(const QVector& items); + +private: + Ui::BIOSSettingsWidget m_ui; + + class RefreshThread final : public QThread + { + public: + RefreshThread(BIOSSettingsWidget* parent, const QString& directory); + ~RefreshThread(); + + protected: + void run() override; + + private: + BIOSSettingsWidget* m_parent; + QString m_directory; + }; + + RefreshThread* m_refresh_thread = nullptr; +}; diff --git a/pcsx2-qt/Settings/BIOSSettingsWidget.ui b/pcsx2-qt/Settings/BIOSSettingsWidget.ui new file mode 100644 index 0000000000..c51b3cf939 --- /dev/null +++ b/pcsx2-qt/Settings/BIOSSettingsWidget.ui @@ -0,0 +1,161 @@ + + + BIOSSettingsWidget + + + + 0 + 0 + 618 + 408 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + BIOS Directory + + + + + + PCSX2 will search for BIOS images in this directory. + + + true + + + + + + + + + + + + Browse... + + + + + + + Reset + + + + + + + + + + + + BIOS Selection + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Open in Explorer... + + + + + + + Refresh List + + + + + + + + + false + + + + Filename + + + + + Version + + + + + + + + + + + Options and Patches + + + + + + Fast Boot + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + diff --git a/pcsx2-qt/Settings/ControllerBindingWidget.ui b/pcsx2-qt/Settings/ControllerBindingWidget.ui new file mode 100644 index 0000000000..b57b05a21b --- /dev/null +++ b/pcsx2-qt/Settings/ControllerBindingWidget.ui @@ -0,0 +1,97 @@ + + + ControllerBindingWidget + + + + 0 + 0 + 834 + 560 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Controller Type + + + + + + + + + Qt::Horizontal + + + + 460 + 20 + + + + + + + + + + Automatic binding + + + + .. + + + + + + + Load Profile + + + + .. + + + + + + + Save Profile + + + + .. + + + + + + + + + + + + + + + diff --git a/pcsx2-qt/Settings/ControllerBindingWidget_DualShock2.ui b/pcsx2-qt/Settings/ControllerBindingWidget_DualShock2.ui new file mode 100644 index 0000000000..d4334fc311 --- /dev/null +++ b/pcsx2-qt/Settings/ControllerBindingWidget_DualShock2.ui @@ -0,0 +1,1296 @@ + + + ControllerBindingWidget_DualShock2 + + + + 0 + 0 + 1100 + 550 + + + + + 0 + 0 + + + + + 1100 + 550 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + D-Pad + + + + + + Down + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 0 + + + + + 100 + 16777215 + + + + PushButton + + + + + + + + + + Left + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 0 + + + + + 100 + 16777215 + + + + PushButton + + + + + + + + + + Up + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 0 + + + + + 100 + 16777215 + + + + PushButton + + + + + + + + + + Right + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 0 + + + + + 100 + 16777215 + + + + PushButton + + + + + + + + + + + + + Left Analog + + + + + + Down + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 0 + + + + + 100 + 16777215 + + + + PushButton + + + + + + + + + + Left + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 0 + + + + + 100 + 16777215 + + + + PushButton + + + + + + + + + + Up + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 0 + + + + + 100 + 16777215 + + + + PushButton + + + + + + + + + + Right + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 0 + + + + + 100 + 16777215 + + + + PushButton + + + + + + + + + + + + + Axis Scale + + + + + + Qt::Horizontal + + + + + + + 1.0x + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + L2 + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 0 + + + + + 100 + 16777215 + + + + PushButton + + + + + + + + + + R2 + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 0 + + + + + 100 + 16777215 + + + + PushButton + + + + + + + + + + Select + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 0 + + + + + 100 + 16777215 + + + + PushButton + + + + + + + + + + L1 + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 0 + + + + + 100 + 16777215 + + + + PushButton + + + + + + + + + + R1 + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 0 + + + + + 100 + 16777215 + + + + PushButton + + + + + + + + + + Start + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 0 + + + + + 100 + 16777215 + + + + PushButton + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 400 + 266 + + + + + + + :/images/dualshock-2.png + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + L3 + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 0 + + + + + 100 + 16777215 + + + + PushButton + + + + + + + + + + Large Motor + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 0 + + + + + 100 + 16777215 + + + + PushButton + + + + + + + + + + Small Motor + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 0 + + + + + 100 + 16777215 + + + + PushButton + + + + + + + + + + R3 + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 0 + + + + + 100 + 16777215 + + + + PushButton + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + Face Buttons + + + + + + Cross + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 0 + + + + + 100 + 16777215 + + + + PushButton + + + + + + + + + + Square + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 0 + + + + + 100 + 16777215 + + + + PushButton + + + + + + + + + + Triangle + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 0 + + + + + 100 + 16777215 + + + + PushButton + + + + + + + + + + Circle + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 0 + + + + + 100 + 16777215 + + + + PushButton + + + + + + + + + + + + + Right Analog + + + + + + Down + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 0 + + + + + 100 + 16777215 + + + + PushButton + + + + + + + + + + Left + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 0 + + + + + 100 + 16777215 + + + + PushButton + + + + + + + + + + Up + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 0 + + + + + 100 + 16777215 + + + + PushButton + + + + + + + + + + Right + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 100 + 0 + + + + + 100 + 16777215 + + + + PushButton + + + + + + + + + + + + + Vibration Scale + + + + + + Qt::Horizontal + + + + + + + 1.0x + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + InputBindingWidget + QPushButton +
Settings/InputBindingWidget.h
+
+ + InputVibrationBindingWidget + QPushButton +
Settings/InputBindingWidget.h
+
+
+ + + + + +
diff --git a/pcsx2-qt/Settings/ControllerBindingWidgets.cpp b/pcsx2-qt/Settings/ControllerBindingWidgets.cpp new file mode 100644 index 0000000000..667b89e840 --- /dev/null +++ b/pcsx2-qt/Settings/ControllerBindingWidgets.cpp @@ -0,0 +1,158 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" + +#include +#include + +#include "ControllerBindingWidgets.h" +#include "ControllerSettingsDialog.h" +#include "EmuThread.h" +#include "QtUtils.h" +#include "SettingWidgetBinder.h" +#include "SettingsDialog.h" + +#include "common/StringUtil.h" +#include "pcsx2/PAD/Host/PAD.h" + +ControllerBindingWidget::ControllerBindingWidget(QWidget* parent, ControllerSettingsDialog* dialog, u32 port) + : QWidget(parent) + , m_dialog(dialog) + , m_config_section(StringUtil::StdStringFromFormat("Pad%u", port + 1u)) + , m_port_number(port) +{ + m_ui.setupUi(this); + populateControllerTypes(); + onTypeChanged(); + + SettingWidgetBinder::BindWidgetToStringSetting(m_ui.controllerType, m_config_section, "Type", "None"); + connect(m_ui.controllerType, QOverload::of(&QComboBox::currentIndexChanged), this, + &ControllerBindingWidget::onTypeChanged); +} + +ControllerBindingWidget::~ControllerBindingWidget() +{ +} + +void ControllerBindingWidget::populateControllerTypes() +{ + m_ui.controllerType->addItem(tr("None (Not Connected)"), QStringLiteral("None")); + for (const std::string& type : PAD::GetControllerTypeNames()) + m_ui.controllerType->addItem(QString::fromStdString(type), QString::fromStdString(type)); +} + +void ControllerBindingWidget::onTypeChanged() +{ + if (m_current_widget) + { + m_ui.verticalLayout->removeWidget(m_current_widget); + delete m_current_widget; + m_current_widget = nullptr; + } + + m_controller_type = QtHost::GetBaseStringSettingValue(m_config_section.c_str(), "Type"); + + const int index = m_ui.controllerType->findData(QString::fromStdString(m_controller_type)); + if (index >= 0 && index != m_ui.controllerType->currentIndex()) + { + QSignalBlocker sb(m_ui.controllerType); + m_ui.controllerType->setCurrentIndex(index); + } + + if (m_controller_type == "DualShock2") + m_current_widget = ControllerBindingWidget_DualShock2::createInstance(this); + else + m_current_widget = new ControllerBindingWidget_Base(this); + + m_ui.verticalLayout->addWidget(m_current_widget, 1); +} + +////////////////////////////////////////////////////////////////////////// + +ControllerBindingWidget_Base::ControllerBindingWidget_Base(ControllerBindingWidget* parent) + : QWidget(parent) +{ +} + +ControllerBindingWidget_Base::~ControllerBindingWidget_Base() +{ +} + +void ControllerBindingWidget_Base::initBindingWidgets() +{ + const std::string& type = getControllerType(); + const std::string& config_section = getConfigSection(); + std::vector bindings(PAD::GetControllerBinds(type)); + + for (std::string& binding : bindings) + { + InputBindingWidget* widget = findChild(QString::fromStdString(binding)); + if (!widget) + { + Console.Error("(ControllerBindingWidget_Base) No widget found for '%s' (%*s)", + binding.c_str(), static_cast(type.size()), type.data()); + continue; + } + + widget->setKey(config_section, std::move(binding)); + } + + const PAD::VibrationCapabilities vibe_caps = PAD::GetControllerVibrationCapabilities(type); + switch (vibe_caps) + { + case PAD::VibrationCapabilities::LargeSmallMotors: + { + InputVibrationBindingWidget* widget = findChild(QStringLiteral("LargeMotor")); + if (widget) + widget->setKey(getDialog(), config_section, "LargeMotor"); + + widget = findChild(QStringLiteral("SmallMotor")); + if (widget) + widget->setKey(getDialog(), config_section, "SmallMotor"); + } + break; + + case PAD::VibrationCapabilities::SingleMotor: + { + InputVibrationBindingWidget* widget = findChild(QStringLiteral("Motor")); + if (widget) + widget->setKey(getDialog(), config_section, "Motor"); + } + break; + + case PAD::VibrationCapabilities::NoVibration: + default: + break; + } +} + +ControllerBindingWidget_DualShock2::ControllerBindingWidget_DualShock2(ControllerBindingWidget* parent) + : ControllerBindingWidget_Base(parent) +{ + m_ui.setupUi(this); + initBindingWidgets(); +} + +ControllerBindingWidget_DualShock2::~ControllerBindingWidget_DualShock2() +{ +} + +ControllerBindingWidget_Base* ControllerBindingWidget_DualShock2::createInstance(ControllerBindingWidget* parent) +{ + return new ControllerBindingWidget_DualShock2(parent); +} + +////////////////////////////////////////////////////////////////////////// \ No newline at end of file diff --git a/pcsx2-qt/Settings/ControllerBindingWidgets.h b/pcsx2-qt/Settings/ControllerBindingWidgets.h new file mode 100644 index 0000000000..d516f7cf54 --- /dev/null +++ b/pcsx2-qt/Settings/ControllerBindingWidgets.h @@ -0,0 +1,86 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#pragma once + +#include + +#include "ui_ControllerBindingWidget.h" +#include "ui_ControllerBindingWidget_DualShock2.h" + +class InputBindingWidget; +class ControllerSettingsDialog; +class ControllerBindingWidget_Base; + +class ControllerBindingWidget final : public QWidget +{ + Q_OBJECT + +public: + ControllerBindingWidget(QWidget* parent, ControllerSettingsDialog* dialog, u32 port); + ~ControllerBindingWidget(); + + __fi ControllerSettingsDialog* getDialog() const { return m_dialog; } + __fi const std::string& getConfigSection() const { return m_config_section; } + __fi const std::string& getControllerType() const { return m_controller_type; } + __fi u32 getPortNumber() const { return m_port_number; } + +private Q_SLOTS: + void onTypeChanged(); + +private: + void populateControllerTypes(); + + Ui::ControllerBindingWidget m_ui; + + ControllerSettingsDialog* m_dialog; + + std::string m_config_section; + std::string m_controller_type; + u32 m_port_number; + + ControllerBindingWidget_Base* m_current_widget = nullptr; +}; + +class ControllerBindingWidget_Base : public QWidget +{ + Q_OBJECT + +public: + ControllerBindingWidget_Base(ControllerBindingWidget* parent); + virtual ~ControllerBindingWidget_Base(); + + __fi ControllerSettingsDialog* getDialog() const { return static_cast(parent())->getDialog(); } + __fi const std::string& getConfigSection() const { return static_cast(parent())->getConfigSection(); } + __fi const std::string& getControllerType() const { return static_cast(parent())->getControllerType(); } + __fi u32 getPortNumber() const { return static_cast(parent())->getPortNumber(); } + +protected: + void initBindingWidgets(); +}; + +class ControllerBindingWidget_DualShock2 final : public ControllerBindingWidget_Base +{ + Q_OBJECT + +public: + ControllerBindingWidget_DualShock2(ControllerBindingWidget* parent); + ~ControllerBindingWidget_DualShock2(); + + static ControllerBindingWidget_Base* createInstance(ControllerBindingWidget* parent); + +private: + Ui::ControllerBindingWidget_DualShock2 m_ui; +}; diff --git a/pcsx2-qt/Settings/ControllerGlobalSettingsWidget.cpp b/pcsx2-qt/Settings/ControllerGlobalSettingsWidget.cpp new file mode 100644 index 0000000000..7cf805a78f --- /dev/null +++ b/pcsx2-qt/Settings/ControllerGlobalSettingsWidget.cpp @@ -0,0 +1,64 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" + +#include "Frontend/InputManager.h" +#include "Settings/ControllerGlobalSettingsWidget.h" +#include "Settings/ControllerSettingsDialog.h" +#include "QtUtils.h" +#include "SettingWidgetBinder.h" + +ControllerGlobalSettingsWidget::ControllerGlobalSettingsWidget(QWidget* parent, ControllerSettingsDialog* dialog) + : QWidget(parent) +{ + m_ui.setupUi(this); + + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.enableSDLSource, "InputSources", "SDL", true); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.enableSDLEnhancedMode, "InputSources", "SDLControllerEnhancedMode", false); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.enableXInputSource, "InputSources", "XInput", false); + + connect(m_ui.enableSDLSource, &QCheckBox::stateChanged, this, &ControllerGlobalSettingsWidget::updateSDLOptionsEnabled); + updateSDLOptionsEnabled(); +} + +ControllerGlobalSettingsWidget::~ControllerGlobalSettingsWidget() = default; + +void ControllerGlobalSettingsWidget::addDeviceToList(const QString& identifier, const QString& name) +{ + QListWidgetItem* item = new QListWidgetItem(); + item->setText(QStringLiteral("%1: %2").arg(identifier).arg(name)); + item->setData(Qt::UserRole, identifier); + m_ui.deviceList->addItem(item); +} + +void ControllerGlobalSettingsWidget::removeDeviceFromList(const QString& identifier) +{ + const int count = m_ui.deviceList->count(); + for (int i = 0; i < count; i++) + { + QListWidgetItem* item = m_ui.deviceList->item(i); + if (item->data(Qt::UserRole) != identifier) + continue; + + delete m_ui.deviceList->takeItem(i); + } +} + +void ControllerGlobalSettingsWidget::updateSDLOptionsEnabled() +{ + const bool enabled = m_ui.enableSDLSource->isChecked(); + m_ui.enableSDLEnhancedMode->setEnabled(enabled); +} diff --git a/pcsx2-qt/Settings/ControllerGlobalSettingsWidget.h b/pcsx2-qt/Settings/ControllerGlobalSettingsWidget.h new file mode 100644 index 0000000000..e49e4ee50f --- /dev/null +++ b/pcsx2-qt/Settings/ControllerGlobalSettingsWidget.h @@ -0,0 +1,42 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#pragma once + +#include +#include +#include +#include + +#include "ui_ControllerGlobalSettingsWidget.h" + +class ControllerSettingsDialog; + +class ControllerGlobalSettingsWidget : public QWidget +{ + Q_OBJECT + +public: + ControllerGlobalSettingsWidget(QWidget* parent, ControllerSettingsDialog* dialog); + ~ControllerGlobalSettingsWidget(); + + void addDeviceToList(const QString& identifier, const QString& name); + void removeDeviceFromList(const QString& identifier); + +private: + void updateSDLOptionsEnabled(); + + Ui::ControllerGlobalSettingsWidget m_ui; +}; diff --git a/pcsx2-qt/Settings/ControllerGlobalSettingsWidget.ui b/pcsx2-qt/Settings/ControllerGlobalSettingsWidget.ui new file mode 100644 index 0000000000..0dae013b2e --- /dev/null +++ b/pcsx2-qt/Settings/ControllerGlobalSettingsWidget.ui @@ -0,0 +1,173 @@ + + + ControllerGlobalSettingsWidget + + + + 0 + 0 + 880 + 712 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + SDL Input Source + + + + + + The SDL input source supports most controllers, and provides advanced functionality for Dualshock 4/Dualsense pads in Bluetooth mode (Vibration/LED Control). + + + true + + + + + + + Enable SDL Input Source + + + + + + + Dualshock 4/Dualsense Enhanced Mode + + + + + + + + + + XInput Source + + + + + + The XInput source provides support for XBox 360/XBox One/XBox Series controllers, and third party controllers which implement the XInput protocol. + + + true + + + + + + + Enable XInput Input Source + + + + + + + + + + Controller Multitap + + + + + + The multitap enables up to 8 controllers to be connected to the console. Each multitap provides 4 ports. Multitap is not supported by all games. (NOT YET IMPLEMENTED) + + + true + + + + + + + false + + + Multitap on Console Port 1 + + + + + + + false + + + Multitap on Console Port 2 + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + Detected Devices + + + + + + + 200 + 0 + + + + + 200 + 16777215 + + + + + + + + + + + + diff --git a/pcsx2-qt/Settings/ControllerSettingsDialog.cpp b/pcsx2-qt/Settings/ControllerSettingsDialog.cpp new file mode 100644 index 0000000000..f10dab90c9 --- /dev/null +++ b/pcsx2-qt/Settings/ControllerSettingsDialog.cpp @@ -0,0 +1,119 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" + +#include "EmuThread.h" +#include "QtHost.h" +#include "Settings/ControllerSettingsDialog.h" +#include "Settings/ControllerGlobalSettingsWidget.h" +#include "Settings/ControllerBindingWidgets.h" +#include "Settings/HotkeySettingsWidget.h" + +#include +#include + +ControllerSettingsDialog::ControllerSettingsDialog(QWidget* parent /* = nullptr */) + : QDialog(parent) +{ + m_ui.setupUi(this); + + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + + m_global_settings = new ControllerGlobalSettingsWidget(m_ui.settingsContainer, this); + m_ui.settingsContainer->insertWidget(0, m_global_settings); + + for (u32 i = 0; i < MAX_PORTS; i++) + { + m_port_bindings[i] = new ControllerBindingWidget(m_ui.settingsContainer, this, i); + m_ui.settingsContainer->insertWidget(i + 1, m_port_bindings[i]); + } + + m_hotkey_settings = new HotkeySettingsWidget(m_ui.settingsContainer, this); + m_ui.settingsContainer->insertWidget(3, m_hotkey_settings); + + m_ui.settingsCategory->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + connect(m_ui.settingsCategory, &QListWidget::currentRowChanged, this, &ControllerSettingsDialog::onCategoryCurrentRowChanged); + connect(m_ui.buttonBox, &QDialogButtonBox::rejected, this, &ControllerSettingsDialog::close); + + connect(g_emu_thread, &EmuThread::onInputDevicesEnumerated, this, &ControllerSettingsDialog::onInputDevicesEnumerated); + connect(g_emu_thread, &EmuThread::onInputDeviceConnected, this, &ControllerSettingsDialog::onInputDeviceConnected); + connect(g_emu_thread, &EmuThread::onInputDeviceDisconnected, this, &ControllerSettingsDialog::onInputDeviceDisconnected); + connect(g_emu_thread, &EmuThread::onVibrationMotorsEnumerated, this, &ControllerSettingsDialog::onVibrationMotorsEnumerated); + + // trigger a device enumeration to populate the device list + g_emu_thread->enumerateInputDevices(); + g_emu_thread->enumerateVibrationMotors(); +} + +ControllerSettingsDialog::~ControllerSettingsDialog() = default; + +void ControllerSettingsDialog::setCategory(Category category) +{ + switch (category) + { + case Category::GlobalSettings: + m_ui.settingsCategory->setCurrentRow(0); + break; + + // TODO: These will need to take multitap into consideration in the future. + case Category::FirstControllerSettings: + m_ui.settingsCategory->setCurrentRow(1); + break; + + case Category::HotkeySettings: + m_ui.settingsCategory->setCurrentRow(3); + break; + + default: + break; + } +} + +void ControllerSettingsDialog::onCategoryCurrentRowChanged(int row) +{ + m_ui.settingsContainer->setCurrentIndex(row); +} + +void ControllerSettingsDialog::onInputDevicesEnumerated(const QList>& devices) +{ + for (const QPair& device : devices) + m_global_settings->addDeviceToList(device.first, device.second); +} + +void ControllerSettingsDialog::onInputDeviceConnected(const QString& identifier, const QString& device_name) +{ + m_global_settings->addDeviceToList(identifier, device_name); + g_emu_thread->enumerateVibrationMotors(); +} + +void ControllerSettingsDialog::onInputDeviceDisconnected(const QString& identifier) +{ + m_global_settings->removeDeviceFromList(identifier); + g_emu_thread->enumerateVibrationMotors(); +} + +void ControllerSettingsDialog::onVibrationMotorsEnumerated(const QList& motors) +{ + m_vibration_motors.clear(); + m_vibration_motors.reserve(motors.size()); + + for (const InputBindingKey key : motors) + { + const std::string key_str(InputManager::ConvertInputBindingKeyToString(key)); + if (!key_str.empty()) + m_vibration_motors.push_back(QString::fromStdString(key_str)); + } +} diff --git a/pcsx2-qt/Settings/ControllerSettingsDialog.h b/pcsx2-qt/Settings/ControllerSettingsDialog.h new file mode 100644 index 0000000000..5a5a982d6d --- /dev/null +++ b/pcsx2-qt/Settings/ControllerSettingsDialog.h @@ -0,0 +1,74 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#pragma once +#include "ui_ControllerSettingsDialog.h" +#include "Frontend/InputManager.h" +#include +#include +#include +#include +#include +#include + +class ControllerGlobalSettingsWidget; +class ControllerBindingWidget; +class HotkeySettingsWidget; + +class ControllerSettingsDialog final : public QDialog +{ + Q_OBJECT + +public: + enum class Category + { + GlobalSettings, + FirstControllerSettings, + HotkeySettings, + Count + }; + + enum : u32 + { + MAX_PORTS = 2 + }; + + ControllerSettingsDialog(QWidget* parent = nullptr); + ~ControllerSettingsDialog(); + + HotkeySettingsWidget* getHotkeySettingsWidget() const { return m_hotkey_settings; } + + __fi const QStringList& getVibrationMotors() const { return m_vibration_motors; } + +public Q_SLOTS: + void setCategory(Category category); + +private Q_SLOTS: + void onCategoryCurrentRowChanged(int row); + + void onInputDevicesEnumerated(const QList>& devices); + void onInputDeviceConnected(const QString& identifier, const QString& device_name); + void onInputDeviceDisconnected(const QString& identifier); + void onVibrationMotorsEnumerated(const QList& motors); + +private: + Ui::ControllerSettingsDialog m_ui; + + ControllerGlobalSettingsWidget* m_global_settings = nullptr; + std::array m_port_bindings{}; + HotkeySettingsWidget* m_hotkey_settings = nullptr; + + QStringList m_vibration_motors; +}; diff --git a/pcsx2-qt/Settings/ControllerSettingsDialog.ui b/pcsx2-qt/Settings/ControllerSettingsDialog.ui new file mode 100644 index 0000000000..4c1287d2a1 --- /dev/null +++ b/pcsx2-qt/Settings/ControllerSettingsDialog.ui @@ -0,0 +1,122 @@ + + + ControllerSettingsDialog + + + Qt::WindowModal + + + + 0 + 0 + 1274 + 668 + + + + + 0 + 0 + + + + PCSX2 Controller Settings + + + + + + + 0 + 0 + + + + + 150 + 0 + + + + + 150 + 16777215 + + + + + 32 + 32 + + + + + Global Settings + + + + .. + + + + + Controller 1 + + + + .. + + + + + Controller 2 + + + + .. + + + + + Hotkeys + + + + .. + + + + + + + + + 1100 + 620 + + + + 4 + + + + + + + + + + + + + QDialogButtonBox::Close + + + + + + + + + + diff --git a/pcsx2-qt/Settings/EmulationSettingsWidget.cpp b/pcsx2-qt/Settings/EmulationSettingsWidget.cpp new file mode 100644 index 0000000000..5a09f20d0f --- /dev/null +++ b/pcsx2-qt/Settings/EmulationSettingsWidget.cpp @@ -0,0 +1,165 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" + +#include +#include + +#include "EmulationSettingsWidget.h" +#include "QtUtils.h" +#include "SettingWidgetBinder.h" +#include "SettingsDialog.h" + +static constexpr u32 DEFAULT_FRAME_LATENCY = 2; + +static void FillComboBoxWithEmulationSpeeds(QComboBox* cb) +{ + cb->addItem(qApp->translate("GeneralSettingsWidget", "Unlimited"), QVariant(0.0f)); + + static const int speeds[] = {10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 125, 150, 175, + 200, 250, 300, 350, 400, 450, 500, 600, 700, 800, 900, 1000}; + for (const int speed : speeds) + { + cb->addItem(qApp->translate("EmulationSettingsWidget", "%1% [%2 FPS (NTSC) / %3 FPS (PAL)]") + .arg(speed) + .arg((60 * speed) / 100) + .arg((50 * speed) / 100), + QVariant(static_cast(speed) / 100.0f)); + } +} + +EmulationSettingsWidget::EmulationSettingsWidget(QWidget* parent, SettingsDialog* dialog) + : QWidget(parent) +{ + m_ui.setupUi(this); + + FillComboBoxWithEmulationSpeeds(m_ui.normalSpeed); + if (const int index = + m_ui.normalSpeed->findData(QVariant(QtHost::GetBaseFloatSettingValue("Framerate", "NominalScalar", 1.0f))); + index >= 0) + { + m_ui.normalSpeed->setCurrentIndex(index); + } + connect(m_ui.normalSpeed, QOverload::of(&QComboBox::currentIndexChanged), this, + &EmulationSettingsWidget::onNormalSpeedIndexChanged); + FillComboBoxWithEmulationSpeeds(m_ui.fastForwardSpeed); + + if (const int index = + m_ui.fastForwardSpeed->findData(QVariant(QtHost::GetBaseFloatSettingValue("Framerate", "TurboScalar", 2.0f))); + index >= 0) + { + m_ui.fastForwardSpeed->setCurrentIndex(index); + } + connect(m_ui.fastForwardSpeed, QOverload::of(&QComboBox::currentIndexChanged), this, + &EmulationSettingsWidget::onFastForwardSpeedIndexChanged); + + FillComboBoxWithEmulationSpeeds(m_ui.slowMotionSpeed); + if (const int index = + m_ui.slowMotionSpeed->findData(QVariant(QtHost::GetBaseFloatSettingValue("Framerate", "SlomoScalar", 0.5f))); + index >= 0) + { + m_ui.slowMotionSpeed->setCurrentIndex(index); + } + connect(m_ui.slowMotionSpeed, QOverload::of(&QComboBox::currentIndexChanged), this, + &EmulationSettingsWidget::onSlowMotionSpeedIndexChanged); + + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.speedLimiter, "EmuCore/GS", "FrameLimitEnable", true); + SettingWidgetBinder::BindWidgetToIntSetting(m_ui.maxFrameLatency, "EmuCore/GS", "VsyncQueueSize", DEFAULT_FRAME_LATENCY); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.syncToHostRefreshRate, "EmuCore/GS", "SyncToHostRefreshRate", + false); + connect(m_ui.optimalFramePacing, &QCheckBox::toggled, this, &EmulationSettingsWidget::onOptimalFramePacingChanged); + + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.cheats, "EmuCore", "EnableCheats", false); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.widescreenPatches, "EmuCore", "EnableWideScreenPatches", false); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.perGameSettings, "EmuCore", "EnablePerGameSettings", true); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.hostFilesystem, "EmuCore", "HostFs", false); + + dialog->registerWidgetHelp( + m_ui.normalSpeed, tr("Normal Speed"), "100%", + tr("Sets the target emulation speed. It is not guaranteed that this speed will be reached, " + "and if not, the emulator will run as fast as it can manage.")); + dialog->registerWidgetHelp( + m_ui.fastForwardSpeed, tr("Fast Forward Speed"), tr("User Preference"), + tr("Sets the fast forward speed. This speed will be used when the fast forward hotkey is pressed/toggled.")); + dialog->registerWidgetHelp( + m_ui.slowMotionSpeed, tr("Slow Motion Speed"), tr("User Preference"), + tr("Sets the slow motion speed. This speed will be used when the slow motion hotkey is pressed/toggled.")); + + dialog->registerWidgetHelp( + m_ui.syncToHostRefreshRate, tr("Sync To Host Refresh Rate"), tr("Unchecked"), + tr("Adjusts the emulation speed so the console's refresh rate matches the host's refresh rate when both VSync and " + "Audio Resampling settings are enabled. This results in the smoothest animations possible, at the cost of " + "potentially increasing the emulation speed by less than 1%. Sync To Host Refresh Rate will not take effect if " + "the console's refresh rate is too far from the host's refresh rate. Users with variable refresh rate displays " + "should disable this option.")); + dialog->registerWidgetHelp(m_ui.cheats, tr("Enable Cheats"), tr("Unchecked"), + tr("Automatically loads and applies cheats on game start.")); + dialog->registerWidgetHelp( + m_ui.perGameSettings, tr("Enable Per-Game Settings"), tr("Checked"), + tr("When enabled, per-game settings will be applied, and incompatible enhancements will be disabled. You should " + "leave this option enabled except when testing enhancements with incompatible games.")); + + updateOptimalFramePacing(); +} + +EmulationSettingsWidget::~EmulationSettingsWidget() = default; + +void EmulationSettingsWidget::onNormalSpeedIndexChanged(int index) +{ + bool okay; + const float value = m_ui.normalSpeed->currentData().toFloat(&okay); + QtHost::SetBaseFloatSettingValue("Framerate", "NominalScalar", okay ? value : 1.0f); + g_emu_thread->applySettings(); +} + +void EmulationSettingsWidget::onFastForwardSpeedIndexChanged(int index) +{ + bool okay; + const float value = m_ui.fastForwardSpeed->currentData().toFloat(&okay); + QtHost::SetBaseFloatSettingValue("Framerate", "TurboScalar", okay ? value : 1.0f); + g_emu_thread->applySettings(); +} + +void EmulationSettingsWidget::onSlowMotionSpeedIndexChanged(int index) +{ + bool okay; + const float value = m_ui.slowMotionSpeed->currentData().toFloat(&okay); + QtHost::SetBaseFloatSettingValue("Framerate", "SlomoScalar", okay ? value : 1.0f); + g_emu_thread->applySettings(); +} + +void EmulationSettingsWidget::onOptimalFramePacingChanged(bool checked) +{ + const QSignalBlocker sb(m_ui.maxFrameLatency); + m_ui.maxFrameLatency->setValue(DEFAULT_FRAME_LATENCY); + m_ui.maxFrameLatency->setEnabled(!checked); + + QtHost::SetBaseIntSettingValue("EmuCore/GS", "VsyncQueueSize", checked ? 0 : DEFAULT_FRAME_LATENCY); + g_emu_thread->applySettings(); +} + +void EmulationSettingsWidget::updateOptimalFramePacing() +{ + const int value = QtHost::GetBaseIntSettingValue("EmuCore/GS", "VsyncQueueSize", DEFAULT_FRAME_LATENCY); + const bool optimal = (value == 0); + + const QSignalBlocker sb(m_ui.optimalFramePacing); + m_ui.optimalFramePacing->setChecked(optimal); + + const QSignalBlocker sb2(m_ui.maxFrameLatency); + m_ui.maxFrameLatency->setEnabled(!optimal); + m_ui.maxFrameLatency->setValue(optimal ? DEFAULT_FRAME_LATENCY : value); +} diff --git a/pcsx2-qt/Settings/EmulationSettingsWidget.h b/pcsx2-qt/Settings/EmulationSettingsWidget.h new file mode 100644 index 0000000000..c6b2c05ba9 --- /dev/null +++ b/pcsx2-qt/Settings/EmulationSettingsWidget.h @@ -0,0 +1,42 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#pragma once + +#include + +#include "ui_EmulationSettingsWidget.h" + +class SettingsDialog; + +class EmulationSettingsWidget : public QWidget +{ + Q_OBJECT + +public: + EmulationSettingsWidget(QWidget* parent, SettingsDialog* dialog); + ~EmulationSettingsWidget(); + +private Q_SLOTS: + void onNormalSpeedIndexChanged(int index); + void onFastForwardSpeedIndexChanged(int index); + void onSlowMotionSpeedIndexChanged(int index); + void onOptimalFramePacingChanged(bool checked); + +private: + void updateOptimalFramePacing(); + + Ui::EmulationSettingsWidget m_ui; +}; diff --git a/pcsx2-qt/Settings/EmulationSettingsWidget.ui b/pcsx2-qt/Settings/EmulationSettingsWidget.ui new file mode 100644 index 0000000000..ae530145e3 --- /dev/null +++ b/pcsx2-qt/Settings/EmulationSettingsWidget.ui @@ -0,0 +1,178 @@ + + + EmulationSettingsWidget + + + + 0 + 0 + 672 + 518 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Speed Control + + + + + + + + + + + + + + + Normal Speed: + + + + + + + Slow Motion Speed: + + + + + + + Fast Forward Speed: + + + + + + + Enable Speed Limiter + + + + + + + + + + Frame Pacing/Latency Control + + + + + + frames + + + 0 + + + 5 + + + + + + + Maximum Frame Latency: + + + + + + + + + Optimal Frame Pacing + + + + + + + Adjust To Host Refresh Rate + + + + + + + + + + + + Game Settings + + + + + + Enable Per-Game Settings + + + + + + + Enable Cheats + + + + + + + Enable Widescreen Patches + + + + + + + Enable Host Filesystem + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + diff --git a/pcsx2-qt/Settings/GameFixSettingsWidget.cpp b/pcsx2-qt/Settings/GameFixSettingsWidget.cpp new file mode 100644 index 0000000000..01531a4719 --- /dev/null +++ b/pcsx2-qt/Settings/GameFixSettingsWidget.cpp @@ -0,0 +1,50 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" + +#include +#include + +#include "GameFixSettingsWidget.h" +#include "EmuThread.h" +#include "QtUtils.h" +#include "SettingWidgetBinder.h" +#include "SettingsDialog.h" + +GameFixSettingsWidget::GameFixSettingsWidget(QWidget* parent, SettingsDialog* dialog) + : QWidget(parent) +{ + m_ui.setupUi(this); + + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.FpuMulHack, "EmuCore/Gamefixes", "FpuMulHack", false); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.FpuNegDivHack, "EmuCore/Gamefixes", "FpuNegDivHack", false); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.GoemonTlbHack, "EmuCore/Gamefixes", "GoemonTlbHack", false); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.SoftwareRendererFMVHack, "EmuCore/Gamefixes", "SoftwareRendererFMVHack", false); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.SkipMPEGHack, "EmuCore/Gamefixes", "SkipMPEGHack", false); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.OPHFlagHack, "EmuCore/Gamefixes", "OPHFlagHack", false); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.EETimingHack, "EmuCore/Gamefixes", "EETimingHack", false); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.DMABusyHack, "EmuCore/Gamefixes", "DMABusyHack", false); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.GIFFIFOHack, "EmuCore/Gamefixes", "GIFFIFOHack", false); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.VIFFIFOHack, "EmuCore/Gamefixes", "VIFFIFOHack", false); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.VIF1StallHack, "EmuCore/Gamefixes", "VIF1StallHack", false); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.VuAddSubHack, "EmuCore/Gamefixes", "VuAddSubHack", false); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.IbitHack, "EmuCore/Gamefixes", "IbitHack", false); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.VUKickstartHack, "EmuCore/Gamefixes", "VUKickstartHack", false); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.VUOverflowHack, "EmuCore/Gamefixes", "VUOverflowHack", false); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.XgKickHack, "EmuCore/Gamefixes", "XgKickHack", false); +} + +GameFixSettingsWidget::~GameFixSettingsWidget() = default; diff --git a/pcsx2-qt/Settings/GameFixSettingsWidget.h b/pcsx2-qt/Settings/GameFixSettingsWidget.h new file mode 100644 index 0000000000..c1bdf6aac8 --- /dev/null +++ b/pcsx2-qt/Settings/GameFixSettingsWidget.h @@ -0,0 +1,34 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#pragma once + +#include + +#include "ui_GameFixSettingsWidget.h" + +class SettingsDialog; + +class GameFixSettingsWidget : public QWidget +{ + Q_OBJECT + +public: + GameFixSettingsWidget(QWidget* parent, SettingsDialog* dialog); + ~GameFixSettingsWidget(); + +private: + Ui::GameFixSettingsWidget m_ui; +}; diff --git a/pcsx2-qt/Settings/GameFixSettingsWidget.ui b/pcsx2-qt/Settings/GameFixSettingsWidget.ui new file mode 100644 index 0000000000..eb85de7e2e --- /dev/null +++ b/pcsx2-qt/Settings/GameFixSettingsWidget.ui @@ -0,0 +1,166 @@ + + + GameFixSettingsWidget + + + + 0 + 0 + 648 + 481 + + + + Form + + + + 0 + + + 0 + + + 0 + + + + + Game Fixes (NOT recommended to change globally) + + + + + + FPU Negative Divide Hack (For Gundam Games) + + + + + + + FPU Multiply Hack (For Tales of Destiny) + + + + + + + Use Software Renderer For FMVs + + + + + + + Skip MPEG Hack (Skips Videos/FMVs) + + + + + + + Preload TLB Hack (For Goemon) + + + + + + + EE Timing Hack (General Purpose Timing Hack) + + + + + + + OPH Flag Hack (For Bleach Blade Battlers) + + + + + + + Emulate GIF FIFO (Correct But Slower) + + + + + + + DMA Busy Hack (Deny Writes When Busy) + + + + + + + Delay VIF1 Stalls (For SOCOM 2 HUD/Spy Hunter) + + + + + + + Emulate VIF FIFO (Correct But Slower) + + + + + + + VU I Bit Hack (For Scarface The World is Yours/Crash Tag Team Racing) + + + + + + + VU Add Hack (For Tri-Ace Games) + + + + + + + VU Overflow Hack (Superman Returns) + + + + + + + VU Kickstart Hack (Run Ahead) + + + + + + + VU XGKick Sync (Correct But Slower) + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + diff --git a/pcsx2-qt/Settings/GameListSettingsWidget.cpp b/pcsx2-qt/Settings/GameListSettingsWidget.cpp new file mode 100644 index 0000000000..eb5ff4239d --- /dev/null +++ b/pcsx2-qt/Settings/GameListSettingsWidget.cpp @@ -0,0 +1,244 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "GameListSettingsWidget.h" +#include "MainWindow.h" +#include "QtHost.h" +#include "QtUtils.h" + +GameListSettingsWidget::GameListSettingsWidget(QWidget* parent, SettingsDialog* dialog) + : QWidget(parent) +{ + m_ui.setupUi(this); + + m_ui.searchDirectoryList->setSelectionMode(QAbstractItemView::SingleSelection); + m_ui.searchDirectoryList->setSelectionBehavior(QAbstractItemView::SelectRows); + m_ui.searchDirectoryList->setAlternatingRowColors(true); + m_ui.searchDirectoryList->setShowGrid(false); + m_ui.searchDirectoryList->horizontalHeader()->setHighlightSections(false); + m_ui.searchDirectoryList->verticalHeader()->hide(); + m_ui.searchDirectoryList->setCurrentIndex({}); + m_ui.searchDirectoryList->setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu); + + connect(m_ui.searchDirectoryList, &QTableWidget::customContextMenuRequested, this, + &GameListSettingsWidget::onDirectoryListContextMenuRequested); + connect(m_ui.addSearchDirectoryButton, &QPushButton::clicked, this, + &GameListSettingsWidget::onAddSearchDirectoryButtonClicked); + connect(m_ui.removeSearchDirectoryButton, &QPushButton::clicked, this, + &GameListSettingsWidget::onRemoveSearchDirectoryButtonClicked); + connect(m_ui.addExcludedPath, &QPushButton::clicked, this, &GameListSettingsWidget::onAddExcludedPathButtonClicked); + connect(m_ui.removeExcludedPath, &QPushButton::clicked, this, + &GameListSettingsWidget::onRemoveExcludedPathButtonClicked); + connect(m_ui.rescanAllGames, &QPushButton::clicked, this, &GameListSettingsWidget::onRescanAllGamesClicked); + connect(m_ui.scanForNewGames, &QPushButton::clicked, this, &GameListSettingsWidget::onScanForNewGamesClicked); + + refreshDirectoryList(); + refreshExclusionList(); +} + +GameListSettingsWidget::~GameListSettingsWidget() = default; + +bool GameListSettingsWidget::addExcludedPath(const std::string& path) +{ + if (!QtHost::AddBaseValueToStringList("GameList", "ExcludedPaths", path.c_str())) + return false; + + m_ui.excludedPaths->addItem(QString::fromStdString(path)); + g_main_window->refreshGameList(false); + return true; +} + +void GameListSettingsWidget::refreshExclusionList() +{ + m_ui.excludedPaths->clear(); + + const std::vector paths(QtHost::GetBaseStringListSetting("GameList", "ExcludedPaths")); + for (const std::string& path : paths) + m_ui.excludedPaths->addItem(QString::fromStdString(path)); +} + +void GameListSettingsWidget::resizeEvent(QResizeEvent* event) +{ + QWidget::resizeEvent(event); + + QtUtils::ResizeColumnsForTableView(m_ui.searchDirectoryList, {-1, 100}); +} + +void GameListSettingsWidget::addPathToTable(const std::string& path, bool recursive) +{ + const int row = m_ui.searchDirectoryList->rowCount(); + m_ui.searchDirectoryList->insertRow(row); + + QTableWidgetItem* item = new QTableWidgetItem(); + item->setText(QString::fromStdString(path)); + m_ui.searchDirectoryList->setItem(row, 0, item); + + QCheckBox* cb = new QCheckBox(m_ui.searchDirectoryList); + m_ui.searchDirectoryList->setCellWidget(row, 1, cb); + cb->setChecked(recursive); + + connect(cb, &QCheckBox::stateChanged, [this, item](int state) { + const std::string path(item->text().toStdString()); + if (state == Qt::Checked) + { + QtHost::RemoveBaseValueFromStringList("GameList", "Paths", path.c_str()); + QtHost::AddBaseValueToStringList("GameList", "RecursivePaths", path.c_str()); + } + else + { + QtHost::RemoveBaseValueFromStringList("GameList", "RecursivePaths", path.c_str()); + QtHost::AddBaseValueToStringList("GameList", "Paths", path.c_str()); + } + }); +} + +void GameListSettingsWidget::refreshDirectoryList() +{ + QSignalBlocker sb(m_ui.searchDirectoryList); + while (m_ui.searchDirectoryList->rowCount() > 0) + m_ui.searchDirectoryList->removeRow(0); + + std::vector path_list = QtHost::GetBaseStringListSetting("GameList", "Paths"); + for (const std::string& entry : path_list) + addPathToTable(entry, false); + + path_list = QtHost::GetBaseStringListSetting("GameList", "RecursivePaths"); + for (const std::string& entry : path_list) + addPathToTable(entry, true); + + m_ui.searchDirectoryList->sortByColumn(0, Qt::AscendingOrder); +} + +void GameListSettingsWidget::addSearchDirectory(const QString& path, bool recursive) +{ + const std::string spath(path.toStdString()); + QtHost::RemoveBaseValueFromStringList("GameList", recursive ? "Paths" : "RecursivePaths", spath.c_str()); + QtHost::AddBaseValueToStringList("GameList", recursive ? "RecursivePaths" : "Paths", spath.c_str()); + refreshDirectoryList(); + g_main_window->refreshGameList(false); +} + +void GameListSettingsWidget::removeSearchDirectory(const QString& path) +{ + const std::string spath(path.toStdString()); + if (!QtHost::RemoveBaseValueFromStringList("GameList", "Paths", spath.c_str()) && + !QtHost::RemoveBaseValueFromStringList("GameList", "RecursivePaths", spath.c_str())) + { + return; + } + + refreshDirectoryList(); + g_main_window->refreshGameList(false); +} + +void GameListSettingsWidget::onDirectoryListContextMenuRequested(const QPoint& point) +{ + QModelIndexList selection = m_ui.searchDirectoryList->selectionModel()->selectedIndexes(); + if (selection.size() < 1) + return; + + const int row = selection[0].row(); + + QMenu menu; + menu.addAction(tr("Remove"), [this]() { onRemoveSearchDirectoryButtonClicked(); }); + menu.addSeparator(); + menu.addAction(tr("Open Directory..."), [this, row]() { + QtUtils::OpenURL(this, QUrl::fromLocalFile(m_ui.searchDirectoryList->item(row, 0)->text())); + }); + menu.exec(m_ui.searchDirectoryList->mapToGlobal(point)); +} + +void GameListSettingsWidget::addSearchDirectory(QWidget* parent_widget) +{ + QString dir = + QDir::toNativeSeparators(QFileDialog::getExistingDirectory(parent_widget, tr("Select Search Directory"))); + + if (dir.isEmpty()) + return; + + QMessageBox::StandardButton selection = + QMessageBox::question(this, tr("Scan Recursively?"), + tr("Would you like to scan the directory \"%1\" recursively?\n\nScanning recursively takes " + "more time, but will identify files in subdirectories.") + .arg(dir), + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); + if (selection == QMessageBox::Cancel) + return; + + const bool recursive = (selection == QMessageBox::Yes); + addSearchDirectory(dir, recursive); +} + +void GameListSettingsWidget::onAddSearchDirectoryButtonClicked() +{ + addSearchDirectory(this); +} + +void GameListSettingsWidget::onRemoveSearchDirectoryButtonClicked() +{ + const int row = m_ui.searchDirectoryList->currentRow(); + QTableWidgetItem* item = (row >= 0) ? m_ui.searchDirectoryList->takeItem(row, 0) : nullptr; + if (!item) + return; + + removeSearchDirectory(item->text()); + delete item; +} + +void GameListSettingsWidget::onAddExcludedPathButtonClicked() +{ + QString path = + QDir::toNativeSeparators(QFileDialog::getOpenFileName(QtUtils::GetRootWidget(this), tr("Select Path"))); + if (path.isEmpty()) + return; + + addExcludedPath(path.toStdString()); +} + +void GameListSettingsWidget::onRemoveExcludedPathButtonClicked() +{ + const int row = m_ui.excludedPaths->currentRow(); + QListWidgetItem* item = (row >= 0) ? m_ui.excludedPaths->takeItem(row) : 0; + if (!item) + return; + + QtHost::RemoveBaseValueFromStringList("GameList", "ExcludedPaths", item->text().toUtf8().constData()); + delete item; + + g_main_window->refreshGameList(false); +} + +void GameListSettingsWidget::onRescanAllGamesClicked() +{ + g_main_window->refreshGameList(true); +} + +void GameListSettingsWidget::onScanForNewGamesClicked() +{ + g_main_window->refreshGameList(false); +} diff --git a/pcsx2-qt/Settings/GameListSettingsWidget.h b/pcsx2-qt/Settings/GameListSettingsWidget.h new file mode 100644 index 0000000000..4a8bdc23e2 --- /dev/null +++ b/pcsx2-qt/Settings/GameListSettingsWidget.h @@ -0,0 +1,59 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#pragma once +#include +#include + +#include "ui_GameListSettingsWidget.h" + +class SettingsDialog; + +class GameListSearchDirectoriesModel; + +class GameListSettingsWidget : public QWidget +{ + Q_OBJECT + +public: + GameListSettingsWidget(QWidget* parent, SettingsDialog* dialog); + ~GameListSettingsWidget(); + + bool addExcludedPath(const std::string& path); + void refreshExclusionList(); + +public Q_SLOTS: + void addSearchDirectory(QWidget* parent_widget); + +private Q_SLOTS: + void onDirectoryListContextMenuRequested(const QPoint& point); + void onAddSearchDirectoryButtonClicked(); + void onRemoveSearchDirectoryButtonClicked(); + void onAddExcludedPathButtonClicked(); + void onRemoveExcludedPathButtonClicked(); + void onScanForNewGamesClicked(); + void onRescanAllGamesClicked(); + +protected: + void resizeEvent(QResizeEvent* event); + +private: + void addPathToTable(const std::string& path, bool recursive); + void refreshDirectoryList(); + void addSearchDirectory(const QString& path, bool recursive); + void removeSearchDirectory(const QString& path); + + Ui::GameListSettingsWidget m_ui; +}; diff --git a/pcsx2-qt/Settings/GameListSettingsWidget.ui b/pcsx2-qt/Settings/GameListSettingsWidget.ui new file mode 100644 index 0000000000..6b61d81330 --- /dev/null +++ b/pcsx2-qt/Settings/GameListSettingsWidget.ui @@ -0,0 +1,213 @@ + + + GameListSettingsWidget + + + + 0 + 0 + 532 + 376 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + Search Directories (will be scanned for games) + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + Add + + + + + + + + + + + 0 + 0 + + + + Remove + + + + + + + + + + + + + Search Directory + + + + + Scan Recursively + + + + + + + + + + Excluded Paths (will not be scanned) + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + Add + + + + + + + + + + + 0 + 0 + + + + Remove + + + + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + Scan For New Games + + + + + + + + + + + 0 + 0 + + + + Rescan All Games + + + + + + + + + + + + + + + diff --git a/pcsx2-qt/Settings/GraphicsSettingsWidget.cpp b/pcsx2-qt/Settings/GraphicsSettingsWidget.cpp new file mode 100644 index 0000000000..e70507c5db --- /dev/null +++ b/pcsx2-qt/Settings/GraphicsSettingsWidget.cpp @@ -0,0 +1,381 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" + +#include "GraphicsSettingsWidget.h" +#include "QtUtils.h" +#include "SettingWidgetBinder.h" +#include "SettingsDialog.h" +#include + +#include "pcsx2/GS/GS.h" +#include "pcsx2/GS/GSUtil.h" + +#include "Frontend/VulkanHostDisplay.h" + +#ifdef _WIN32 +#include "Frontend/D3D11HostDisplay.h" +#endif + +struct RendererInfo +{ + const char* name; + GSRendererType type; +}; + +static constexpr RendererInfo s_renderer_info[] = { + QT_TRANSLATE_NOOP("GraphicsSettingsWidget", "Automatic"), + GSRendererType::Auto, +#ifdef _WIN32 + QT_TRANSLATE_NOOP("GraphicsSettingsWidget", "Direct3D 11"), + GSRendererType::DX11, +#endif + QT_TRANSLATE_NOOP("GraphicsSettingsWidget", "OpenGL"), + GSRendererType::OGL, + QT_TRANSLATE_NOOP("GraphicsSettingsWidget", "Vulkan"), + GSRendererType::VK, + QT_TRANSLATE_NOOP("GraphicsSettingsWidget", "Software"), + GSRendererType::SW, + QT_TRANSLATE_NOOP("GraphicsSettingsWidget", "Null"), + GSRendererType::Null, +}; + +static const char* s_anisotropic_filtering_entries[] = {QT_TRANSLATE_NOOP("GraphicsSettingsWidget", "Off (Default)"), + QT_TRANSLATE_NOOP("GraphicsSettingsWidget", "2x"), + QT_TRANSLATE_NOOP("GraphicsSettingsWidget", "4x"), + QT_TRANSLATE_NOOP("GraphicsSettingsWidget", "8x"), + QT_TRANSLATE_NOOP("GraphicsSettingsWidget", "16x"), + nullptr}; +static const char* s_anisotropic_filtering_values[] = {"1", "2", "4", "8", "16", nullptr}; + +static constexpr int DEFAULT_INTERLACE_MODE = 7; + +GraphicsSettingsWidget::GraphicsSettingsWidget(QWidget* parent, SettingsDialog* dialog) + : QWidget(parent) +{ + m_ui.setupUi(this); + + ////////////////////////////////////////////////////////////////////////// + // Global Settings + ////////////////////////////////////////////////////////////////////////// + SettingWidgetBinder::BindWidgetToStringSetting(m_ui.adapter, "EmuCore/GS", "Adapter"); + SettingWidgetBinder::BindWidgetToIntSetting(m_ui.vsync, "EmuCore/GS", "VsyncEnable", 0); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.enableHWFixes, "EmuCore/GS", "UserHacks", false); + + ////////////////////////////////////////////////////////////////////////// + // Game Display Settings + ////////////////////////////////////////////////////////////////////////// + SettingWidgetBinder::BindWidgetToEnumSetting(m_ui.aspectRatio, "EmuCore/GS", "AspectRatio", + Pcsx2Config::GSOptions::AspectRatioNames, AspectRatioType::R4_3); + SettingWidgetBinder::BindWidgetToEnumSetting(m_ui.fmvAspectRatio, "EmuCore/GS", "FMVAspectRatioSwitch", + Pcsx2Config::GSOptions::FMVAspectRatioSwitchNames, + FMVAspectRatioSwitchType::Off); + SettingWidgetBinder::BindWidgetToIntSetting(m_ui.interlacing, "EmuCore/GS", "interlace", DEFAULT_INTERLACE_MODE); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.bilinearFiltering, "EmuCore/GS", "LinearPresent", true); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.integerScaling, "EmuCore/GS", "IntegerScaling", false); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.internalResolutionScreenshots, "EmuCore/GS", + "InternalResolutionScreenshots", false); + SettingWidgetBinder::BindWidgetToFloatSetting(m_ui.zoom, "EmuCore/GS", "Zoom", 100.0f); + SettingWidgetBinder::BindWidgetToFloatSetting(m_ui.stretchY, "EmuCore/GS", "StretchY", 100.0f); + SettingWidgetBinder::BindWidgetToFloatSetting(m_ui.offsetX, "EmuCore/GS", "OffsetX", 0.0f); + SettingWidgetBinder::BindWidgetToFloatSetting(m_ui.offsetY, "EmuCore/GS", "OffsetY", 0.0f); + + connect(m_ui.integerScaling, &QCheckBox::stateChanged, this, &GraphicsSettingsWidget::onIntegerScalingChanged); + onIntegerScalingChanged(); + + connect(m_ui.fullscreenModes, QOverload::of(&QComboBox::currentIndexChanged), this, + &GraphicsSettingsWidget::onFullscreenModeChanged); + + ////////////////////////////////////////////////////////////////////////// + // OSD Settings + ////////////////////////////////////////////////////////////////////////// + SettingWidgetBinder::BindWidgetToFloatSetting(m_ui.osdScale, "EmuCore/GS", "OsdScale", 100.0f); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.osdShowMessages, "EmuCore/GS", "OsdShowMessages", true); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.osdShowSpeed, "EmuCore/GS", "OsdShowSpeed", false); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.osdShowFPS, "EmuCore/GS", "OsdShowFPS", false); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.osdShowCPU, "EmuCore/GS", "OsdShowCPU", false); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.osdShowResolution, "EmuCore/GS", "OsdShowResolution", false); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.osdShowGSStats, "EmuCore/GS", "OsdShowGSStats", false); + + dialog->registerWidgetHelp(m_ui.osdShowMessages, tr("Show OSD Messages"), tr("Checked"), + tr("Shows on-screen-display messages when events occur such as save states being " + "created/loaded, screenshots being taken, etc.")); + dialog->registerWidgetHelp(m_ui.osdShowFPS, tr("Show Game Frame Rate"), tr("Unchecked"), + tr("Shows the internal frame rate of the game in the top-right corner of the display.")); + dialog->registerWidgetHelp( + m_ui.osdShowSpeed, tr("Show Emulation Speed"), tr("Unchecked"), + tr("Shows the current emulation speed of the system in the top-right corner of the display as a percentage.")); + dialog->registerWidgetHelp(m_ui.osdShowResolution, tr("Show Resolution"), tr("Unchecked"), + tr("Shows the resolution of the game in the top-right corner of the display.")); + + ////////////////////////////////////////////////////////////////////////// + // HW Settings + ////////////////////////////////////////////////////////////////////////// + SettingWidgetBinder::BindWidgetToIntSetting(m_ui.upscaleMultiplier, "EmuCore/GS", "upscale_multiplier", 1); + SettingWidgetBinder::BindWidgetToIntSetting(m_ui.textureFiltering, "EmuCore/GS", "filter", + static_cast(BiFiltering::PS2)); + SettingWidgetBinder::BindWidgetToIntSetting(m_ui.trilinearFiltering, "EmuCore/GS", "UserHacks_TriFilter", + static_cast(TriFiltering::Off)); + SettingWidgetBinder::BindWidgetToEnumSetting(m_ui.anisotropicFiltering, "EmuCore/GS", "MaxAnisotropy", + s_anisotropic_filtering_entries, s_anisotropic_filtering_values, "1"); + SettingWidgetBinder::BindWidgetToIntSetting(m_ui.dithering, "EmuCore/GS", "dithering_ps2", 2); + SettingWidgetBinder::BindWidgetToIntSetting(m_ui.mipmapping, "EmuCore/GS", "mipmap_hw", + static_cast(HWMipmapLevel::Automatic), -1); + SettingWidgetBinder::BindWidgetToIntSetting(m_ui.crcFixLevel, "EmuCore/GS", "crc_hack_level", + static_cast(CRCHackLevel::Automatic), -1); + SettingWidgetBinder::BindWidgetToIntSetting(m_ui.blending, "EmuCore/GS", "accurate_blending_unit", + static_cast(AccBlendLevel::Basic)); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.accurateDATE, "EmuCore/GS", "accurate_date", true); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.conservativeBufferAllocation, "EmuCore/GS", + "conservative_framebuffer", true); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.gpuPaletteConversion, "EmuCore/GS", "paltex", false); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.preloadTexture, "EmuCore/GS", "preload_texture", false); + + ////////////////////////////////////////////////////////////////////////// + // HW Renderer Fixes + ////////////////////////////////////////////////////////////////////////// + SettingWidgetBinder::BindWidgetToIntSetting(m_ui.halfScreenFix, "EmuCore/GS", "UserHacks_Half_Bottom_Override", -1, + -1); + SettingWidgetBinder::BindWidgetToIntSetting(m_ui.skipDrawRangeStart, "EmuCore/GS", "UserHacks_SkipDraw", 0); + SettingWidgetBinder::BindWidgetToIntSetting(m_ui.skipDrawRangeCount, "EmuCore/GS", "UserHacks_SkipDraw_Offset", 0); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.hwAutoFlush, "EmuCore/GS", "UserHacks_AutoFlush", false); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.frameBufferConversion, "EmuCore/GS", "UserHacks_CPU_FB_Conversion", + false); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.disableDepthEmulation, "EmuCore/GS", + "UserHacks_DisableDepthSupport", false); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.memoryWrapping, "EmuCore/GS", "wrap_gs_mem", false); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.disableSafeFeatures, "EmuCore/GS", + "UserHacks_Disable_Safe_Features", false); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.preloadFrameData, "EmuCore/GS", "preload_frame_with_gs_data", + false); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.fastTextureInvalidation, "EmuCore/GS", + "UserHacks_DisablePartialInvalidation", false); + + ////////////////////////////////////////////////////////////////////////// + // HW Upscaling Fixes + ////////////////////////////////////////////////////////////////////////// + SettingWidgetBinder::BindWidgetToIntSetting(m_ui.halfPixelOffset, "EmuCore/GS", "UserHacks_HalfPixelOffset", 0); + SettingWidgetBinder::BindWidgetToIntSetting(m_ui.roundSprite, "EmuCore/GS", "UserHacks_RoundSprite", 0); + SettingWidgetBinder::BindWidgetToIntSetting(m_ui.textureOffsetX, "EmuCore/GS", "UserHacks_TCOffsetX", 0); + SettingWidgetBinder::BindWidgetToIntSetting(m_ui.textureOffsetY, "EmuCore/GS", "UserHacks_TCOffsetY", 0); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.alignSprite, "EmuCore/GS", "UserHacks_align_sprite_X", false); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.mergeSprite, "EmuCore/GS", "UserHacks_merge_pp_sprite", false); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.wildHack, "EmuCore/GS", "UserHacks_WildHack", false); + + ////////////////////////////////////////////////////////////////////////// + // Advanced Settings + ////////////////////////////////////////////////////////////////////////// + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.useBlitSwapChain, "EmuCore/GS", "UseBlitSwapChain", false); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.useDebugDevice, "EmuCore/GS", "UseDebugDevice", false); + + ////////////////////////////////////////////////////////////////////////// + // SW Settings + ////////////////////////////////////////////////////////////////////////// + SettingWidgetBinder::BindWidgetToIntSetting(m_ui.extraSWThreads, "EmuCore/GS", "extrathreads", 2); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.swAutoFlush, "EmuCore/GS", "autoflush_sw", true); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.swAA1, "EmuCore/GS", "aa1", true); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.swMipmap, "EmuCore/GS", "mipmap", true); + + ////////////////////////////////////////////////////////////////////////// + // Non-trivial settings + ////////////////////////////////////////////////////////////////////////// + const GSRendererType current_renderer = static_cast( + QtHost::GetBaseIntSettingValue("EmuCore/GS", "Renderer", static_cast(GSRendererType::Auto))); + for (const RendererInfo& ri : s_renderer_info) + { + m_ui.renderer->addItem(qApp->translate("GraphicsSettingsWidget", ri.name)); + if (ri.type == current_renderer) + m_ui.renderer->setCurrentIndex(m_ui.renderer->count() - 1); + } + connect(m_ui.renderer, QOverload::of(&QComboBox::currentIndexChanged), this, + &GraphicsSettingsWidget::onRendererChanged); + connect(m_ui.enableHWFixes, &QCheckBox::stateChanged, this, &GraphicsSettingsWidget::onEnableHardwareFixesChanged); + updateRendererDependentOptions(); + + dialog->registerWidgetHelp(m_ui.useBlitSwapChain, tr("Use Blit Swap Chain"), tr("Unchecked"), + tr("Uses a blit presentation model instead of flipping when using the Direct3D 11 " + "renderer. This usually results in slower performance, but may be required for some " + "streaming applications, or to uncap framerates on some systems.")); +} + +GraphicsSettingsWidget::~GraphicsSettingsWidget() = default; + +void GraphicsSettingsWidget::onRendererChanged(int index) +{ + QtHost::SetBaseIntSettingValue("EmuCore/GS", "Renderer", static_cast(s_renderer_info[index].type)); + g_emu_thread->applySettings(); + updateRendererDependentOptions(); +} + +void GraphicsSettingsWidget::onAdapterChanged(int index) +{ + if (index == 0) + QtHost::RemoveBaseSettingValue("EmuCore/GS", "Adapter"); + else + QtHost::SetBaseStringSettingValue("EmuCore/GS", "Adapter", m_ui.adapter->currentText().toUtf8().constData()); + g_emu_thread->applySettings(); +} + +void GraphicsSettingsWidget::onEnableHardwareFixesChanged() +{ + const bool enabled = m_ui.enableHWFixes->isChecked(); + m_ui.hardwareRendererGroup->setTabEnabled(2, enabled); + m_ui.hardwareRendererGroup->setTabEnabled(3, enabled); +} + +void GraphicsSettingsWidget::updateRendererDependentOptions() +{ + const int index = m_ui.renderer->currentIndex(); + GSRendererType type = s_renderer_info[index].type; + if (type == GSRendererType::Auto) + type = GSUtil::GetPreferredRenderer(); + +#ifdef _WIN32 + const bool is_dx11 = (type == GSRendererType::DX11 || type == GSRendererType::SW); +#else + const bool is_dx11 = false; +#endif + + const bool is_hardware = (type == GSRendererType::DX11 || type == GSRendererType::OGL || type == GSRendererType::VK); + const bool is_software = (type == GSRendererType::SW); + + // move advanced tab to the correct parent + static constexpr std::array move_tab_names = {{"Display", "On-Screen Display", "Advanced"}}; + const std::array move_tab_pointers = {{m_ui.gameDisplayTab, m_ui.osdTab, m_ui.advancedTab}}; + for (size_t i = 0; i < move_tab_pointers.size(); i++) + { + QWidget* tab = move_tab_pointers[i]; + const QString tab_label(tr(move_tab_names[i])); + if (const int index = m_ui.softwareRendererGroup->indexOf(tab); index >= 0 && is_hardware) + m_ui.softwareRendererGroup->removeTab(index); + if (const int index = m_ui.hardwareRendererGroup->indexOf(tab); index >= 0 && is_software) + m_ui.hardwareRendererGroup->removeTab(index); + if (const int index = m_ui.hardwareRendererGroup->indexOf(tab); index < 0 && is_hardware) + m_ui.hardwareRendererGroup->insertTab((i == 0) ? 0 : m_ui.hardwareRendererGroup->count(), tab, tab_label); + if (const int index = m_ui.softwareRendererGroup->indexOf(tab); index < 0 && is_software) + m_ui.softwareRendererGroup->insertTab((i == 0) ? 0 : m_ui.softwareRendererGroup->count(), tab, tab_label); + } + + if (is_hardware != is_software) + { + if (is_hardware) + m_ui.hardwareRendererGroup->setCurrentIndex(0); + else + m_ui.softwareRendererGroup->setCurrentIndex(0); + } + + if (m_hardware_renderer_visible != is_hardware) + { + m_ui.hardwareRendererGroup->setVisible(is_hardware); + if (!is_hardware) + m_ui.verticalLayout->removeWidget(m_ui.hardwareRendererGroup); + else + m_ui.verticalLayout->insertWidget(1, m_ui.hardwareRendererGroup); + + m_hardware_renderer_visible = is_hardware; + } + + if (m_software_renderer_visible != is_software) + { + m_ui.softwareRendererGroup->setVisible(is_software); + if (!is_hardware) + m_ui.verticalLayout->removeWidget(m_ui.softwareRendererGroup); + else + m_ui.verticalLayout->insertWidget(1, m_ui.softwareRendererGroup); + + m_software_renderer_visible = is_software; + } + + m_ui.useBlitSwapChain->setEnabled(is_dx11); + + // populate adapters + HostDisplay::AdapterAndModeList modes; + switch (type) + { +#ifdef _WIN32 + case GSRendererType::DX11: + modes = D3D11HostDisplay::StaticGetAdapterAndModeList(); + break; +#endif + + case GSRendererType::VK: + modes = VulkanHostDisplay::StaticGetAdapterAndModeList(nullptr); + break; + + case GSRendererType::OGL: + case GSRendererType::SW: + case GSRendererType::Null: + case GSRendererType::Auto: + default: + break; + } + + // fill+select adapters + { + const std::string current_adapter = QtHost::GetBaseStringSettingValue("EmuCore/GS", "Adapter", ""); + QSignalBlocker sb(m_ui.adapter); + m_ui.adapter->clear(); + m_ui.adapter->setEnabled(!modes.adapter_names.empty()); + m_ui.adapter->addItem(tr("(Default)")); + for (const std::string& adapter : modes.adapter_names) + { + m_ui.adapter->addItem(QString::fromStdString(adapter)); + if (current_adapter == adapter) + m_ui.adapter->setCurrentIndex(m_ui.adapter->count() - 1); + } + } + + // fill+select fullscreen modes + { + QSignalBlocker sb(m_ui.fullscreenModes); + + const std::string current_mode(QtHost::GetBaseStringSettingValue("EmuCore/GS", "FullscreenMode", "")); + m_ui.fullscreenModes->clear(); + m_ui.fullscreenModes->addItem(tr("Borderless Fullscreen")); + if (current_mode.empty()) + m_ui.fullscreenModes->setCurrentIndex(0); + + for (const std::string& fs_mode : modes.fullscreen_modes) + { + m_ui.fullscreenModes->addItem(QString::fromStdString(fs_mode)); + if (current_mode == fs_mode) + m_ui.fullscreenModes->setCurrentIndex(m_ui.fullscreenModes->count() - 1); + } + } + + m_ui.enableHWFixes->setEnabled(is_hardware); + onEnableHardwareFixesChanged(); +} + +void GraphicsSettingsWidget::onIntegerScalingChanged() +{ + m_ui.bilinearFiltering->setEnabled(!m_ui.integerScaling->isChecked()); +} + +void GraphicsSettingsWidget::onFullscreenModeChanged(int index) +{ + if (index == 0) + { + QtHost::RemoveBaseSettingValue("EmuCore/GS", "FullscreenMode"); + } + else + { + QtHost::SetBaseStringSettingValue("EmuCore/GS", "FullscreenMode", + m_ui.fullscreenModes->currentText().toUtf8().constData()); + } + + g_emu_thread->applySettings(); +} diff --git a/pcsx2-qt/Settings/GraphicsSettingsWidget.h b/pcsx2-qt/Settings/GraphicsSettingsWidget.h new file mode 100644 index 0000000000..9600b36706 --- /dev/null +++ b/pcsx2-qt/Settings/GraphicsSettingsWidget.h @@ -0,0 +1,49 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#pragma once + +#include + +#include "ui_GraphicsSettingsWidget.h" + +class SettingsDialog; + +class GraphicsSettingsWidget : public QWidget +{ + Q_OBJECT + +public: + GraphicsSettingsWidget(QWidget* parent, SettingsDialog* dialog); + ~GraphicsSettingsWidget(); + + void updateRendererDependentOptions(); + +Q_SIGNALS: + void fullscreenModesChanged(const QStringList& modes); + +private Q_SLOTS: + void onRendererChanged(int index); + void onAdapterChanged(int index); + void onEnableHardwareFixesChanged(); + void onIntegerScalingChanged(); + void onFullscreenModeChanged(int index); + +private: + Ui::GraphicsSettingsWidget m_ui; + + bool m_hardware_renderer_visible = true; + bool m_software_renderer_visible = true; +}; diff --git a/pcsx2-qt/Settings/GraphicsSettingsWidget.ui b/pcsx2-qt/Settings/GraphicsSettingsWidget.ui new file mode 100644 index 0000000000..1a1509ae1b --- /dev/null +++ b/pcsx2-qt/Settings/GraphicsSettingsWidget.ui @@ -0,0 +1,997 @@ + + + GraphicsSettingsWidget + + + + 0 + 0 + 747 + 890 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Renderer + + + + + + Renderer: + + + + + + + + + + Adapter: + + + + + + + + + + + + + 0 + + + true + + + + Display + + + + + + Fullscreen Mode: + + + + + + + + + + Aspect Ratio: + + + + + + + + Fit to Window/Screen + + + + + Standard (4:3) + + + + + Widescreen (16:9) + + + + + + + + FMV Aspect Ratio: + + + + + + + + Off (Default) + + + + + Standard (4:3) + + + + + Widescreen (16:9) + + + + + + + + Interlacing: + + + + + + + + None + + + + + Weave (Top Field First, Sawtooth) + + + + + Weave (Bottom Field First, Sawtooth) + + + + + Bob (Top Field First) + + + + + Bob (Bottom Field First) + + + + + Blend (Top Field First, Half FPS) + + + + + Blend (Bottom Field First, Half FPS) + + + + + Automatic (Default) + + + + + + + + Zoom: + + + + + + + % + + + 1 + + + 300 + + + + + + + Stretch Height: + + + + + + + % + + + 1 + + + 300 + + + + + + + Offset: + + + + + + + + + px + + + 1000 + + + + + + + x + + + + + + + px + + + 1000 + + + + + + + + + + + Integer Upscaling + + + + + + + Bilinear Filtering + + + + + + + Sync To Host Refresh (VSync) + + + + + + + Internal Resolution Screenshots + + + + + + + + + + Rendering + + + + + + Internal Resolution: + + + + + + + + Custom + + + + + Native (PS2) + + + + + 2x Native (~720p) + + + + + 3x Native (~1080p) + + + + + 4x Native (~1440p/2K) + + + + + 5x Native (~1620p) + + + + + 6x Native (~2160p/4K) + + + + + 7x Native (~2520p) + + + + + 8x Native (~2280p) + + + + + + + + Mipmapping: + + + + + + + + Automatic (Default) + + + + + Off + + + + + Basic (Generated Mipmaps) + + + + + Full (PS2 Mipmaps) + + + + + + + + Texture Filtering: + + + + + + + + Nearest + + + + + Bilinear (Forced) + + + + + Bilinear (PS2) + + + + + Bilinear (Forced excluding sprite) + + + + + + + + Trilinear Filtering: + + + + + + + + None (Default) + + + + + Trilinear (PS2) + + + + + Trilinear (Forced) + + + + + + + + Anisotropic Filtering: + + + + + + + + + + Dithering: + + + + + + + + Off + + + + + Scaled + + + + + Unscaled (Default) + + + + + + + + CRC Fix Level: + + + + + + + + Automatic (Default) + + + + + None (Debug) + + + + + Minimum (Debug) + + + + + Partial (OpenGL) + + + + + Full (Direct3D) + + + + + Aggressive + + + + + + + + Blending Accuracy: + + + + + + + + Minimum (Fastest) + + + + + Basic (Recommended) + + + + + Medium + + + + + High + + + + + Full (Very Slow) + + + + + Ultra (Ultra Slow) + + + + + + + + + + Accurate Destination Alpha Test + + + + + + + Conservative Buffer Allocation + + + + + + + GPU Palette Conversion + + + + + + + Preload Textures + + + + + + + Enable Hardware Renderer Fixes + + + + + + + + + + Hardware Fixes + + + + + + Half Screen Fix: + + + + + + + + Automatic (Default) + + + + + Force Disabled + + + + + Force Enabled + + + + + + + + Skipdraw Range: + + + + + + + + + + + + + + + + + + + Auto Flush + + + + + + + Frame Buffer Conversion + + + + + + + Disable Depth Emulation + + + + + + + Memory Wrapping + + + + + + + Disable Safe Features + + + + + + + Preload Frame Data + + + + + + + Fast Texture Invalidation + + + + + + + + + + Upscaling Fixes + + + + + + Half Pixel Offset: + + + + + + + + Off (Default) + + + + + Normal (Vertex) + + + + + Special (Texture) + + + + + Special (Texture - Aggressive) + + + + + + + + Round Sprite: + + + + + + + + Off (Default + + + + + Half + + + + + Full + + + + + + + + Texture Offsets: + + + + + + + + + X: + + + + + + + + + + Y: + + + + + + + + + + + + + + Merge Sprite + + + + + + + Align Sprite + + + + + + + Wild Arms Hack + + + + + + + + + + On-Screen Display + + + + + + OSD Scale: + + + + + + + % + + + 100 + + + 500 + + + + + + + + + Show Notifications + + + + + + + Show Speed + + + + + + + Show FPS + + + + + + + Show Resolution + + + + + + + Show CPU Usage + + + + + + + Show Statistics + + + + + + + + + + Advanced + + + + + + + + Use Blit Swap Chain + + + + + + + Use Debug Device + + + + + + + + + + + + + true + + + + Rendering + + + + + + Extra Rendering Threads: + + + + + + + threads + + + + + + + + + Auto Flush + + + + + + + Mipmapping + + + + + + + Edge Anti-Aliasing + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/pcsx2-qt/Settings/HotkeySettingsWidget.cpp b/pcsx2-qt/Settings/HotkeySettingsWidget.cpp new file mode 100644 index 0000000000..c9f88fa14f --- /dev/null +++ b/pcsx2-qt/Settings/HotkeySettingsWidget.cpp @@ -0,0 +1,84 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" + +#include "Frontend/InputManager.h" +#include "Settings/HotkeySettingsWidget.h" +#include "Settings/ControllerSettingsDialog.h" +#include "InputBindingWidget.h" +#include "QtUtils.h" +#include "SettingWidgetBinder.h" +#include +#include +#include +#include +#include + +HotkeySettingsWidget::HotkeySettingsWidget(QWidget* parent, ControllerSettingsDialog* dialog) + : QWidget(parent) +{ + createUi(); +} + +HotkeySettingsWidget::~HotkeySettingsWidget() = default; + +void HotkeySettingsWidget::createUi() +{ + QGridLayout* layout = new QGridLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + + m_tab_widget = new QTabWidget(this); + + createButtons(); + + layout->addWidget(m_tab_widget, 0, 0, 1, 1); + + setLayout(layout); +} + +void HotkeySettingsWidget::createButtons() +{ + const std::vector hotkeys(InputManager::GetHotkeyList()); + for (const HotkeyInfo* hotkey : hotkeys) + { + const auto category = qApp->translate("Hotkeys", hotkey->category); + + auto iter = m_categories.find(category); + if (iter == m_categories.end()) + { + QScrollArea* scroll = new QScrollArea(m_tab_widget); + QWidget* container = new QWidget(scroll); + QVBoxLayout* vlayout = new QVBoxLayout(container); + QGridLayout* layout = new QGridLayout(); + layout->setContentsMargins(0, 0, 0, 0); + vlayout->addLayout(layout); + vlayout->addStretch(1); + iter = m_categories.insert(category, Category{container, layout}); + scroll->setWidget(container); + scroll->setWidgetResizable(true); + scroll->setBackgroundRole(QPalette::Base); + scroll->setFrameShape(QFrame::NoFrame); + m_tab_widget->addTab(scroll, category); + } + + QWidget* container = iter->container; + QGridLayout* layout = iter->layout; + const int target_row = layout->count() / 2; + + layout->addWidget(new QLabel(qApp->translate("Hotkeys", hotkey->display_name), container), target_row, 0); + layout->addWidget(new InputBindingWidget(container, "Hotkeys", hotkey->name), target_row, 1); + } +} diff --git a/pcsx2-qt/Settings/HotkeySettingsWidget.h b/pcsx2-qt/Settings/HotkeySettingsWidget.h new file mode 100644 index 0000000000..00dfd5d994 --- /dev/null +++ b/pcsx2-qt/Settings/HotkeySettingsWidget.h @@ -0,0 +1,48 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#pragma once + +#include +#include +#include +#include + +class QTabWidget; +class QGridLayout; + +class ControllerSettingsDialog; + +class HotkeySettingsWidget : public QWidget +{ + Q_OBJECT + +public: + HotkeySettingsWidget(QWidget* parent, ControllerSettingsDialog* dialog); + ~HotkeySettingsWidget(); + +private: + void createUi(); + void createButtons(); + + QTabWidget* m_tab_widget; + + struct Category + { + QWidget* container; + QGridLayout* layout; + }; + QMap m_categories; +}; diff --git a/pcsx2-qt/Settings/InputBindingDialog.cpp b/pcsx2-qt/Settings/InputBindingDialog.cpp new file mode 100644 index 0000000000..2e5a18053e --- /dev/null +++ b/pcsx2-qt/Settings/InputBindingDialog.cpp @@ -0,0 +1,230 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" + +#include "EmuThread.h" +#include "QtHost.h" +#include "Settings/InputBindingDialog.h" +#include +#include +#include + +// _BitScanForward() +#include "pcsx2/GS/GSIntrin.h" + +InputBindingDialog::InputBindingDialog(std::string section_name, std::string key_name, + std::vector bindings, QWidget* parent) + : QDialog(parent) + , m_section_name(std::move(section_name)) + , m_key_name(std::move(key_name)) + , m_bindings(std::move(bindings)) +{ + m_ui.setupUi(this); + m_ui.title->setText( + tr("Bindings for %1 %2").arg(QString::fromStdString(m_section_name)).arg(QString::fromStdString(m_key_name))); + m_ui.buttonBox->button(QDialogButtonBox::Close)->setText(tr("Close")); + + connect(m_ui.addBinding, &QPushButton::clicked, this, &InputBindingDialog::onAddBindingButtonClicked); + connect(m_ui.removeBinding, &QPushButton::clicked, this, &InputBindingDialog::onRemoveBindingButtonClicked); + connect(m_ui.clearBindings, &QPushButton::clicked, this, &InputBindingDialog::onClearBindingsButtonClicked); + connect(m_ui.buttonBox, &QDialogButtonBox::rejected, [this]() { done(0); }); + updateList(); +} + +InputBindingDialog::~InputBindingDialog() +{ + Q_ASSERT(!isListeningForInput()); +} + +bool InputBindingDialog::eventFilter(QObject* watched, QEvent* event) +{ + const QEvent::Type event_type = event->type(); + + // if the key is being released, set the input + if (event_type == QEvent::KeyRelease || event_type == QEvent::MouseButtonPress) + { + addNewBinding(); + stopListeningForInput(); + return true; + } + else if (event_type == QEvent::KeyPress) + { + const QKeyEvent* key_event = static_cast(event); + m_new_bindings.push_back(InputManager::MakeHostKeyboardKey(key_event->key())); + return true; + } + else if (event_type == QEvent::MouseButtonPress) + { + unsigned long button_index; + if (_BitScanForward(&button_index, static_cast(static_cast(event)->button()))) + m_new_bindings.push_back(InputManager::MakeHostMouseButtonKey(button_index)); + return true; + } + else if (event_type == QEvent::MouseButtonDblClick) + { + // just eat double clicks + return true; + } + + return false; +} + +void InputBindingDialog::onInputListenTimerTimeout() +{ + m_input_listen_remaining_seconds--; + if (m_input_listen_remaining_seconds == 0) + { + stopListeningForInput(); + return; + } + + m_ui.status->setText(tr("Push Button/Axis... [%1]").arg(m_input_listen_remaining_seconds)); +} + +void InputBindingDialog::startListeningForInput(u32 timeout_in_seconds) +{ + m_input_listen_timer = new QTimer(this); + m_input_listen_timer->setSingleShot(false); + m_input_listen_timer->start(1000); + + m_input_listen_timer->connect(m_input_listen_timer, &QTimer::timeout, this, + &InputBindingDialog::onInputListenTimerTimeout); + m_input_listen_remaining_seconds = timeout_in_seconds; + m_ui.status->setText(tr("Push Button/Axis... [%1]").arg(m_input_listen_remaining_seconds)); + m_ui.addBinding->setEnabled(false); + m_ui.removeBinding->setEnabled(false); + m_ui.clearBindings->setEnabled(false); + m_ui.buttonBox->setEnabled(false); + + installEventFilter(this); + grabKeyboard(); + grabMouse(); +} + +void InputBindingDialog::stopListeningForInput() +{ + m_ui.status->clear(); + m_ui.addBinding->setEnabled(true); + m_ui.removeBinding->setEnabled(true); + m_ui.clearBindings->setEnabled(true); + m_ui.buttonBox->setEnabled(true); + + delete m_input_listen_timer; + m_input_listen_timer = nullptr; + + releaseMouse(); + releaseKeyboard(); + removeEventFilter(this); +} + +void InputBindingDialog::addNewBinding() +{ + if (m_new_bindings.empty()) + return; + + const std::string new_binding( + InputManager::ConvertInputBindingKeysToString(m_new_bindings.data(), m_new_bindings.size())); + if (!new_binding.empty()) + { + if (std::find(m_bindings.begin(), m_bindings.end(), new_binding) != m_bindings.end()) + return; + + m_ui.bindingList->addItem(QString::fromStdString(new_binding)); + m_bindings.push_back(std::move(new_binding)); + saveListToSettings(); + } +} + +void InputBindingDialog::onAddBindingButtonClicked() +{ + if (isListeningForInput()) + stopListeningForInput(); + + startListeningForInput(TIMEOUT_FOR_BINDING); +} + +void InputBindingDialog::onRemoveBindingButtonClicked() +{ + const int row = m_ui.bindingList->currentRow(); + if (row < 0 || static_cast(row) >= m_bindings.size()) + return; + + m_bindings.erase(m_bindings.begin() + row); + delete m_ui.bindingList->takeItem(row); + saveListToSettings(); +} + +void InputBindingDialog::onClearBindingsButtonClicked() +{ + m_bindings.clear(); + m_ui.bindingList->clear(); + saveListToSettings(); +} + +void InputBindingDialog::updateList() +{ + m_ui.bindingList->clear(); + for (const std::string& binding : m_bindings) + m_ui.bindingList->addItem(QString::fromStdString(binding)); +} + +void InputBindingDialog::saveListToSettings() +{ + if (!m_bindings.empty()) + QtHost::SetBaseStringListSettingValue(m_section_name.c_str(), m_key_name.c_str(), m_bindings); + else + QtHost::RemoveBaseSettingValue(m_section_name.c_str(), m_key_name.c_str()); + + g_emu_thread->reloadInputBindings(); +} + +void InputBindingDialog::inputManagerHookCallback(InputBindingKey key, float value) +{ + for (InputBindingKey other_key : m_new_bindings) + { + if (other_key.MaskDirection() == key.MaskDirection()) + { + if (value < 0.25f) + { + // if this key is in our new binding list, it's a "release", and we're done + addNewBinding(); + stopListeningForInput(); + return; + } + + // otherwise, keep waiting + return; + } + } + + // new binding, add it to the list, but wait for a decent distance first, and then wait for release + if (value >= 0.25f) + m_new_bindings.push_back(key); +} + +void InputBindingDialog::hookInputManager() +{ + InputManager::SetHook([this](InputBindingKey key, float value) { + QMetaObject::invokeMethod(this, "inputManagerHookCallback", Qt::QueuedConnection, Q_ARG(InputBindingKey, key), + Q_ARG(float, value)); + return InputInterceptHook::CallbackResult::StopProcessingEvent; + }); +} + +void InputBindingDialog::unhookInputManager() +{ + InputManager::RemoveHook(); +} diff --git a/pcsx2-qt/Settings/InputBindingDialog.h b/pcsx2-qt/Settings/InputBindingDialog.h new file mode 100644 index 0000000000..515febea62 --- /dev/null +++ b/pcsx2-qt/Settings/InputBindingDialog.h @@ -0,0 +1,68 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#pragma once +#include "ui_InputBindingDialog.h" +#include "Frontend/InputManager.h" +#include +#include +#include +#include + +class InputBindingDialog : public QDialog +{ + Q_OBJECT + +public: + InputBindingDialog(std::string section_name, std::string key_name, std::vector bindings, QWidget* parent); + ~InputBindingDialog(); + +protected Q_SLOTS: + void onAddBindingButtonClicked(); + void onRemoveBindingButtonClicked(); + void onClearBindingsButtonClicked(); + void onInputListenTimerTimeout(); + void inputManagerHookCallback(InputBindingKey key, float value); + +protected: + enum : u32 + { + TIMEOUT_FOR_BINDING = 5 + }; + + virtual bool eventFilter(QObject* watched, QEvent* event) override; + + virtual void startListeningForInput(u32 timeout_in_seconds); + virtual void stopListeningForInput(); + + bool isListeningForInput() const { return m_input_listen_timer != nullptr; } + void addNewBinding(); + + void updateList(); + void saveListToSettings(); + + void hookInputManager(); + void unhookInputManager(); + + Ui::InputBindingDialog m_ui; + + std::string m_section_name; + std::string m_key_name; + std::vector m_bindings; + std::vector m_new_bindings; + + QTimer* m_input_listen_timer = nullptr; + u32 m_input_listen_remaining_seconds = 0; +}; diff --git a/pcsx2-qt/Settings/InputBindingDialog.ui b/pcsx2-qt/Settings/InputBindingDialog.ui new file mode 100644 index 0000000000..c951b5a377 --- /dev/null +++ b/pcsx2-qt/Settings/InputBindingDialog.ui @@ -0,0 +1,76 @@ + + + InputBindingDialog + + + Qt::WindowModal + + + + 0 + 0 + 533 + 283 + + + + Edit Bindings + + + true + + + + + + Bindings for Controller0/ButtonCircle + + + + + + + + + + + + + + + + + + + Add Binding + + + + + + + Remove Binding + + + + + + + Clear Bindings + + + + + + + QDialogButtonBox::Close + + + + + + + + + + diff --git a/pcsx2-qt/Settings/InputBindingWidget.cpp b/pcsx2-qt/Settings/InputBindingWidget.cpp new file mode 100644 index 0000000000..6690d533ae --- /dev/null +++ b/pcsx2-qt/Settings/InputBindingWidget.cpp @@ -0,0 +1,371 @@ +/* PCSX2 - PS2 Emulator for PCs +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" + +#include "EmuThread.h" +#include "QtHost.h" +#include "QtUtils.h" +#include "Settings/ControllerSettingsDialog.h" +#include "Settings/InputBindingDialog.h" +#include "Settings/InputBindingWidget.h" +#include +#include +#include +#include +#include +#include + +#include "pcsx2/GS/GSIntrin.h" // _BitScanForward + +InputBindingWidget::InputBindingWidget(QWidget* parent) + : QPushButton(parent) +{ + connect(this, &QPushButton::clicked, this, &InputBindingWidget::onClicked); +} + +InputBindingWidget::InputBindingWidget(QWidget* parent, std::string section_name, std::string key_name) + : QPushButton(parent) +{ + setMinimumWidth(225); + setMaximumWidth(225); + + connect(this, &QPushButton::clicked, this, &InputBindingWidget::onClicked); + + setKey(std::move(section_name), std::move(key_name)); +} + +InputBindingWidget::~InputBindingWidget() +{ + Q_ASSERT(!isListeningForInput()); +} + +void InputBindingWidget::setKey(std::string section_name, std::string key_name) +{ + m_section_name = std::move(section_name); + m_key_name = std::move(key_name); + m_bindings = QtHost::GetBaseStringListSetting(m_section_name.c_str(), m_key_name.c_str()); + updateText(); +} + +void InputBindingWidget::updateText() +{ + if (m_bindings.empty()) + { + setText(QString()); + } + else if (m_bindings.size() > 1) + { + setText(tr("%n bindings", "", static_cast(m_bindings.size()))); + + // keep the full thing for the tooltip + std::stringstream ss; + bool first = true; + for (const std::string& binding : m_bindings) + { + if (first) + first = false; + else + ss << "\n"; + ss << binding; + } + setToolTip(QString::fromStdString(ss.str())); + } + else + { + QString binding_text(QString::fromStdString(m_bindings[0])); + setToolTip(binding_text); + + // fix up accelerators, and if it's too long, ellipsise it + if (binding_text.contains('&')) + binding_text = binding_text.replace(QStringLiteral("&"), QStringLiteral("&&")); + if (binding_text.length() > 35) + binding_text = binding_text.left(35).append(QStringLiteral("...")); + setText(binding_text); + } +} + +bool InputBindingWidget::eventFilter(QObject* watched, QEvent* event) +{ + const QEvent::Type event_type = event->type(); + + // if the key is being released, set the input + if (event_type == QEvent::KeyRelease || event_type == QEvent::MouseButtonPress) + { + setNewBinding(); + stopListeningForInput(); + return true; + } + else if (event_type == QEvent::KeyPress) + { + const QKeyEvent* key_event = static_cast(event); + m_new_bindings.push_back(InputManager::MakeHostKeyboardKey(key_event->key())); + return true; + } + else if (event_type == QEvent::MouseButtonPress) + { + unsigned long button_index; + if (_BitScanForward(&button_index, static_cast(static_cast(event)->button()))) + m_new_bindings.push_back(InputManager::MakeHostMouseButtonKey(button_index)); + return true; + } + else if (event_type == QEvent::MouseButtonDblClick) + { + // just eat double clicks + return true; + } + + return false; +} + +bool InputBindingWidget::event(QEvent* event) +{ + if (event->type() == QEvent::MouseButtonRelease) + { + QMouseEvent* mev = static_cast(event); + if (mev->button() == Qt::LeftButton && mev->modifiers() & Qt::ShiftModifier) + { + openDialog(); + return false; + } + } + + return QPushButton::event(event); +} + +void InputBindingWidget::mouseReleaseEvent(QMouseEvent* e) +{ + if (e->button() == Qt::RightButton) + { + clearBinding(); + return; + } + + QPushButton::mouseReleaseEvent(e); +} + +void InputBindingWidget::setNewBinding() +{ + if (m_new_bindings.empty()) + return; + + const std::string new_binding( + InputManager::ConvertInputBindingKeysToString(m_new_bindings.data(), m_new_bindings.size())); + if (!new_binding.empty()) + { + QtHost::SetBaseStringSettingValue(m_section_name.c_str(), m_key_name.c_str(), new_binding.c_str()); + g_emu_thread->reloadInputBindings(); + } + + m_bindings.clear(); + m_bindings.push_back(std::move(new_binding)); +} + +void InputBindingWidget::clearBinding() +{ + m_bindings.clear(); + QtHost::RemoveBaseSettingValue(m_section_name.c_str(), m_key_name.c_str()); + g_emu_thread->reloadInputBindings(); + updateText(); +} + +void InputBindingWidget::reloadBinding() +{ + m_bindings = QtHost::GetBaseStringListSetting(m_section_name.c_str(), m_key_name.c_str()); + updateText(); +} + +void InputBindingWidget::onClicked() +{ + if (m_bindings.size() > 1) + { + openDialog(); + return; + } + + if (isListeningForInput()) + stopListeningForInput(); + + startListeningForInput(TIMEOUT_FOR_SINGLE_BINDING); +} + +void InputBindingWidget::onInputListenTimerTimeout() +{ + m_input_listen_remaining_seconds--; + if (m_input_listen_remaining_seconds == 0) + { + stopListeningForInput(); + return; + } + + setText(tr("Push Button/Axis... [%1]").arg(m_input_listen_remaining_seconds)); +} + +void InputBindingWidget::startListeningForInput(u32 timeout_in_seconds) +{ + m_new_bindings.clear(); + m_input_listen_timer = new QTimer(this); + m_input_listen_timer->setSingleShot(false); + m_input_listen_timer->start(1000); + + m_input_listen_timer->connect(m_input_listen_timer, &QTimer::timeout, this, + &InputBindingWidget::onInputListenTimerTimeout); + m_input_listen_remaining_seconds = timeout_in_seconds; + setText(tr("Push Button/Axis... [%1]").arg(m_input_listen_remaining_seconds)); + + installEventFilter(this); + grabKeyboard(); + grabMouse(); + hookInputManager(); +} + +void InputBindingWidget::stopListeningForInput() +{ + updateText(); + delete m_input_listen_timer; + m_input_listen_timer = nullptr; + std::vector().swap(m_new_bindings); + + unhookInputManager(); + releaseMouse(); + releaseKeyboard(); + removeEventFilter(this); +} + +void InputBindingWidget::inputManagerHookCallback(InputBindingKey key, float value) +{ + const float abs_value = std::abs(value); + + for (InputBindingKey other_key : m_new_bindings) + { + if (other_key.MaskDirection() == key.MaskDirection()) + { + if (abs_value < 0.5f) + { + // if this key is in our new binding list, it's a "release", and we're done + setNewBinding(); + stopListeningForInput(); + return; + } + + // otherwise, keep waiting + return; + } + } + + // new binding, add it to the list, but wait for a decent distance first, and then wait for release + if (abs_value >= 0.5f) + { + InputBindingKey key_to_add = key; + key_to_add.negative = (value < 0.0f); + m_new_bindings.push_back(key_to_add); + } +} + +void InputBindingWidget::hookInputManager() +{ + InputManager::SetHook([this](InputBindingKey key, float value) { + QMetaObject::invokeMethod(this, "inputManagerHookCallback", Qt::QueuedConnection, Q_ARG(InputBindingKey, key), + Q_ARG(float, value)); + return InputInterceptHook::CallbackResult::StopProcessingEvent; + }); +} + +void InputBindingWidget::unhookInputManager() +{ + InputManager::RemoveHook(); +} + +void InputBindingWidget::openDialog() +{ + InputBindingDialog binding_dialog(m_section_name, m_key_name, m_bindings, QtUtils::GetRootWidget(this)); + binding_dialog.exec(); + reloadBinding(); +} + +InputVibrationBindingWidget::InputVibrationBindingWidget(QWidget* parent) +{ + connect(this, &QPushButton::clicked, this, &InputVibrationBindingWidget::onClicked); +} + +InputVibrationBindingWidget::InputVibrationBindingWidget(QWidget* parent, ControllerSettingsDialog* dialog, std::string section_name, std::string key_name) +{ + setMinimumWidth(225); + setMaximumWidth(225); + + connect(this, &QPushButton::clicked, this, &InputVibrationBindingWidget::onClicked); + + setKey(dialog, std::move(section_name), std::move(key_name)); +} + +InputVibrationBindingWidget::~InputVibrationBindingWidget() +{ +} + +void InputVibrationBindingWidget::setKey(ControllerSettingsDialog* dialog, std::string section_name, std::string key_name) +{ + m_dialog = dialog; + m_section_name = std::move(section_name); + m_key_name = std::move(key_name); + m_binding = QtHost::GetBaseStringSettingValue(m_section_name.c_str(), m_key_name.c_str()); + setText(QString::fromStdString(m_binding)); +} + +void InputVibrationBindingWidget::clearBinding() +{ + m_binding = {}; + QtHost::RemoveBaseSettingValue(m_section_name.c_str(), m_key_name.c_str()); + g_emu_thread->reloadInputBindings(); + setText(QString()); +} + +void InputVibrationBindingWidget::onClicked() +{ + QInputDialog dialog(QtUtils::GetRootWidget(this)); + + const QString full_key(QStringLiteral("%1/%2").arg(QString::fromStdString(m_section_name)).arg(QString::fromStdString(m_key_name))); + const QString current(QString::fromStdString(m_binding)); + QStringList input_options(m_dialog->getVibrationMotors()); + if (!current.isEmpty() && input_options.indexOf(current) < 0) + input_options.append(current); + + QInputDialog input_dialog(this); + input_dialog.setWindowTitle(full_key); + input_dialog.setLabelText(tr("Select vibration motor for %1.").arg(full_key)); + input_dialog.setInputMode(QInputDialog::TextInput); + input_dialog.setOptions(QInputDialog::UseListViewForComboBoxItems); + input_dialog.setComboBoxEditable(false); + input_dialog.setComboBoxItems(std::move(input_options)); + input_dialog.setTextValue(current); + if (input_dialog.exec() == 0) + return; + + const QString new_value(input_dialog.textValue()); + m_binding = new_value.toStdString(); + QtHost::SetBaseStringSettingValue(m_section_name.c_str(), m_key_name.c_str(), m_binding.c_str()); + setText(new_value); +} + +void InputVibrationBindingWidget::mouseReleaseEvent(QMouseEvent* e) +{ + if (e->button() == Qt::RightButton) + { + clearBinding(); + return; + } + + QPushButton::mouseReleaseEvent(e); +} diff --git a/pcsx2-qt/Settings/InputBindingWidget.h b/pcsx2-qt/Settings/InputBindingWidget.h new file mode 100644 index 0000000000..59b4b31c61 --- /dev/null +++ b/pcsx2-qt/Settings/InputBindingWidget.h @@ -0,0 +1,102 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#pragma once +#include "common/Pcsx2Defs.h" +#include "pcsx2/Frontend/InputManager.h" +#include +#include + +class QTimer; + +class ControllerSettingsDialog; + +class InputBindingWidget : public QPushButton +{ + Q_OBJECT + +public: + InputBindingWidget(QWidget* parent); + InputBindingWidget(QWidget* parent, std::string section_name, std::string key_name); + ~InputBindingWidget(); + + void setKey(std::string section_name, std::string key_name); + +public Q_SLOTS: + void clearBinding(); + void reloadBinding(); + +protected Q_SLOTS: + void onClicked(); + void onInputListenTimerTimeout(); + void inputManagerHookCallback(InputBindingKey key, float value); + +protected: + enum : u32 + { + TIMEOUT_FOR_SINGLE_BINDING = 5, + TIMEOUT_FOR_ALL_BINDING = 10 + }; + + virtual bool eventFilter(QObject* watched, QEvent* event) override; + virtual bool event(QEvent* event) override; + virtual void mouseReleaseEvent(QMouseEvent* e) override; + + virtual void startListeningForInput(u32 timeout_in_seconds); + virtual void stopListeningForInput(); + virtual void openDialog(); + + bool isListeningForInput() const { return m_input_listen_timer != nullptr; } + void setNewBinding(); + void updateText(); + + void hookInputManager(); + void unhookInputManager(); + + std::string m_section_name; + std::string m_key_name; + std::vector m_bindings; + std::vector m_new_bindings; + QTimer* m_input_listen_timer = nullptr; + u32 m_input_listen_remaining_seconds = 0; +}; + +class InputVibrationBindingWidget : public QPushButton +{ + Q_OBJECT + +public: + InputVibrationBindingWidget(QWidget* parent); + InputVibrationBindingWidget(QWidget* parent, ControllerSettingsDialog* dialog, std::string section_name, std::string key_name); + ~InputVibrationBindingWidget(); + + void setKey(ControllerSettingsDialog* dialog, std::string section_name, std::string key_name); + +public Q_SLOTS: + void clearBinding(); + +protected Q_SLOTS: + void onClicked(); + +protected: + virtual void mouseReleaseEvent(QMouseEvent* e) override; + +private: + std::string m_section_name; + std::string m_key_name; + std::string m_binding; + + ControllerSettingsDialog* m_dialog; +}; diff --git a/pcsx2-qt/Settings/InterfaceSettingsWidget.cpp b/pcsx2-qt/Settings/InterfaceSettingsWidget.cpp new file mode 100644 index 0000000000..c872d79eb1 --- /dev/null +++ b/pcsx2-qt/Settings/InterfaceSettingsWidget.cpp @@ -0,0 +1,101 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" + +#include "InterfaceSettingsWidget.h" +#include "MainWindow.h" +#include "SettingWidgetBinder.h" +#include "SettingsDialog.h" + +static const char* THEME_NAMES[] = {QT_TRANSLATE_NOOP("InterfaceSettingsWidget", "Native"), + QT_TRANSLATE_NOOP("InterfaceSettingsWidget", "Fusion"), + QT_TRANSLATE_NOOP("InterfaceSettingsWidget", "Dark Fusion (Gray)"), + QT_TRANSLATE_NOOP("InterfaceSettingsWidget", "Dark Fusion (Blue)"), nullptr}; + +static const char* THEME_VALUES[] = {"", "fusion", "darkfusion", "darkfusionblue", nullptr}; + +InterfaceSettingsWidget::InterfaceSettingsWidget(QWidget* parent, SettingsDialog* dialog) + : QWidget(parent) +{ + m_ui.setupUi(this); + + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.inhibitScreensaver, "UI", "InhibitScreensaver", true); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.discordPresence, "UI", "DiscordPresence", false); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.confirmPowerOff, "UI", "ConfirmPowerOff", true); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.saveStateOnExit, "EmuCore", "AutoStateLoadSave", false); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.pauseOnStart, "UI", "StartPaused", false); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.pauseOnFocusLoss, "UI", "PauseOnFocusLoss", false); + + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.startFullscreen, "UI", "StartFullscreen", false); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.doubleClickTogglesFullscreen, "UI", "DoubleClickTogglesFullscreen", + true); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.hideMouseCursor, "UI", "HideMouseCursor", false); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.renderToMainWindow, "UI", "RenderToMainWindow", true); + + SettingWidgetBinder::BindWidgetToEnumSetting(m_ui.theme, "UI", "Theme", THEME_NAMES, THEME_VALUES, + MainWindow::DEFAULT_THEME_NAME); + connect(m_ui.theme, QOverload::of(&QComboBox::currentIndexChanged), [this]() { emit themeChanged(); }); + + dialog->registerWidgetHelp( + m_ui.inhibitScreensaver, tr("Inhibit Screensaver"), tr("Checked"), + tr("Prevents the screen saver from activating and the host from sleeping while emulation is running.")); + + dialog->registerWidgetHelp(m_ui.discordPresence, tr("Enable Discord Presence"), tr("Unchecked"), + tr("Shows the game you are currently playing as part of your profile in Discord.")); + if (true) + { + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.autoUpdateEnabled, "AutoUpdater", "CheckAtStartup", true); + dialog->registerWidgetHelp(m_ui.autoUpdateEnabled, tr("Enable Automatic Update Check"), tr("Checked"), + tr("Automatically checks for updates to the program on startup. Updates can be deferred " + "until later or skipped entirely.")); + + // m_ui.autoUpdateTag->addItems(AutoUpdaterDialog::getTagList()); + // SettingWidgetBinder::BindWidgetToStringSetting(m_ui.autoUpdateTag, "AutoUpdater", "UpdateTag", + // AutoUpdaterDialog::getDefaultTag()); + + // m_ui.autoUpdateCurrentVersion->setText(tr("%1 (%2)").arg(g_scm_tag_str).arg(g_scm_date_str)); + // connect(m_ui.checkForUpdates, &QPushButton::clicked, [this]() { + // m_host_interface->getMainWindow()->checkForUpdates(true); }); + } + else + { + m_ui.verticalLayout->removeWidget(m_ui.automaticUpdaterGroup); + m_ui.automaticUpdaterGroup->hide(); + } + + dialog->registerWidgetHelp( + m_ui.confirmPowerOff, tr("Confirm Power Off"), tr("Checked"), + tr("Determines whether a prompt will be displayed to confirm shutting down the emulator/game " + "when the hotkey is pressed.")); + dialog->registerWidgetHelp(m_ui.saveStateOnExit, tr("Save State On Exit"), tr("Checked"), + tr("Automatically saves the emulator state when powering down or exiting. You can then " + "resume directly from where you left off next time.")); + dialog->registerWidgetHelp(m_ui.pauseOnStart, tr("Pause On Start"), tr("Unchecked"), + tr("Pauses the emulator when a game is started.")); + dialog->registerWidgetHelp(m_ui.pauseOnFocusLoss, tr("Pause On Focus Loss"), tr("Unchecked"), + tr("Pauses the emulator when you minimize the window or switch to another application, " + "and unpauses when you switch back.")); + dialog->registerWidgetHelp(m_ui.startFullscreen, tr("Start Fullscreen"), tr("Unchecked"), + tr("Automatically switches to fullscreen mode when a game is started.")); + dialog->registerWidgetHelp(m_ui.hideMouseCursor, tr("Hide Cursor In Fullscreen"), tr("Checked"), + tr("Hides the mouse pointer/cursor when the emulator is in fullscreen mode.")); + dialog->registerWidgetHelp( + m_ui.renderToMainWindow, tr("Render To Main Window"), tr("Checked"), + tr("Renders the display of the simulated console to the main window of the application, over " + "the game list. If unchecked, the display will render in a separate window.")); +} + +InterfaceSettingsWidget::~InterfaceSettingsWidget() = default; diff --git a/pcsx2-qt/Settings/InterfaceSettingsWidget.h b/pcsx2-qt/Settings/InterfaceSettingsWidget.h new file mode 100644 index 0000000000..6df38d1e49 --- /dev/null +++ b/pcsx2-qt/Settings/InterfaceSettingsWidget.h @@ -0,0 +1,37 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#pragma once + +#include + +#include "ui_InterfaceSettingsWidget.h" + +class SettingsDialog; + +class InterfaceSettingsWidget : public QWidget +{ + Q_OBJECT + +public: + InterfaceSettingsWidget(QWidget* parent, SettingsDialog* dialog); + ~InterfaceSettingsWidget(); + +Q_SIGNALS: + void themeChanged(); + +private: + Ui::InterfaceSettingsWidget m_ui; +}; diff --git a/pcsx2-qt/Settings/InterfaceSettingsWidget.ui b/pcsx2-qt/Settings/InterfaceSettingsWidget.ui new file mode 100644 index 0000000000..2620a71ad2 --- /dev/null +++ b/pcsx2-qt/Settings/InterfaceSettingsWidget.ui @@ -0,0 +1,240 @@ + + + InterfaceSettingsWidget + + + + 0 + 0 + 698 + 740 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Behaviour + + + + + + Inhibit Screensaver + + + + + + + Enable Discord Presence + + + + + + + Pause On Start + + + + + + + Pause On Focus Loss + + + + + + + Confirm Power Off + + + + + + + Save/Load State On Exit/Resume + + + + + + + + + + Game Display + + + + + + + + Start Fullscreen + + + + + + + Hide Mouse Cursor + + + + + + + Render To Main Window + + + + + + + Double-Click Toggles Fullscreen + + + + + + + Disable Window Resizing + + + + + + + + + + + + Preferences + + + + + + Language: + + + + + + + + + + Theme: + + + + + + + + + + + + + Automatic Updater + + + + + + Update Channel: + + + + + + + + + + Current Version: + + + + + + + + + + + + + + Enable Automatic Update Check + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Check for Updates... + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + diff --git a/pcsx2-qt/Settings/OSDSettingsWidget.ui b/pcsx2-qt/Settings/OSDSettingsWidget.ui new file mode 100644 index 0000000000..fd5fe9ce83 --- /dev/null +++ b/pcsx2-qt/Settings/OSDSettingsWidget.ui @@ -0,0 +1,121 @@ + + + OSDSettingsWidget + + + + 0 + 0 + 604 + 469 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + On-Screen Display + + + + + + OSD Scale: + + + + + + + % + + + 100 + + + 500 + + + + + + + + + Show Notifications + + + + + + + Show Speed + + + + + + + Show FPS + + + + + + + Show Resolution + + + + + + + Show CPU Usage + + + + + + + Show Statistics + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/pcsx2-qt/Settings/SettingsDialog.cpp b/pcsx2-qt/Settings/SettingsDialog.cpp new file mode 100644 index 0000000000..049b1ad2b2 --- /dev/null +++ b/pcsx2-qt/Settings/SettingsDialog.cpp @@ -0,0 +1,156 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" + +#include "QtHost.h" +#include "SettingsDialog.h" + +#include "AdvancedSystemSettingsWidget.h" +#include "BIOSSettingsWidget.h" +#include "EmulationSettingsWidget.h" +#include "GameFixSettingsWidget.h" +#include "GameListSettingsWidget.h" +#include "GraphicsSettingsWidget.h" +#include "HotkeySettingsWidget.h" +#include "InterfaceSettingsWidget.h" +#include "SystemSettingsWidget.h" + +#include +#include + +static constexpr char DEFAULT_SETTING_HELP_TEXT[] = ""; + +SettingsDialog::SettingsDialog(QWidget* parent /* = nullptr */) + : QDialog(parent) +{ + m_ui.setupUi(this); + setCategoryHelpTexts(); + + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + + m_interface_settings = new InterfaceSettingsWidget(m_ui.settingsContainer, this); + m_game_list_settings = new GameListSettingsWidget(m_ui.settingsContainer, this); + m_bios_settings = new BIOSSettingsWidget(m_ui.settingsContainer, this); + m_emulation_settings = new EmulationSettingsWidget(m_ui.settingsContainer, this); + m_system_settings = new SystemSettingsWidget(m_ui.settingsContainer, this); + m_advanced_system_settings = new AdvancedSystemSettingsWidget(m_ui.settingsContainer, this); + m_game_fix_settings_widget = new GameFixSettingsWidget(m_ui.settingsContainer, this); + m_graphics_settings = new GraphicsSettingsWidget(m_ui.settingsContainer, this); + + m_ui.settingsContainer->insertWidget(static_cast(Category::InterfaceSettings), m_interface_settings); + m_ui.settingsContainer->insertWidget(static_cast(Category::GameListSettings), m_game_list_settings); + m_ui.settingsContainer->insertWidget(static_cast(Category::BIOSSettings), m_bios_settings); + m_ui.settingsContainer->insertWidget(static_cast(Category::EmulationSettings), m_emulation_settings); + m_ui.settingsContainer->insertWidget(static_cast(Category::SystemSettings), m_system_settings); + m_ui.settingsContainer->insertWidget(static_cast(Category::AdvancedSystemSettings), m_advanced_system_settings); + m_ui.settingsContainer->insertWidget(static_cast(Category::GameFixSettings), m_game_fix_settings_widget); + m_ui.settingsContainer->insertWidget(static_cast(Category::GraphicsSettings), m_graphics_settings); + + m_ui.settingsCategory->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + m_ui.settingsCategory->setCurrentRow(0); + m_ui.settingsContainer->setCurrentIndex(0); + m_ui.helpText->setText(m_category_help_text[0]); + connect(m_ui.settingsCategory, &QListWidget::currentRowChanged, this, &SettingsDialog::onCategoryCurrentRowChanged); + connect(m_ui.closeButton, &QPushButton::clicked, this, &SettingsDialog::accept); + connect(m_ui.restoreDefaultsButton, &QPushButton::clicked, this, &SettingsDialog::onRestoreDefaultsClicked); +} + +SettingsDialog::~SettingsDialog() = default; + +void SettingsDialog::setCategoryHelpTexts() +{ + m_category_help_text[static_cast(Category::InterfaceSettings)] = + tr("Interface Settings
These options control how the software looks and behaves.

Mouse " + "over " + "an option for additional information."); + m_category_help_text[static_cast(Category::SystemSettings)] = + tr("System Settings
These options determine the configuration of the simulated " + "console.

Mouse over an option for additional information."); + m_category_help_text[static_cast(Category::GameListSettings)] = + tr("Game List Settings
The list above shows the directories which will be searched by " + "PCSX2 to populate the game list. Search directories can be added, removed, and switched to " + "recursive/non-recursive."); + m_category_help_text[static_cast(Category::AudioSettings)] = + tr("Audio Settings
These options control the audio output of the console. Mouse over an option " + "for additional information."); +} + +void SettingsDialog::setCategory(Category category) +{ + if (category >= Category::Count) + return; + + m_ui.settingsCategory->setCurrentRow(static_cast(category)); +} + +void SettingsDialog::onCategoryCurrentRowChanged(int row) +{ + Q_ASSERT(row < static_cast(Category::Count)); + m_ui.settingsContainer->setCurrentIndex(row); + m_ui.helpText->setText(m_category_help_text[row]); +} + +void SettingsDialog::onRestoreDefaultsClicked() +{ + if (QMessageBox::question(this, tr("Confirm Restore Defaults"), + tr("Are you sure you want to restore the default settings? Any preferences will be lost."), + QMessageBox::Yes, QMessageBox::No) != QMessageBox::Yes) + { + return; + } + + // TODO +} + +void SettingsDialog::registerWidgetHelp(QObject* object, QString title, QString recommended_value, QString text) +{ + // construct rich text with formatted description + QString full_text; + full_text += "
"; + full_text += title; + full_text += ""; + full_text += tr("Recommended Value"); + full_text += ": "; + full_text += recommended_value; + full_text += "

"; + full_text += text; + + m_widget_help_text_map[object] = std::move(full_text); + object->installEventFilter(this); +} + +bool SettingsDialog::eventFilter(QObject* object, QEvent* event) +{ + if (event->type() == QEvent::Enter) + { + auto iter = m_widget_help_text_map.constFind(object); + if (iter != m_widget_help_text_map.end()) + { + m_current_help_widget = object; + m_ui.helpText->setText(iter.value()); + } + } + else if (event->type() == QEvent::Leave) + { + if (m_current_help_widget) + { + m_current_help_widget = nullptr; + m_ui.helpText->setText(m_category_help_text[m_ui.settingsCategory->currentRow()]); + } + } + + return QDialog::eventFilter(object, event); +} \ No newline at end of file diff --git a/pcsx2-qt/Settings/SettingsDialog.h b/pcsx2-qt/Settings/SettingsDialog.h new file mode 100644 index 0000000000..3b40885a36 --- /dev/null +++ b/pcsx2-qt/Settings/SettingsDialog.h @@ -0,0 +1,101 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#pragma once +#include "ui_SettingsDialog.h" +#include +#include +#include +#include + +class InterfaceSettingsWidget; +class GameListSettingsWidget; +class EmulationSettingsWidget; +class BIOSSettingsWidget; +class SystemSettingsWidget; +class AdvancedSystemSettingsWidget; +class GameFixSettingsWidget; +class GraphicsSettingsWidget; +class AudioSettingsWidget; +class MemoryCardSettingsWidget; + +class SettingsDialog final : public QDialog +{ + Q_OBJECT + +public: + enum class Category + { + InterfaceSettings, + GameListSettings, + BIOSSettings, + EmulationSettings, + SystemSettings, + AdvancedSystemSettings, + GameFixSettings, + GraphicsSettings, + AudioSettings, + MemoryCardSettings, + Count + }; + + explicit SettingsDialog(QWidget* parent = nullptr); + ~SettingsDialog(); + + InterfaceSettingsWidget* getInterfaceSettingsWidget() const { return m_interface_settings; } + GameListSettingsWidget* getGameListSettingsWidget() const { return m_game_list_settings; } + BIOSSettingsWidget* getBIOSSettingsWidget() const { return m_bios_settings; } + EmulationSettingsWidget* getEmulationSettingsWidget() const { return m_emulation_settings; } + SystemSettingsWidget* getSystemSettingsWidget() const { return m_system_settings; } + AdvancedSystemSettingsWidget* getAdvancedSystemSettingsWidget() const { return m_advanced_system_settings; } + GameFixSettingsWidget* getGameFixSettingsWidget() const { return m_game_fix_settings_widget; } + GraphicsSettingsWidget* getGraphicsSettingsWidget() const { return m_graphics_settings; } + AudioSettingsWidget* getAudioSettingsWidget() const { return m_audio_settings; } + MemoryCardSettingsWidget* getMemoryCardSettingsWidget() const { return m_memory_card_settings; } + + void registerWidgetHelp(QObject* object, QString title, QString recommended_value, QString text); + bool eventFilter(QObject* object, QEvent* event) override; + +Q_SIGNALS: + void settingsResetToDefaults(); + +public Q_SLOTS: + void setCategory(Category category); + +private Q_SLOTS: + void onCategoryCurrentRowChanged(int row); + void onRestoreDefaultsClicked(); + +private: + void setCategoryHelpTexts(); + + Ui::SettingsDialog m_ui; + + InterfaceSettingsWidget* m_interface_settings = nullptr; + GameListSettingsWidget* m_game_list_settings = nullptr; + BIOSSettingsWidget* m_bios_settings = nullptr; + EmulationSettingsWidget* m_emulation_settings = nullptr; + SystemSettingsWidget* m_system_settings = nullptr; + AdvancedSystemSettingsWidget* m_advanced_system_settings = nullptr; + GameFixSettingsWidget* m_game_fix_settings_widget = nullptr; + GraphicsSettingsWidget* m_graphics_settings = nullptr; + AudioSettingsWidget* m_audio_settings = nullptr; + MemoryCardSettingsWidget* m_memory_card_settings = nullptr; + + std::array(Category::Count)> m_category_help_text; + + QObject* m_current_help_widget = nullptr; + QMap m_widget_help_text_map; +}; diff --git a/pcsx2-qt/Settings/SettingsDialog.ui b/pcsx2-qt/Settings/SettingsDialog.ui new file mode 100644 index 0000000000..0aa6849191 --- /dev/null +++ b/pcsx2-qt/Settings/SettingsDialog.ui @@ -0,0 +1,227 @@ + + + SettingsDialog + + + Qt::WindowModal + + + + 0 + 0 + 780 + 650 + + + + + 0 + 0 + + + + PCSX2 Settings + + + + + + + 0 + 0 + + + + + 150 + 0 + + + + + 150 + 16777215 + + + + + 32 + 32 + + + + + Interface + + + + + + + + Game List + + + + + + + + BIOS + + + + + + + + Emulation + + + + + + + + System + + + + + + + + Advanced System + + + + + + + + Game Fixes + + + + + + + + Graphics + + + + + + + + Audio + + + + + + + + Memory Cards + + + + + + + + + + + + 500 + 0 + + + + 0 + + + + + + + + + + 0 + 120 + + + + + 16777215 + 120 + + + + true + + + + + + + + + + Preset: Custom + + + + + Preset: Safe (Default) + + + + + Preset: Fast + + + + + + + + Restore Defaults + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Close + + + true + + + + + + + + + + + + diff --git a/pcsx2-qt/Settings/SystemSettingsWidget.cpp b/pcsx2-qt/Settings/SystemSettingsWidget.cpp new file mode 100644 index 0000000000..90f2276c6d --- /dev/null +++ b/pcsx2-qt/Settings/SystemSettingsWidget.cpp @@ -0,0 +1,88 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" + +#include +#include + +#include "EmuThread.h" +#include "QtUtils.h" +#include "SettingWidgetBinder.h" +#include "SettingsDialog.h" +#include "SystemSettingsWidget.h" + +static constexpr int MINIMUM_EE_CYCLE_RATE = -3; +static constexpr int MAXIMUM_EE_CYCLE_RATE = 3; +static constexpr int DEFAULT_EE_CYCLE_RATE = 0; +static constexpr int DEFAULT_EE_CYCLE_SKIP = 0; + +SystemSettingsWidget::SystemSettingsWidget(QWidget* parent, SettingsDialog* dialog) + : QWidget(parent) +{ + m_ui.setupUi(this); + + SettingWidgetBinder::BindWidgetToIntSetting(m_ui.eeRoundingMode, "EmuCore/CPU", "FPU.Roundmode", 3); + m_ui.eeClampMode->setCurrentIndex(getClampingModeIndex(false)); + connect(m_ui.eeClampMode, QOverload::of(&QComboBox::currentIndexChanged), + [this](int index) { setClampingMode(false, index); }); + m_ui.eeCycleRate->setCurrentIndex( + std::clamp(QtHost::GetBaseIntSettingValue("EmuCore/Speedhacks", "EECycleRate", DEFAULT_EE_CYCLE_RATE), + MINIMUM_EE_CYCLE_RATE, MAXIMUM_EE_CYCLE_RATE) + + (0 - MINIMUM_EE_CYCLE_RATE)); + connect(m_ui.eeCycleRate, QOverload::of(&QComboBox::currentIndexChanged), [](int index) { + QtHost::SetBaseIntSettingValue("EmuCore/Speedhacks", "EECycleRate", MINIMUM_EE_CYCLE_RATE + index); + g_emu_thread->applySettings(); + }); + SettingWidgetBinder::BindWidgetToIntSetting(m_ui.eeCycleSkipping, "EmuCore/Speedhacks", "EECycleSkip", 0); + + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.MTVU, "EmuCore/Speedhacks", "vuThread", false); + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.instantVU1, "EmuCore/Speedhacks", "vu1Instant", true); + SettingWidgetBinder::BindWidgetToIntSetting(m_ui.vuRoundingMode, "EmuCore/CPU", "VU.Roundmode", 3); + m_ui.vuClampMode->setCurrentIndex(getClampingModeIndex(true)); + connect(m_ui.vuClampMode, QOverload::of(&QComboBox::currentIndexChanged), + [this](int index) { setClampingMode(true, index); }); + + SettingWidgetBinder::BindWidgetToBoolSetting(m_ui.fastCDVD, "EmuCore/Speedhacks", "fastCDVD", false); + + updateVU1InstantState(); + connect(m_ui.MTVU, &QCheckBox::stateChanged, this, &SystemSettingsWidget::updateVU1InstantState); +} + +SystemSettingsWidget::~SystemSettingsWidget() = default; + +void SystemSettingsWidget::updateVU1InstantState() +{ + m_ui.instantVU1->setEnabled(!m_ui.MTVU->isChecked()); +} + +int SystemSettingsWidget::getClampingModeIndex(bool vu) +{ + if (QtHost::GetBaseBoolSettingValue("EmuCore/CPU/Recompiler", vu ? "vuSignOverflow" : "fpuFullMode", false)) + return 3; + if (QtHost::GetBaseBoolSettingValue("EmuCore/CPU/Recompiler", vu ? "vuExtraOverflow" : "fpuExtraOverflow", false)) + return 2; + if (QtHost::GetBaseBoolSettingValue("EmuCore/CPU/Recompiler", vu ? "vuOverflow" : "fpuOverflow", true)) + return 1; + return 0; +} + +void SystemSettingsWidget::setClampingMode(bool vu, int index) +{ + QtHost::SetBaseBoolSettingValue("EmuCore/CPU/Recompiler", vu ? "vuSignOverflow" : "fpuFullMode", (index >= 3)); + QtHost::SetBaseBoolSettingValue("EmuCore/CPU/Recompiler", vu ? "vuExtraOverflow" : "fpuExtraOverflow", (index >= 2)); + QtHost::SetBaseBoolSettingValue("EmuCore/CPU/Recompiler", vu ? "vuOverflow" : "fpuOverflow", (index >= 1)); + g_emu_thread->applySettings(); +} diff --git a/pcsx2-qt/Settings/SystemSettingsWidget.h b/pcsx2-qt/Settings/SystemSettingsWidget.h new file mode 100644 index 0000000000..09f3d468e3 --- /dev/null +++ b/pcsx2-qt/Settings/SystemSettingsWidget.h @@ -0,0 +1,40 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#pragma once + +#include + +#include "ui_SystemSettingsWidget.h" + +class SettingsDialog; + +class SystemSettingsWidget : public QWidget +{ + Q_OBJECT + +public: + SystemSettingsWidget(QWidget* parent, SettingsDialog* dialog); + ~SystemSettingsWidget(); + +private Q_SLOTS: + void updateVU1InstantState(); + +private: + static int getClampingModeIndex(bool vu); + static void setClampingMode(bool vu, int index); + + Ui::SystemSettingsWidget m_ui; +}; diff --git a/pcsx2-qt/Settings/SystemSettingsWidget.ui b/pcsx2-qt/Settings/SystemSettingsWidget.ui new file mode 100644 index 0000000000..4e33de1ac8 --- /dev/null +++ b/pcsx2-qt/Settings/SystemSettingsWidget.ui @@ -0,0 +1,301 @@ + + + SystemSettingsWidget + + + + 0 + 0 + 648 + 454 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + EmotionEngine (EE, MIPS-IV) + + + + + + Cycle Rate: + + + + + + + + 50% Speed + + + + + 60% Speed + + + + + 75% Speed + + + + + 100% (Normal Speed) + + + + + 130% Speed + + + + + 180% Speed + + + + + 300% Speed + + + + + + + + Cycle Skip: + + + + + + + + Normal + + + + + Mild Underclock + + + + + Moderate Underclock + + + + + Maximum Underclock + + + + + + + + Rounding Mode: + + + + + + + + Nearest + + + + + Negative + + + + + Positive + + + + + Chop/Zero (Default) + + + + + + + + Clamping Mode: + + + + + + + + None + + + + + Normal (Default) + + + + + Extra + Preserve Sign + + + + + Full + + + + + + + + + + + Vector Units (VU) + + + + + + Rounding Mode: + + + + + + + + Nearest + + + + + Negative + + + + + Positive + + + + + Chop/Zero (Default) + + + + + + + + Clamping Mode: + + + + + + + + None + + + + + Normal (Default) + + + + + Extra + Preserve Sign + + + + + Full + + + + + + + + + + MTVU (Multi-Threaded VU1) + + + + + + + Instant VU1 + + + + + + + + + + + + I/O Processor (IOP, MIPS-I) + + + + + + Enable Fast CDVD + + + + + + + + + + Qt::Vertical + + + + 20 + 3 + + + + + + + + + + + diff --git a/pcsx2-qt/pcsx2-qt.vcxproj b/pcsx2-qt/pcsx2-qt.vcxproj new file mode 100644 index 0000000000..1476c6a413 --- /dev/null +++ b/pcsx2-qt/pcsx2-qt.vcxproj @@ -0,0 +1,292 @@ + + + + + + + {2A016F21-87AE-4154-8271-1F57E91408E9} + + + + Application + Unicode + $(DefaultPlatformToolset) + true + true + false + + + + + + + + + + + + + + + + + AllRules.ruleset + $(EXEString) + + + + $(SolutionDir)3rdparty\xbyak;%(AdditionalIncludeDirectories) + $(SolutionDir)3rdparty\freetype\include;%(AdditionalIncludeDirectories) + $(SolutionDir)3rdparty\xz\xz\src\liblzma\api;%(AdditionalIncludeDirectories) + $(SolutionDir)3rdparty\baseclasses;%(AdditionalIncludeDirectories) + $(SolutionDir)3rdparty\zlib;%(AdditionalIncludeDirectories) + $(SolutionDir)3rdparty\libpng;%(AdditionalIncludeDirectories) + $(SolutionDir)3rdparty\glad\include;%(AdditionalIncludeDirectories) + $(SolutionDir)3rdparty\simpleini\include;%(AdditionalIncludeDirectories) + $(ProjectDir);$(SolutionDir)pcsx2;%(AdditionalIncludeDirectories) + + %(AdditionalIncludeDirectories);$(ProjectDir)\Settings;$(ProjectDir)\GameList + Async + Use + PrecompiledHeader.h + NoExtensions + WIN32_LEAN_AND_MEAN;LZMA_API_STATIC;BUILD_DX=1;SPU2X_PORTAUDIO;DIRECTINPUT_VERSION=0x0800;PCSX2_CORE;DISABLE_RECORDING;%(PreprocessorDefinitions) + PCSX2_DEBUG;PCSX2_DEVBUILD;_SECURE_SCL_=1;%(PreprocessorDefinitions) + PCSX2_DEVEL;PCSX2_DEVBUILD;NDEBUG;_SECURE_SCL_=1;%(PreprocessorDefinitions) + NDEBUG;_SECURE_SCL_=0;%(PreprocessorDefinitions) + PCSX2_CI;%(PreprocessorDefinitions) + _M_SSE=0x401;%(PreprocessorDefinitions) + _M_SSE=0x501;%(PreprocessorDefinitions) + + NotSet + StreamingSIMDExtensions2 + AdvancedVectorExtensions2 + false + $(IntDir)%(RelativeDir) + + + Precise + true + /Zc:__cplusplus /Zo /utf-8%(AdditionalOptions) + + + Windows + Yes + comctl32.lib;ws2_32.lib;shlwapi.lib;winmm.lib;rpcrt4.lib;iphlpapi.lib;dsound.lib;%(AdditionalDependencies) + dxguid.lib;dinput8.lib;hid.lib;PowrProf.lib;d3dcompiler.lib;d3d11.lib;dxgi.lib;strmiids.lib;opengl32.lib;comsuppw.lib;%(AdditionalDependencies) + + + + + $(SolutionDir)3rdparty\wxwidgets3.0\$(PlatformName);$(SolutionDir)3rdparty\wxwidgets3.0\include;%(AdditionalIncludeDirectories) + + + + + + + + + + + + + {27f17499-a372-4408-8afa-4f9f4584fbd3} + + + {449ad25e-424a-4714-babc-68706cdcc33b} + + + {bc236261-77e8-4567-8d09-45cd02965eb6} + + + {d6973076-9317-4ef2-a0b8-b7a18ac0713e} + + + {47afdbef-f15f-4bc0-b436-5be443c3f80f} + + + {0fae817d-9a32-4830-857e-81da57246e16} + false + + + {e9b51944-7e6d-4bcd-83f2-7bbd5a46182d} + + + {3fcc50c2-81e9-5db2-b8d8-2129427568b1} + + + {12728250-16ec-4dc6-94d7-e21dd88947f8} + + + {a0d2b3ad-1f72-4ee3-8b5c-f2c358da35f0} + + + {2f6c0388-20cb-4242-9f6c-a6ebb6a83f47} + false + + + {677b7d11-d5e1-40b3-88b1-9a4df83d2213} + false + + + {ed2f21fd-0a36-4a8f-9b90-e7d92a2acb63} + + + {88fb34ec-845e-4f21-a552-f1573b9ed167} + + + {4639972e-424e-4e13-8b07-ca403c481346} + + + {6c7986c4-3e4d-4dcc-b3c6-6bb12b238995} + + + {c0293b32-5acf-40f0-aa6c-e6da6f3bf33a} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Create + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Document + + + + + + + + + + + + + + + + + + + + + + + + + + + NotUsing + + + + + Document + + + Document + + + Document + + + Document + + + Document + + + Document + + + Document + + + Document + + + Document + + + Document + + + Document + + + Document + + + Document + + + Document + + + Document + + + + + + \ No newline at end of file diff --git a/pcsx2-qt/pcsx2-qt.vcxproj.filters b/pcsx2-qt/pcsx2-qt.vcxproj.filters new file mode 100644 index 0000000000..18273a68dc --- /dev/null +++ b/pcsx2-qt/pcsx2-qt.vcxproj.filters @@ -0,0 +1,301 @@ + + + + + {1f18feb4-85f0-455d-927a-d4d6b226d8a5} + + + {1ab68559-2163-40b5-818b-d2a7ff210c12} + + + {11eec1ee-70c1-42ad-8cd1-e7f72f4c8673} + + + {aff229ec-39ea-40d4-b1c9-b5c4a932a2e0} + + + + + Resources + + + Resources + + + Resources + + + Resources + + + Resources + + + Resources + + + Resources + + + Resources + + + Resources + + + + + + + + + moc + + + Resources + + + + moc + + + + + moc + + + + Settings + + + Settings + + + Settings + + + Settings + + + Settings + + + Settings + + + Settings + + + moc + + + moc + + + moc + + + moc + + + moc + + + moc + + + moc + + + moc + + + Settings + + + moc + + + Settings + + + moc + + + moc + + + moc + + + Settings + + + Settings + + + Settings + + + + GameList + + + GameList + + + GameList + + + moc + + + moc + + + moc + + + moc + + + moc + + + Settings + + + Settings + + + moc + + + Settings + + + moc + + + + + Resources + + + + + + + + + + + + + + Settings + + + Settings + + + Settings + + + Settings + + + Settings + + + Settings + + + Settings + + + Settings + + + Settings + + + Settings + + + Settings + + + Settings + + + + GameList + + + GameList + + + GameList + + + Settings + + + Settings + + + Settings + + + + + Resources + + + + + + Settings + + + Settings + + + Settings + + + Settings + + + Settings + + + Settings + + + Settings + + + Settings + + + Settings + + + Settings + + + Settings + + + Settings + + + Settings + + + Settings + + + \ No newline at end of file diff --git a/pcsx2-qt/resources/generate.sh b/pcsx2-qt/resources/generate.sh new file mode 100644 index 0000000000..7fc10c012b --- /dev/null +++ b/pcsx2-qt/resources/generate.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +IFS=" +" + +printf "\n" +printf "\t\n" +for i in $(find . -not -iname '*.sh' -not -iname '*.qrc' -type f | cut -d'/' -f2-99); do + printf "\t\t%s\n" "$i" +done +printf "\t\n" +printf "\n" + diff --git a/pcsx2-qt/resources/icons/AppIcon.png b/pcsx2-qt/resources/icons/AppIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..66a7d5b77692247f54aa2ac06b6db0011271cc51 GIT binary patch literal 675 zcmV;U0$lxxP)P)t-s00013 zfZ;+}r$JVnz}5aqjq^xqtuI}kk)gymRfIZNkwu5=XnVpzW2X%fAYqgLXNcESjQCZW z?3%9sRe;uHmHAAM@m-+jK6c56p!QXlPM`_h_2`Sb@!M zh~6DLXJwZ9iJatJXpnV;vtDnf9VkySL3S`nd|8Frt-=4p*#Ayfb1z(;o3H*iJxfoK z=s{SgXPN(im*!)Y|1DXUK3=C`ZkT9&#Vk^QO_lyPK2s@6a2_Q(7$HBh$N4^a)Js%q z6dE-pGhwU0{z;JeJyoPDH(&?}4o8B`1qKWR2N3}S0|X{7YMlJa-uj}o^O>yks=n`v zo7z5n*G*-%M_;c-U9C`FYO4SM0AzGhPE!B}{`~v(@$KlxxobKrD;y63{{8*@{QUd+ z`S|tp^78QS?CR;~l&As-eM2n0^i%{Tx60HjGoK~xyiW55OY8Bw@`Ua~&?NPGcf z5j%5H4@Q_YcbK}IG8blxVat@0tHsOU zZRRG;?yH}vm#$;OoXs4`zz{Da#j7h5=#XX^Wu+PdGQ?lpAUNDG!zolzI~ih45T_{* z2a6*M2OrEGvGNLjnyDIb2$w}DNqV{_AiJGU%^J;fiAlJ;4FHy37zRwK^Oyht002ov JPDHLkV1meXKhyvK literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/AppIcon@2x.png b/pcsx2-qt/resources/icons/AppIcon@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..00587a305f12b208a28cf6c8d3c755b891234b83 GIT binary patch literal 1554 zcmV+t2JQKYP)4UL0G3|ng0n57GswG z5f&*SF0jZTA;N52n!Sp7b*n_5g{l+6(vVZ zlloMQ?vAAQO>3&N$M<29|ERqGkf!QEM`%}TsX1@Edx)PkQHMK3VOXL6UTJ?hYNbC_ zoefM@NnHeU|J# zRH3)b|45DX1qcTJiE+#8Qaf{(=pZ`*s z|3-@NMO>_Bbe&&zz^T6d2@4P_J8cRK5hN)?Ns#$Pit!B~Jy@atYlhkmBSK`A|BRpQ zN>_y%9Vlj%>uQ47bA!DQ7b}LB$}T%*JyoM$ko`nktUp$zWtaa?kmDUMRw5`t4In+D zw*QT#`&63yN|gSUsrzHA|43=HB0XnHjrM+->uH+*3?e@O0|*HdDa6_TX`B9Tq5e*l z{zhG{s=oiFx&NH7|4U=D%isT8rT=T5|7My0X0QB6jr3}O%t&CdL|m*xW~xG3sVqZt zztsP|(*Lo=|E|LSptS#Bs{c`#|BIyhny>d|miL66^JbRrVv+84kmqoU;f9yWJafiR zXSjxxwMSpCNnou=UaCM?r*M9yLR*|HS(_?SjyFk&w8;N#w*Qf<|5>5`Rh|E`#{SOZ z{KVM!QkV6^*6@9o?R}Q*ho0=5ujZ4b<64H}O^n}=q1-@x(}b1LT7}V+rO;V}&XJ+V zQhLRZpTuc?!)AH8M{2KqjH^a?sAO@ZTxga@Rev)~eH%4dE2d=q00021bW%=J00IdG z{w^vG3I6{6^xv+NR8&1XJ31pE5B~l8`}+C#^6=W!($UVszPq}qrk#+4ersxIS5QJV zE+QNo6B7yk{{8*@{QLX*`S|wr^z`)e^YZfY@9ypE>+0$0=jZ3<G%F|`8yOXYuf-bx00I_CL_t(I%VMBAV47-$S6)P!Gldb4_AXA&y}XP9Ryeh9 zf4yI0vDS9Q8CXp*TeIej>_h>@HD^w4*IJGt4Uhzb(B3uM@+!Bj`N6&a5MS#EnHj| zZpv^4nX;+!iY_$Q+q+*?Vc))e*GeFUEJ$4^b&IFdXWcp(4rV)jY3XiVL$~$o*YDZ0 zCtw=T5Yu(*-f?SKR2@5Zfrs6?XcG{WTFBqty?giN%j?(I*fFr#-{dizpY-6#EhW*p zkw;Ga`F-L@;sjo~E1QAfY9k{9W1IStcIW$V{`1-DZ`}CGiC_2q#*GoS+T1rTUc3^^ z3U&oxGq-Nt>S@GSzhT4qU_mCG4IAF7F)$j--@ckA2JyxU*P};67BSSmyspG*oqPE3 zc@;4xorFm$IV)k|VB)c3hnIj*7$3WE)SZXdHQ8;74;{*9=0Jqh9N!&xV;DIYEm>Gt zE;TS)X0fn*>_rJ{q0nE^{T+;KpAQ`H7Z#2`c8Ow}ao<5Q^!kS{()pwmb6>dJ;&s`8ln&T+@ zcKo>aJT_6Bsg_y$`@I)E_>w&%|KB)P2D~Z+#Te)g0LsV5nAHP?IsgCw07*qoM6N<$ Ef|ySUmH+?% literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/AppIconLarge.png b/pcsx2-qt/resources/icons/AppIconLarge.png new file mode 100644 index 0000000000000000000000000000000000000000..00587a305f12b208a28cf6c8d3c755b891234b83 GIT binary patch literal 1554 zcmV+t2JQKYP)4UL0G3|ng0n57GswG z5f&*SF0jZTA;N52n!Sp7b*n_5g{l+6(vVZ zlloMQ?vAAQO>3&N$M<29|ERqGkf!QEM`%}TsX1@Edx)PkQHMK3VOXL6UTJ?hYNbC_ zoefM@NnHeU|J# zRH3)b|45DX1qcTJiE+#8Qaf{(=pZ`*s z|3-@NMO>_Bbe&&zz^T6d2@4P_J8cRK5hN)?Ns#$Pit!B~Jy@atYlhkmBSK`A|BRpQ zN>_y%9Vlj%>uQ47bA!DQ7b}LB$}T%*JyoM$ko`nktUp$zWtaa?kmDUMRw5`t4In+D zw*QT#`&63yN|gSUsrzHA|43=HB0XnHjrM+->uH+*3?e@O0|*HdDa6_TX`B9Tq5e*l z{zhG{s=oiFx&NH7|4U=D%isT8rT=T5|7My0X0QB6jr3}O%t&CdL|m*xW~xG3sVqZt zztsP|(*Lo=|E|LSptS#Bs{c`#|BIyhny>d|miL66^JbRrVv+84kmqoU;f9yWJafiR zXSjxxwMSpCNnou=UaCM?r*M9yLR*|HS(_?SjyFk&w8;N#w*Qf<|5>5`Rh|E`#{SOZ z{KVM!QkV6^*6@9o?R}Q*ho0=5ujZ4b<64H}O^n}=q1-@x(}b1LT7}V+rO;V}&XJ+V zQhLRZpTuc?!)AH8M{2KqjH^a?sAO@ZTxga@Rev)~eH%4dE2d=q00021bW%=J00IdG z{w^vG3I6{6^xv+NR8&1XJ31pE5B~l8`}+C#^6=W!($UVszPq}qrk#+4ersxIS5QJV zE+QNo6B7yk{{8*@{QLX*`S|wr^z`)e^YZfY@9ypE>+0$0=jZ3<G%F|`8yOXYuf-bx00I_CL_t(I%VMBAV47-$S6)P!Gldb4_AXA&y}XP9Ryeh9 zf4yI0vDS9Q8CXp*TeIej>_h>@HD^w4*IJGt4Uhzb(B3uM@+!Bj`N6&a5MS#EnHj| zZpv^4nX;+!iY_$Q+q+*?Vc))e*GeFUEJ$4^b&IFdXWcp(4rV)jY3XiVL$~$o*YDZ0 zCtw=T5Yu(*-f?SKR2@5Zfrs6?XcG{WTFBqty?giN%j?(I*fFr#-{dizpY-6#EhW*p zkw;Ga`F-L@;sjo~E1QAfY9k{9W1IStcIW$V{`1-DZ`}CGiC_2q#*GoS+T1rTUc3^^ z3U&oxGq-Nt>S@GSzhT4qU_mCG4IAF7F)$j--@ckA2JyxU*P};67BSSmyspG*oqPE3 zc@;4xorFm$IV)k|VB)c3hnIj*7$3WE)SZXdHQ8;74;{*9=0Jqh9N!&xV;DIYEm>Gt zE;TS)X0fn*>_rJ{q0nE^{T+;KpAQ`H7Z#2`c8Ow}ao<5Q^!kS{()pwmb6>dJ;&s`8ln&T+@ zcKo>aJT_6Bsg_y$`@I)E_>w&%|KB)P2D~Z+#Te)g0LsV5nAHP?IsgCw07*qoM6N<$ Ef|ySUmH+?% literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/AppIconLarge@2x.png b/pcsx2-qt/resources/icons/AppIconLarge@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..913fd0faeed84baed99b4fef36466271aa0a75e4 GIT binary patch literal 2380 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 Ta`LWr00000NkvXXu0mjfPE?d; literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/address-book-new-22.png b/pcsx2-qt/resources/icons/address-book-new-22.png new file mode 100644 index 0000000000000000000000000000000000000000..3ceee199b2fe8bece163957eec9f107d238ef36e GIT binary patch literal 681 zcmV;a0#^NrP)F#j zrmVc(-?^ibwz|Tzik7m0lCR+4tcsMZ@#&}Y>!#`BndIM=nx~B5+KP;viG-M9tS&qP zMaYW)000zpQchC<3N#1=sVXom2mb#3$opmU z;%J4o*y$u+7z1G9v$S*G+=hN!eysq&6^2^PF*OJ>CLIX+4Y-{*_Ht251;}nZxr5WJ*R5}rfes?^{M55XT)ZsXAZgw>< P00000NkvXXu0mjf_KRz5 literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/address-book-new-22@2x.png b/pcsx2-qt/resources/icons/address-book-new-22@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..f80cff57f8e7fc20302e8b267b1821c49d999a7b GIT binary patch literal 2130 zcmV-Y2(9;tP)3S96(Wh%mM;AOL{g( zb^rAlCp1_8^8o+!0HUc=|Mem?MRNc36aV!R|Md+2^a%g-2nY-k|Mmy}^aDy;hk}hD z^zdc!=Pt>x8~XB6G(&JRL~<}eZU6NS|MU(uNO=GB1?JlT*3vg#W=~&mn0SN{-zqA_ zq660*8=a&-{q!?8NO#Ah069x}&C4$T^&HLv1ON0F#-jlL^%c$m0sQe29w|kYjRnmB z0G*5p%>e<3k!gS-HFJP@a)P9EfC^}Oou@ek=;9{L1O%(CJ-4?wbb}4f0|S7FQBPcL z$fN-O^%!-63v_`DGeT~1eGAM005U{!%m4txp8#@x3CsWh|MLhpNP0C(c$Klwt$(On zYn05)%>V!Y)#C56$KI;J*>QrVW_6mOy4CL~DVVFur;M}n^Yh#0^5^I0<>lqt+S<*{ z&6=mjouI;^mAdrw^t{gD+1c5pkhQFWs?6K#%h~9dw9%@Dtx{!=y2{>qh^U>gDYnVp zuf*GztjwRBzM+`Bq?5O5d!g}bYsb~)!O`QezRR4X#c+S4#M9&X@=5sdIoRazw7|}o zsmY6)xQLapsEM%p^>o(TX7uhr(cbIO+vlaV&7-f&zR88e&wlakQu0VhsK3{;!P2d{ z&;I^=I z_lb$v-GRx|e)V^E=jLn9)M@YEWc&4F(Kyg@6V{_xn?*tW#jnzhB>=fkSM!Iqw}ua%?E*rKn&p8ffm=j)htgnqDLalOiK z`u1$BTx?frVxLD`!AMiz+(p+dG^jK(sGlpE<`obC003unQchC<1rQtg^%O$=GzXhy6*im*D`(C*ySux)ySwgA0p~yrK!hO@G8iBO=3wEb3peLxcXxMpcjq6z z_qfADo%8nx_wMfd-TS`xJr0U_C|jm#nX<+Hn=*}NaJk%w%2f-wUz}as=e$ZpgYLsy zmn&DUZ~uVh6agdzB*4ERJ)T>?VuvP~nHvx6-MuY4TObgu0Py*IyvGH9NN8NS ze%FpQGm{IF4?9wi?Avhx&$D4(q<9`GMWg+3s8&y^i15xsYC3K^oT*1ZMoKk^pc@K! z;Jcevuikt-IYoo;4qqLqpl>PrcD%qPC>OO_WHZ2`ML2xGiY;$5GBO5|ov2)C*ufeH z;LZRE$>1s}aitoRf*au!vEWK}`X`-kck|w)(P-4#Hy8I_e7WtJ0zFR+BQXUIV}-a0 zwtDjwoi3v~tO7B5!_#xy&Yc#_Q;NvEA$d4R$$(ByX|C#Y?^{o!gwULXn009yK|0Dv z0RkZ++y-VqmzG&LI^Feh6DgtnW*!jqf;D(73Iu&XqJ*jeQw81C9X&eED`E1~|6l1L67zN@#24#jHx;0|Cx21D~iA5Gw^^h&Af_i4!LVQ(x5(3omA+m=B6h zf~yLaM<|R{;&`GA7hwpw_MO>mZZ(M%{L)APrH#en5EIyZN+ae$@A{mTWxmyk6gWH} zBmlwYd7?z7kl8S!OlrVL_>!B^(OKr9p7>IJ38b)0gnITTf^;Ap-yiG|7(rr6FsMP5 z6vAJ_?9JDJu&0lw>RHvGz=HxNOv2T%Xv5k}O3*oyd2%2`?+NaOgqZK-k}6ibog(gjK7NP#CKGZ_&}_ zm8dyG}C z_rAA1Z*8m9YPVY~7L`hsCaKG!wjQJ27P0uTdv1Ju9AfToXd&@L9absw_k~gStauIK zJ~O*;o<}VYs=#EE0;^8CopAd`EcW3%K~$>{#$-@JXpV$9^JpoZMLx_0LY=^0LWOiU zaeAQ-3w+QLM1BRD5W2-hoS*N*5<2%+5ybMgflRh{m53aIIQw8u;pD^xghYA;cA&ot zWa%Z!m$-Nc!HGo6iW=72$ zqXeCjGy(&eEKmX6U@`v%i^cH$zfrstoAFQBWIlA~pLoN50`wIVxJu_YbN~PV07*qo IM6N<$g3@q0iU0rr literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/applications-system-24.png b/pcsx2-qt/resources/icons/applications-system-24.png new file mode 100644 index 0000000000000000000000000000000000000000..4f738c46c4420ad0dbf99cec70168f270b0ab1ad GIT binary patch literal 1151 zcmV-_1c3XAP)uu00000000000HLVGm7cYgm$Efs zokeb_MQx@uWTQD>mo#LgELM#wPk{gc04-RM0000000000000000RjYxox5g(tVe91 zNo$}@V~nh@$A^=-Q+uvueyl!dq(5buG+&u-f1Y%Env$EdMrD+LhM+)RjY?gFG+~}I zUY9gwq&;4eMPiUOV46BwiZo@UG-aeTVxTu)m@izGFkYB9V3{*yq%&ZfGF+21WTP)z zl}%N3Fj|r#LTo5be@atxLQZctTaQaoZa_w2I#PWI4;eINq}|=>xwF&1$Jev3(9X`_ zzron6vdf&M!g&8)1*rmV=LtH-3J z$DXRim!H3(r@oY#zLTK6lAXPZo4J~tw~dstQFNetie z*VEv;i$kn^U)3(3Ux4F};vC_G<(XqD8xwFl# zu+6Kt%&@n}qp!!OsKcC}!grRzqN~51r@Wb>yN;K-jFh>So4Ashw_Jm^f0D9YeX@s? zv4D)Revhz>lC6l2tA&rLgNdhYe4JWslhgn!cmMzZQgl*IQvfqYA`b!z5Cpt{UNIR4 z{{H^{{rvm*@~@#jIyyHe9Txun{{H^`{r&y@{r&v>`uXNZ6$E;FUYI=KY8GffvW`& z`sjLhLC}lQ6)t9}s$-YC+BTGt9`NSoEW?b<*LAp@%9DME)e?$4N$(j(Z?HR^4o7=y z>z?fq@^;_O(^c8)bvjv{y`}oj4R3kIt-LXVzUFD$s$8bD9ezDme*tV27aPihehPW% z&jTx}rvLz(V0kW|sgUQt5^|savL30l+|mdb49fKKHQR6qwrADkjw=+3GB;@2lv}Oq zLlM}KCQ+Fy7aD^x_{&9_Yip%PenMXMOG;{DU*it0J3$a|YQRcYbfk RA$kA+002ovPDHLkV1kRSLVExJ literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/applications-system-24@2x.png b/pcsx2-qt/resources/icons/applications-system-24@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..6c72a6fc0dc0380ae7cad5614a5f14360d45737a GIT binary patch literal 2346 zcmV+_3Dx$AP)4+dzD;beo$9)LP%smM`S-nVm?G)Izd`LMPYr3q>`DjhmNLyhM#G0 zi+zBZZF7ubWq(6RWkE<|K}KRDCpbPvVmLilKSW_4A~HclV5OwGpQO2$m9CAAsB?Xq zlai}~f}CAwg)uctKt^OTHcS!|8YwM5H$GNub&rLJpofj2IXzW2J5fDBTrV|B$i3XQ zvC(gUsJODztg6hYr^?vS=E264&(#+z` z(c#O;-^|0`#mn5q!`sHZ+q}cqrK!l5o4|XBuXKZ}W_qJpahu`Z?B3h!+}P>b(&*9A z+q`;J!zL1u?h?ux}hpu;ptzLAW z+1KdR*yhs6;>gV2$zrWbHxzo6|)4RRUv#-#rx6P`t%$J|QpQ5~x zn7ogfyJ2>qRc)B!;_b-8+P$^b!MxS8xY511&#SJ?wzbQxslleHz^bUek)OPbnz){x zw}h3pmX@-ClCpV?uXKs5V0EAC>+{pn#@oTe*}TQq#lhCHv(US?&b7JA zrmMuIufv<8!knJMnV!0alCX7!qEKp4{=JE-C)~`}+C!_VlWsa%WdjH#Qso{{8*@`}p;Vd}>KUCHeUEua}BpKBo7Z z000GXNkl@77)T3aY;$vT2%>a@ zlz@PMNQ&Lv-R-?oaN+TAj?bU>`#tY{-uK@7A^xV(R(2@byak9Ji*7mw2s5b12HLp$ z4<6jHoz!E4<>$(;U+=G(*ki+ld#PW({1}J&H?@w`mD$MnR%d6Y`)7-;PgWC#so}`E zlqqJvm+7|UovEqEKI#9eu*AYmIpf9Rccv&LVi1k0JMLa;YU)G&8WVGCB*GfI zl7HtWLB)ZZB`$1!ku+l{N@v>lo8^v`m6Z=G%K1}l4J-+_Zn^JG;hWNx4Epb0G&eVU z)J>hn;+F5Le0cWkKA`Y>C4!YDg79#6$CAx+l?=m(Vs2q!A-68Ui&U?VK@kh$TrT$}gx$O0qft>l zGQD*ORZSu!cmO(RPjPt{3*i$dPEv7DeF4-dYZTT}@rR-6#a#?d3)m1Bo0t(F~4@Efwd6=F@%O4TNCCD#4DDx>8mzDn;OeWN=lNY_|$5X zM#B|gp^jzocwu2-lKLP$Lwj`hy_>wya1OR9A&JdqO9j$p=G|Y0n6Z;1A|lFKYS?YF zY>lZT!y#bRf=t=E>`iSoi_MnFF7aXs3P&_=W_>cdSW@F#(gZr!^5(U+hlNE*ug_65Q0p7Avl{{H>gwX+;#o8l>|o52 zwzel10~oYdcI6FD@f~8$Wo0!$c-O1OhQ!1+bJzoA`qZ})X>wUvdwXeV=o74w!Udx| z%F5my5)$J1ylsWH9>xp>XScx4LLXxzDJ?BM0_3{|^=AWI)49i|cLSZiZGf7%NTHMw z?qkT2BVZICu4EDDe;)3+=&W5po_XV~5X!RBmV|zQvv7U>j6jng^%Bzfw5sStPtQY# z9O85~sTh`E3qpR+BJta5EXfXqBaeNQ79D-@z=1QtdDo~2RgtC@xVV7!VBqNJt4nV* zr9aM#tE$R(aBv9Bo^7pWn5-!27_^msZCQdptR#) zh}@W%cz+1{`^yV6gc+jSF(-i|UPM+5Wn_FcH!#3i^qNS#CXC7Plf#T05mUIFY(cop z%we3oW}x)ruqK>8l^e4PyM8^Dc=voJ6UI21`Fb_ka%10}*i}s7WgUB@Vhd(vd zxW$@%M$Wgym|hkE19PUq_6;Kk_R(C7qukIL)*G#4(6G|cHSE>5&%nVWhU2#_hp{t> z^YOz+07u_mJM@QXX(*i?y0_NG#r@=l;duPo@j^Hl;|;(t7#jNZAE41&d9G*;tx^5^ z^#YNAW4;~}%;n+)JieRPQs!Z*-Wmgdqp#+`k%RFjnGA;Lh9$%`0Y0j$zPbhO0!(Ic zOeS;1T1`+KR#(8)Jz4{R3#Uh+%qA}cr2&jqq19bT^d|a3`XqaR0``CP6Vt+6yByhv QW&i*H07*qoM6N<$f&$&8=Kufz literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/16/artboard-2-line.png b/pcsx2-qt/resources/icons/black/16/artboard-2-line.png new file mode 100644 index 0000000000000000000000000000000000000000..d9a8162b217468788f88bcaf5805d80d99b3a497 GIT binary patch literal 165 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf67>k44ofy`glX(f`#Cy6phE&|z zdoi5xumMlYL;0o!9gTex&u&O7yIZ~8b6@q>3lEB$pJ+rD9`8_2eAn?nrx z@stS(&gkQNIm6)zyQxlzf+o}D70yy>8lM)M$@8z;r}L>Uve|zvqjrJmWP7HaAnO@C MUHx3vIVCg!00v<^o&W#< literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/16/book-open-line.png b/pcsx2-qt/resources/icons/black/16/book-open-line.png new file mode 100644 index 0000000000000000000000000000000000000000..cbf19bf8b470171d036d81ac31078fcf90be7634 GIT binary patch literal 147 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf67>k44ofy`glX(f`_q3{v2miKj7zopr01~+=!Tk44ofy`glX(f`^mw{BhE&{I zdtS2Ph=B;hh36v6t*&OqZu2XZn!fV%mCV?(Igc*w@`(|ezUJ%uT6^z*Z?{j{{KR>& z@Ald$Myod+{Acwv<#OmomYSMT`}Zi5V?#0z&7Q@9f^ zEj=$(cJpC683)gF%F=+7(8A5 KT-G@yGywp2b630o literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/16/close-line.png b/pcsx2-qt/resources/icons/black/16/close-line.png new file mode 100644 index 0000000000000000000000000000000000000000..0081fa6ba4ab7167d4cf27d5dfe1f0a2dbce0fea GIT binary patch literal 127 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf67>k44ofy`glX(f`SbDlRhE&{2 zUZ5`E@HEk@KEb1*uwl)24pokW{~dY`9a_br*z3&cGiw9Wp)i306~=s~N~RX3t~v&W Y&ytcR>g%7f0}WyDboFyt=akR{0Awg7WdHyG literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/16/dashboard-line.png b/pcsx2-qt/resources/icons/black/16/dashboard-line.png new file mode 100644 index 0000000000000000000000000000000000000000..93abfc8e78cec488cb3fbc17474cb211797e354b GIT binary patch literal 162 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf67>k44ofy`glX(f`#CW?zn#y4uPJut>2Rr+N z3k)f&GfEjfl9>}&Ii!qb1h_V|Fx#>IOxT~?#{6)>G8qPjk44ofy`glX(f`Ebw%345_#^ z^@4N)i=jx{#m6GcE!JMP>h(UJ856bKDt3D5t4HgN#oZx{hZfG7&dJdAtbqMN&h=-y&R*f+{y`dNLT3w@ dUAk44ofy`glX(f`M0&b7hE&{2 zRuHu?IAVMynTwl&^}6w#^dJ1K-x4$$mhfxrXk6H+*x<-`q|J$4g!M&El%_$#4S$b= zj1sfY^_e7`U~iQZd3IRz33KX!2MiOMxf&Tk44ofy`glX(f`_;|WFhE&`- z+Ov`OfC10pib?h#x-D1A|Cm}cA!~(MFQb;8qsNTb)ko?bFDAKkdmVgMlcP1KH0f=< t{hWy745zGE;<(!QdYX?aZr-s${QY&4y`_$4P6Ewk@O1TaS?83{1OWR5G|T`1 literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/16/dvd-line.png b/pcsx2-qt/resources/icons/black/16/dvd-line.png new file mode 100644 index 0000000000000000000000000000000000000000..64ac224777456657125f4dc82fabeec70c1e2a98 GIT binary patch literal 222 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf67>k44ofy`glX(f`%ayv=}v~2C(lBI_6jyB=g6Lk$q;U*v+$do>q%1e3YNnfBWrX$JAo( zLBV|?6SLk09oYq(?Q7T{2z=vlUUhjw3-=47n%0+vO%IoIEl=ds z4+@x{5D`_osIDmG#gzFj=B5RT`)_-it&bEl-TO+fRMAFG@kzopr0Q90%nE(I) literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/16/file-add-line.png b/pcsx2-qt/resources/icons/black/16/file-add-line.png new file mode 100644 index 0000000000000000000000000000000000000000..41e8b74f001af917d67757db15d50692da313121 GIT binary patch literal 171 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf67>k44ofy`glX(f`q*uWLFRlVIIqc UbIX$FKnoc>UHx3vIVCg!09(&LdjJ3c literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/16/file-line.png b/pcsx2-qt/resources/icons/black/16/file-line.png new file mode 100644 index 0000000000000000000000000000000000000000..5ef1a2ef80e37762ae1a94612eb675c62d5a3a79 GIT binary patch literal 153 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf67>k44ofy`glX(f`1bezThE&{2 zR$!Gdc;nvq_r<06lK+fj5-b=R9j?`vykvDyWZ%)jJfU6fY(w-xb_MnfZ9mq6-R1@w y4+0u4SS}HrlYXP0?Yy#f#DM|^?l}%2BFqfk5>6WzF5d|k44ofy`glX(f`M0&b7hE&{| zdoi5xfC3N0#mOSk44ofy`glX(f`gnPO;hE&`- zdOncR!9l?FqO{|ZUX@cW7aUsVvMm;5kb7``_x|>~PjC0=TovJplBx;!*2why)i^== z!sO}+zr1c42P|>8msEaWd0xXCeu0!NH=4QjpZKwfZ~iQ|zdpYf&I4M(;OXk;vd$@? F2>?hPI}88- literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/16/file-search-line.png b/pcsx2-qt/resources/icons/black/16/file-search-line.png new file mode 100644 index 0000000000000000000000000000000000000000..4cfabe46c34d254cad5e85d955f061d4ee5bfbc4 GIT binary patch literal 199 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf67>k44ofy`glX(f`G^54>c1vW+ek44ofy`glX(f`qk44ofy`glX(f`RC>BNhE&{I zdN!P~*+IbJ;KT-&B`!=t69hOt9GHX(4p{E+IKWuAL2>W*%Kgzbi%n&+=JU2(jmfYT z6e~U`&m1W>Z$*m==hXHpPL;jvr!J@IPI*`2#<2L^#X8>ec_wbtuStn>O@5Y^7{sta oWYfb%HD7~PPR@HJYPXRwEzx}9f63Z?Ku0imy85}Sb4q9e0P!?J`v3p{ literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/16/folder-add-line.png b/pcsx2-qt/resources/icons/black/16/folder-add-line.png new file mode 100644 index 0000000000000000000000000000000000000000..a59e31fe7939afe814794ab70f0f312a2d868c45 GIT binary patch literal 165 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf67>k44ofy`glX(f`#Cy6phE&{I z+waN9tSG?ZeaV7DNQseiVpEI7%`%3dYX|RrKgYjnUvbIh7W>1AM;t{CJ?d|K_o!T6 zz*mdKI;Vst08(8#MF0Q* literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/16/folder-open-line.png b/pcsx2-qt/resources/icons/black/16/folder-open-line.png new file mode 100644 index 0000000000000000000000000000000000000000..06516f703d9acb7a91d9e469e2280f7ec635de4b GIT binary patch literal 184 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf67>k44ofy`glX(f`6nVNhhE&{I z+vmyH?7+j|F2uJ>NQ;qkVgUPE7L_ZEg$zOy1laGq`Fm&P`zH%HnOARL-emOHuXXCh z-@J{#cDU!KUyBkbs0=vHo`0+ZxTvD>8#DBxPXB=#bGb&Pl hi9b9Z6`FB~?bQ+=dG?dH)&Xs2@O1TaS?83{1OV6kLx=zX literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/16/folder-reduce-line.png b/pcsx2-qt/resources/icons/black/16/folder-reduce-line.png new file mode 100644 index 0000000000000000000000000000000000000000..4b2f1bcc92b090298c4d54bbd825f484d26d23b8 GIT binary patch literal 153 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf67>k44ofy`glX(f`1bezThE&{2 zUceP`;DbD$@CHqb0|ku-8F{!HyuK+PWLUwZ(9+1FAkAjaU>L&~HhJax2Ml-g**9n# zBos6_F~8dV(5Bf|tU_*C0@LC8Hv$^YtPBjr{w|SW3~RZ8#xr=j`njxgN@xNAVeBmY literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/16/folder-settings-line.png b/pcsx2-qt/resources/icons/black/16/folder-settings-line.png new file mode 100644 index 0000000000000000000000000000000000000000..63a6999b09cdb474cca1a520ccd8a5865a725a30 GIT binary patch literal 195 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf67>k44ofy`glX(f`)Oxx&hE&{I zdtRF1u!D%p#n&OrEsidIEwbF=>eA9Gz1ZbDzU)-dTN@m+?En9SXRkcVrn`m-p08q` z)LE3>Y*BN?*6~w}|ECM_4*5N#BVroE?qrPct=>XJJ2Bvp00i_>zopr0E%HtT>t<8 literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/16/fullscreen-line.png b/pcsx2-qt/resources/icons/black/16/fullscreen-line.png new file mode 100644 index 0000000000000000000000000000000000000000..0dab73af6951941ca1e067961fcc65f011193212 GIT binary patch literal 137 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf67>k44ofy`glX(f`ID5J{hE&{2 zUchB>;DbDm>;omffQifotl|k44ofy`glX(f`czC)vhE&{2 zULf>?zn#y4EqS2N|XhYMj oeJ9v&at1S`Jr{oIdy0`kaE1S-iyZQCKrk44ofy`glX(f`RC&5MhE&{I zdm%jWh=BmZMRv!4X^lq}1eBH;GIu(jQAphI{Po{`HjD3VHBtdWxiir!{63>gK>hNQ*6S{wSR%GVDNPHb6Mw<&;$Tk_DV(o literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/16/hard-drive-2-line.png b/pcsx2-qt/resources/icons/black/16/hard-drive-2-line.png new file mode 100644 index 0000000000000000000000000000000000000000..128df68598d3fa324b4c167bea576de575e9367a GIT binary patch literal 135 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf67>k44ofy`glX(f`IC{D`hE&{2 zR^Zb~xY5u0Ok44ofy`glX(f`IC{D`hE&|T z+GojlfPv$PpVLwW#!d^Sbd%G$Z*QqToPQy>XI9je17SbY#4H1a!ynJ~oYGX4pjGqx hHvjZfXL`>UF!`t%K1wy(-vKm?!PC{xWt~$(698smE@}V( literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/16/layout-grid-line.png b/pcsx2-qt/resources/icons/black/16/layout-grid-line.png new file mode 100644 index 0000000000000000000000000000000000000000..ca59badebb735ba300a3a3adbcd592d6b48cdfc1 GIT binary patch literal 145 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf67>k44ofy`glX(f`cze1yhE&{2 zUceP`;DbDS3`1Z+!$YQKW|26DMEM4-1_OheS3*9_OSBi+=bol4Qp1qM@u%U;venAx rjc+(VtV}WUImi;@$U9{r69dCX-AKcUS2Og1#xi)i`njxgN@xNAZCNdc literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/16/list-check.png b/pcsx2-qt/resources/icons/black/16/list-check.png new file mode 100644 index 0000000000000000000000000000000000000000..f6f8c1cbf2ff7990b6e54b62c9b7628936652771 GIT binary patch literal 136 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf67>k44ofy`glX(f`IC;7_hE&|z zdcmCWfPsMXMgEgKH3ElEB$Zv9a_@BgYPFAnix^vX8BAeQ5^prmE$R(@_R i>AMzQY^@PLmdIIM)Vu!DQ(+#^GzL#sKbLh*2~7Zkf-O7% literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/16/pause-line.png b/pcsx2-qt/resources/icons/black/16/pause-line.png new file mode 100644 index 0000000000000000000000000000000000000000..1cf3293d07613fd8ce5a8b3b567573be34250185 GIT binary patch literal 107 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf67>k44ofy`glX(f`sCv3MhE&{2 zUZ8GqAb}y7De||W=%4h0hRw1KdjwZ77%ynB`NhbP+%3K#NiX>nPy>UftDnm{r-UW| DhKC-? literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/16/play-line.png b/pcsx2-qt/resources/icons/black/16/play-line.png new file mode 100644 index 0000000000000000000000000000000000000000..975bed6366df4f44c0970cff28d9aab55fc893e6 GIT binary patch literal 146 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf67>k44ofy`glX(f`_;|WFhE&{2 zUZ8Jr;KF%@hWLZ^ZNGW=eu%^{XgbYkUipAwf=!GY6L^*asYBxf8Z0 ty`tfC*XMw}Ogr}eWLR&0u<9j0gHDKo(dS)99D(LCc)I$ztaD0e0sx0)GMoSa literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/16/refresh-line.png b/pcsx2-qt/resources/icons/black/16/refresh-line.png new file mode 100644 index 0000000000000000000000000000000000000000..3dd2cf3e65dc66102e151aba75490bb2cfc8747e GIT binary patch literal 208 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf67>k44ofy`glX(f`ba}cshE&{| zdO^Cu*-(J>!ef!;7FSpLl~(EXUfyM**L(U}>g!LhChhpGQ~vGteey?Qfq zWTD`+*|Sw+rrPYC!!So@H`4(%Z8?D#x(bB zEo?IXswO3dr`#@n^)uwk8Rnw`%lO|fyCAx0mE^8vzxMmD3ok44ofy`glX(f`^m@8DhE&{| zdM-M#Ie>@Zg0iB}goYN+70t2iu3UUO{JNUY9V}qr+qBSxF8k44ofy`glX(f`1bezThE&{2 zULf@2zz2EebMBMai?|yE6e}6e%x+k7fRRIxi;v-GgVzV`0*CMhrW1h;@3?cgxEfSg z#1g_EG#Jd|Vf~qq!{7Q-K=6;_UwI8LQ3i%t^THSTu9&(3Xgq_btDnm{r-UW|(ZVm( literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/16/screenshot-2-line.png b/pcsx2-qt/resources/icons/black/16/screenshot-2-line.png new file mode 100644 index 0000000000000000000000000000000000000000..539e4341ccf56cd44852cf007d960cdd7a24a525 GIT binary patch literal 197 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf67>k44ofy`glX(f`)O)%(hE&{I zd%-iY#X+F;p}&)NM@yN3Dx>;DCZR_Pg$~{w%|{q5R_xqZ{J4JkEph&dP97$?>C8@n zEUR|i`rV>@G~51>hlj-_&rGYboK^hy-6rclygxs1zdy&ypMe6i;wx=8UtTRe`|#b( uCJ%nJA2pm&TF$w?*REfAl}*Msj{Md0EvG!`_L>583WKMspUXO@geCwo)=Lcl literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/16/sd-card-line.png b/pcsx2-qt/resources/icons/black/16/sd-card-line.png new file mode 100644 index 0000000000000000000000000000000000000000..4df0dcc58916919bcf2cd8664e76661ed218db7d GIT binary patch literal 144 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf67>k44ofy`glX(f`czL=whE&{2 zR$!Gdc;nvmT2kb~()-3S2^I|P|0QKEEER8%aL{BHa0^|)Fp-UOQGx_Ru_TA41M}g~ qgLNSta+9Pb_!b=GbqGviVE8-Bev9kwYfV5?89ZJ6T-G@yGywpb)ha0f literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/16/settings-3-line.png b/pcsx2-qt/resources/icons/black/16/settings-3-line.png new file mode 100644 index 0000000000000000000000000000000000000000..f7a85502abb8a5f002bc1628489b8c1a5d4308b7 GIT binary patch literal 229 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf67>k44ofy`glX(f`%=dJ045_#^ z^}O_g!v-R356)})6?W~`_bXj>`dP^Gom=$gdcVH3!$j}-t=j$HURM5_XFTI9o4p60 zZ?Q+sl$h9@T{^GL#KTXn_T=Da72#LB!?-EnW1y%}K#A}Qvvw}ESVoDt3wB25ERfzS zReIq}n6k44ofy`glX(f`w0gQYhE&{| zdcnPsSy6=Hf;uCohl7_&56hAV357x`M-F;1u-h;H^3N~0?)&Oxm0Fq!5({=*?w#H6 z@cp;%RVy~=DMsEX6h7eRvHbW^XNK2PuJtTubl%NV@zij^ukUAq8kKV1g(@s4>6-RHI#1sJeMZw5Mu!PC{xWt~$(69Bwb BPqzR7 literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/16/tv-2-line.png b/pcsx2-qt/resources/icons/black/16/tv-2-line.png new file mode 100644 index 0000000000000000000000000000000000000000..4963c3dbdd144c05cac101796a289d9a2c11b11b GIT binary patch literal 136 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf67>k44ofy`glX(f`IC;7_hE&{2 zUcfcuzz2EuIShdX4G)=`nblaj6 literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/16/volume-up-line.png b/pcsx2-qt/resources/icons/black/16/volume-up-line.png new file mode 100644 index 0000000000000000000000000000000000000000..97c692a12c915b3557f9bed95e8e5fb31fd44021 GIT binary patch literal 215 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf67>k44ofy`glX(f`O!Rbd45_%a z_JU*rbD+SnkMl2Eti5b?^rhw1m42m}F;UZxJ?%YxOttW4>CAiU%Qfp~{r(X%XLh2g zJx90Hw`b?X)6{1(H7<)e_{8!0+MwW!u*SWtWnNyt&87SJGEOajkpBA?=j2VlS1;Hn zR&w{P2FK|LLt{&i%*&>&6V&cl|3302NA~rx+4DJ%`?G5PpKjIC{ek2B3adNcmQJz; PI*!59)z4*}Q$iB}w31uL literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/16/window-2-line.png b/pcsx2-qt/resources/icons/black/16/window-2-line.png new file mode 100644 index 0000000000000000000000000000000000000000..411cb6b17b2e6403e6d2fff6e5b17b3c51ce4002 GIT binary patch literal 141 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf67>k44ofy`glX(f`xO=)dhE&{2 zUceP`;DbDS3`1Z+!$YQKW;K>Bxdtr;ClQ8Ayh2A;GOW4J;K}G`E~9XpH=uBrmc{vm ne%5*InllAM)&IvGA9Px#(@8`@RCwCekr9-{Fc5}6r#QtaPI0-qT%GE)E?bwY%f+Q}*>l--Xt`>n~m zd%@dpY~Fm+zZw1ok)1okOdJ@-7em}MTsTLRz^?HHNK#fDX1*2T;E8dPK@@$-+j*&j zgrZ4~?f66OMMHs^7gETQCmJ@4AHrotm2zO*Xo%VpyY8uerDMU^@E`U?pra^dPxkkt zw0T?zN*cz66`h5_f15GCsU3$-2Nd3!y0%fd`uop)|MIF=SxrxvWNHXBpTX1B&t;ucLK6Vi CNjoM0 literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/24/dashboard-line.png b/pcsx2-qt/resources/icons/black/24/dashboard-line.png new file mode 100644 index 0000000000000000000000000000000000000000..d8e7b159fdfcb4d874088778c62cda85da4712a8 GIT binary patch literal 122 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1#^NA%Cx&(BWL^R}CY~;iAr-fh zCH6e9*E*u+wv#QzY?s7Z69oldlLV$iQZMBBXEdb!JG5n95^ssG_m}?mMs^0)jr$!x TU#{u_>SyqD^>bP0l+XkKfY2qR literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/24/disc-line.png b/pcsx2-qt/resources/icons/black/24/disc-line.png new file mode 100644 index 0000000000000000000000000000000000000000..4c0fc6766d9957f6b522bbd9740e5dfc621c74c0 GIT binary patch literal 293 zcmV+=0owkFP)Px#-$_J4RCwCO(oxZyKokYwPXZDuAr2A=>!3Jj9Rvr(L2(c%#R2OeI5>R@nLE?) z*#FB@cIUA7EcgPWhYfTO-<@hU5djggX+Bs)bhFD5=pugmhgrnrErLnJOx470qy~Gt2Yzf@G(FaW34-uK2J}P(r|HD#SJ)WrUWkHPd)sbmt7601J*w^(7g;I z=OfkIzlpV$o=WN6URI`BdK8;8T{P;#?316OE9&d7#=2KOzF!8s4ep-m>1t$icQqJX r{jkZ|uuT{B*EOe_#VTU8X#Tsuw||Ar-fh zB@_}42%HZ%zd@ekBLgE_^@2q|d7exDQ+Bj@z&L3WH;=T0RDj96;{p6)^A1cn@Ss7! zu`R;%fso`gDS;fREQf|b-i)9f3ae~hO0lT}jXgbGs=cE}56EKhboFyt=akR{0QFWc AEC2ui literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/24/download-2-line.png b/pcsx2-qt/resources/icons/black/24/download-2-line.png new file mode 100644 index 0000000000000000000000000000000000000000..902c8ee97e7f8815bd1d1066b9879f0eadc7b650 GIT binary patch literal 141 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1#^NA%Cx&(BWL^R}?w&4=Ar-fh zC9EFU3j}!{UeU&~z{qDugNZIThou7J?jiyH>D|H;rar#faDgePx#*-1n}RCwCO(i?#DKoG$3pVK(i>2qmZzICd*H!iJ9<8)5vl66U3?p)?D+ierP z`;X20T``r8f}bH(rKpWd;@?%ZUK#M1|X#X#CD4&Ye@M%;G=P0MIX?&)TT)P z+LqM)p~+6u4SkVjp$+&1?Of&PaNkbd;nw5VDEqcHOS%ay;nqK2A2Pc-3*qc)*>{Ix lm7T1LLyAe2QZ*@logW}mk^!n4319#K002ovPDHLkV1g(ue*gdg literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/24/file-add-line.png b/pcsx2-qt/resources/icons/black/24/file-add-line.png new file mode 100644 index 0000000000000000000000000000000000000000..3b90629c80d8a2b52fadf3a98f08f6c923304fe4 GIT binary patch literal 140 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1#^NA%Cx&(BWL^R}Zk{fVAr-fh z7fA0o@Ijt^PJgIv%&ZM!Eo?SyXVpWQowyTuIdqp~{peS8oZw&2dn38Uhu8V^Wex)t lQ-L;(2Mmr+r6pdlFx;$Iw(Fu{y)@8722WQ%mvv4FO#mG4DenLP literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/24/file-line.png b/pcsx2-qt/resources/icons/black/24/file-line.png new file mode 100644 index 0000000000000000000000000000000000000000..e891c3e418843080852cd30b190346cd40a66b10 GIT binary patch literal 137 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1#^NA%Cx&(BWL^R}&YmugAr-fh zB^V|g_#m%s{k)xSq}DsY{l_HyKMQK7r+ooIJ_Ai={Z6 jMG6`w6fNMgVr01VV$$O!0R@IY;}|?${an^LB{Ts5cata< literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/24/file-list-line.png b/pcsx2-qt/resources/icons/black/24/file-list-line.png new file mode 100644 index 0000000000000000000000000000000000000000..1efd53e3f1df412f8c912ed970868c34bca598ef GIT binary patch literal 119 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1#^NA%Cx&(BWL^R}hMq2tAr-fh z7f4$i_%NU0PXDTh_K6)U8Tlm~cxUWWj6A$T-tDi$Dz-xtj_7cOII1%+l$31qIb^Np Q3e?Qt>FVdQ&MBb@06L5#fdBvi literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/24/file-reduce-line.png b/pcsx2-qt/resources/icons/black/24/file-reduce-line.png new file mode 100644 index 0000000000000000000000000000000000000000..ad81c1c631155f61b5572fdd3e7eb97a76c05f6a GIT binary patch literal 138 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1#^NA%Cx&(BWL^R}E}kxqAr-fh z7fA0o@Ijt^PJgIv%&ZM!Eo?SyXVpWQowyTuIdqp~{pi;T*C${mOQos(X-_>4XCYExxn_F0^j zj~tY}ZBBHu$Fj*CX_n7qfxFkz)*> zFDCUc`osvNUz%Pq!CdJX#}nQXxf%c1ADy&Xn9#T8is+Q^?P7)wi;e-E#o+1c=d#Wz Gp$PzoQ%W`f literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/24/file-settings-line.png b/pcsx2-qt/resources/icons/black/24/file-settings-line.png new file mode 100644 index 0000000000000000000000000000000000000000..3ab5164f8399ac5edc923d6010966e7c8fb7197d GIT binary patch literal 198 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1#^NA%Cx&(BWL^R}4W2HJAr-fd zUPw=DHDGAHsLaY~ku#@(`OyN86_f4QzrWr4{coMT$D`!m!pmRJiFsmW$+Eo6V_h?I zk+#6y)PO%S4KYl;9mmq9AIO*x+I&BIfv8|;zS5$dJQ|DGLq+0vq6Gi5&F#ooAHVC= vsw|ecUuzr!ACxt$Z_bu&3S1Xbn-RQRf7?-AY0nuzw=j6R`njxgN@xNA)I_ z5?kGk-#d+=1NV%nPP4cQhs* zuZgacUQlv1E&L{PGFlfQ|e1LhOnseKT99xiUQrl;OXk;vd$@?2>^KF BK{o&Z literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/24/folder-add-line.png b/pcsx2-qt/resources/icons/black/24/folder-add-line.png new file mode 100644 index 0000000000000000000000000000000000000000..0980dd6cd7d12e7a6b9885f446ab2d812daa7e03 GIT binary patch literal 143 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1#^NA%Cx&(BWL^R}o}Mm_Ar-fh zCDuG(I9xAMG0%}kX3XD%lc8 p<}IuC5oQlG5=i6-F|}Z1kU28{$?}WAzCc46JYD@<);T3K0RX++D!~8% literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/24/folder-open-line.png b/pcsx2-qt/resources/icons/black/24/folder-open-line.png new file mode 100644 index 0000000000000000000000000000000000000000..85eab4a3629f1fb4eb6c64888a43d057c7540380 GIT binary patch literal 184 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1#^NA%Cx&(BWL^R}MV>B>Ar-fh z6D5{BU^rYaQZdhwVSQCg(4i;+sRs-zzA%X?e@o6#Si&019On?s%i*xIjnzTO-pAqU zhlT}gtSP))4u2SzOY<~r;&KM literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/24/folder-reduce-line.png b/pcsx2-qt/resources/icons/black/24/folder-reduce-line.png new file mode 100644 index 0000000000000000000000000000000000000000..8e983fe3dc5a9091fe074288b7682a8b1a394925 GIT binary patch literal 135 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1#^NA%Cx&(BWL^R}j-D=#Ar-fh zCDuG(I9xAMG0%}yB=gB~A)gd`CvyQ4pIr6l8;i^L>s#|o?ed#*p+H!1`i}<=hecvH z&N!g1W^jkI>7ub?x4_$E$EEkz?_7R|TPSMw%Ng>|J~FZ>F<($*vXfcz_to(;0!nx9 xm$?0&d&OV=^*jZOhj*8Mobik~Qu}ZU<4u?K@6GeL9e|Eu@O1TaS?83{1OUOYN!9=W literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/24/fullscreen-line.png b/pcsx2-qt/resources/icons/black/24/fullscreen-line.png new file mode 100644 index 0000000000000000000000000000000000000000..14b0cd18d30c4b770c8a4b9a421b20838a8df2f9 GIT binary patch literal 104 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1#^NA%Cx&(BWL^R}N}eu`Ar-fh zCDuH!XL>YwaR4);L567$)1d^5BPOz+4nJUFc)!Yj<1+{QGN5V(Pgg&ebxsLQ026Z_ AEdT%j literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/24/function-line.png b/pcsx2-qt/resources/icons/black/24/function-line.png new file mode 100644 index 0000000000000000000000000000000000000000..772a837751dce864512eb9dea08f6b71a4cc5c49 GIT binary patch literal 105 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1#^NA%Cx&(BWL^R}%APKcAr-fh zCH6e9*E*u+wv#QzY}XBis}9ctqnW`H`IzPUyQ{IYwyUuu?W?)wa%H#t ybFV@Lxpi@I@(LMW4(*H6%VfJ-m@!GVkf|}fZ%WMJyBt8*FnGH9xvXUboFyt=akR{03IVF8vpmdKI;Vst0J&Qo1poj5 literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/24/layout-grid-line.png b/pcsx2-qt/resources/icons/black/24/layout-grid-line.png new file mode 100644 index 0000000000000000000000000000000000000000..8a0f8601153cfbe8b9e646e380511e9ecd889328 GIT binary patch literal 114 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1#^NA%Cx&(BWL^R}I-V|$Ar-fh zCDuG(I9#vvpsuCuzl)PZL-B{Mo-74EzP<{HUUng4fq<(ISQz$4?sKUK&J6_WWAJqK Kb6Mw<&;$T;lp$^a literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/24/list-check.png b/pcsx2-qt/resources/icons/black/24/list-check.png new file mode 100644 index 0000000000000000000000000000000000000000..1f92a58f0f1d817998a1f8eed6494ca23922dac7 GIT binary patch literal 108 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1#^NA%Cx&(BWL^R}YMw5RAr-fh zCH5?6Tqwuk$?)G%;Lssf%_|oe+P1JRK4>MiX7NET2Hm=h*>hjacLVBR@O1TaS?83{ F1OSNlp=$H6w+LI-5)!-5%1OF8BlSl7PUI0|X;OXk;vd$@?2>{~t8Q%Z^ literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/24/play-line.png b/pcsx2-qt/resources/icons/black/24/play-line.png new file mode 100644 index 0000000000000000000000000000000000000000..6518c4925130719f06bb3da8d52db84b49a74d86 GIT binary patch literal 171 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1#^NA%Cx&(BWL^R}sh%#5Ar-go zPO@ZdFkoO-UMQr+%IV?A)OUc%$8aF)^1LZ6}Rqf7xg`PGvlI3v{=SA)^y`Jb7SA1+sRPR=%W48 StLi(@LIzJ)KbLh*2~7Z7+c{+b literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/24/refresh-line.png b/pcsx2-qt/resources/icons/black/24/refresh-line.png new file mode 100644 index 0000000000000000000000000000000000000000..10be80e86796bb2c1797e2c6cf2941fd28a47a9d GIT binary patch literal 276 zcmV+v0qg#WP)Px#&PhZ;RCwCO(;1+pPyhzt&nc&za=I>)%jEK&Q!X!;$!T2fb?Lg4OS$~BZ>I(4 zeLvONH}9bH=57^}Pd>QRn54+8b)psV)M-HN)gt1}p~|LLdbNo`;iht?u}L|xMep2A z%>L4eRps2{-!VRm7@h0>Enh@LeL1PgZ~2+KbCKfXYW_d-+f&?}X+*^0w^4M?jpe3v zuIFFd$}X47Nzq#x218( zg2GvgBQlO(x7x@gP{_K~BUr=M)#cvZAa$j0pBIX6bvwY*NMlhcn)6iC4Co z+8viHw*Ot!imBNd?ONMhwcDif)q-o$dyYKJH2bl`{!)5j{RE-Z?LhA^ Nc)I$ztaD0e0stEcYHk1k literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/24/save-3-line.png b/pcsx2-qt/resources/icons/black/24/save-3-line.png new file mode 100644 index 0000000000000000000000000000000000000000..399fa0ae091685bb39aacc6fb23034a3da04cb3c GIT binary patch literal 137 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1#^NA%Cx&(BWL^R}&YmugAr-fh zCH6dEI9zX2VAoc)LnKj`gLj71!bO}3RZJJ95~4gV9&oVbosr;R`k;?jE8Btja1?W5 i$Hc}D&I`H@axui^onb$iqIef*9D}E;pUXO@geCyPzbYI6 literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/24/screenshot-2-line.png b/pcsx2-qt/resources/icons/black/24/screenshot-2-line.png new file mode 100644 index 0000000000000000000000000000000000000000..8ea02beec6c43e85237794d62967a5f59907d766 GIT binary patch literal 181 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1#^NA%Cx&(BWL^R}`JOJ0Ar-gI zP6}l_pdjE7&!QyoQmUWrLV`&HTk-ebQg!d9?3S9fJ$~&6XFjfdaS!*jq}@8yp6b!< zbjW6BN%sQl2UmUNm(AVC5b??BvdVW?#}8LHYNUBPnBQ#Waug0yP?8rZS$y%%`{~E# eZd-iiD%0jYZAZHvR;>eC&EV%AUY|#Se9Jhq8#L8@nVBV}3=Xbn3*`Nw$Y#;t eZy7KxkAY!UcfXkU^gt${Sqz@8elF{r5}E+4d?b(n literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/24/settings-3-line.png b/pcsx2-qt/resources/icons/black/24/settings-3-line.png new file mode 100644 index 0000000000000000000000000000000000000000..e90047dcd3a3c6c5c234495a5aecf7f29a2d1596 GIT binary patch literal 289 zcmV++0p9+JP)Px#+et)0RCwCO(^p}`Fc5&@4}u^BN*Kft1i=hqCN_hbK?0!+Vg?q2>t5Hrf4%AU z@-I{;A)Hhn;wWz4@nil&Qxr|S6=QI)cMw7kdLf)$|Be#feHu*#P__I2&Jz}4GUybK zDLR8mIA#Cm-l7RLC$t3=qg5Cc_wt-n|EP)cecscfM)#hu5mw+f+BW@c$Oa)BG(eLL z^uqk{lycFtCQJ%on~Sc(>Ma(b%O9h?ojkamy-`?RTaER}YOJrV29u+~EHB?|xRxYx nJw>q!^%o9^Y7xSs`UfrmVRw^diisd000000NkvXXu0mjfZisb) literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/24/shut-down-line.png b/pcsx2-qt/resources/icons/black/24/shut-down-line.png new file mode 100644 index 0000000000000000000000000000000000000000..295537559103910803ea5a835050a978dc7ccad1 GIT binary patch literal 258 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1#^NA%Cx&(BWL^R}dpunnLn>|^ zoy4BlY9Qj;&!Q5-Bvjy_@}R{^flrFlLPO{ggN#Ds$6NMA|L>Vs%bbv|@evk2&u+8d zy|J%NnX63Gz46X7`;Ixe;qLzfZl2n>yyxP&ZeC6!CF#SFi`&06>k9wVc`!4h{B*{% zd5mYi`1PGuYj=eR_wApWoj||DV|xGq literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/24/volume-up-line.png b/pcsx2-qt/resources/icons/black/24/volume-up-line.png new file mode 100644 index 0000000000000000000000000000000000000000..37d71d919f5f042bacc6e51952615831e1b9a351 GIT binary patch literal 265 zcmV+k0rvihP)Px#!%0LzRCwCe(OH$kFaSp3PeLRhp%RcV65?QxP$%GEkbr~M0XS$K{PWhF)37{q zvoANR@1fFA9K4~JWY?Y)FBsfqC(L4ko@x;hI%U)`inwe-aaO=lL;-H1g%g`_5r2TI zs6d;%I-&{1L!3Yr*CsT!p53%TOrHhf-g-X^2a#6$Ec}SuR-5;iUb;&!+oeZwrGM66 zO&F|Zz$_~CA9vma1_e~HDmtoFny_h$7@=qI=!Bb?y`-4C9cIN_s_)?+`oe^_7(8T$ P00000NkvXXu0mjf2Uu+; literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/24/window-2-line.png b/pcsx2-qt/resources/icons/black/24/window-2-line.png new file mode 100644 index 0000000000000000000000000000000000000000..bae211995ef1bd42ccec8bfac142448e1077b99c GIT binary patch literal 123 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1#^NA%Cx&(BWL^R}rk*a2Ar-fh zCDuG(I9#vvpsuCu{}IO!UKWNrUWu~F3mb#f1=-oHrns42&|ck4UjKx9jP7LeL$-D$|sy$sCLn>~) zoqU{;MUjU&bdptr&|SfI999Z14!thAv8IwwQkvVxg&|)`sbt==vOmiTcZ%vfklmf> z^DbULe!cs8{|lime;zWma5FFA`orexm#?^EOTLYmg_y!51BYg#OT3@9PMQ3GY1c>l r!o5e=zIeA=W66z&zRo+3E@hm!|0QQ4cjpbDGZ;Kw{an^LB{Ts5R^msw literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/32/book-open-line.png b/pcsx2-qt/resources/icons/black/32/book-open-line.png new file mode 100644 index 0000000000000000000000000000000000000000..1087833a501dd19a21af4b489142bc49f60df71b GIT binary patch literal 202 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UjKx9jP7LeL$-D$|T0C7GLn>~) zo#4*c62Qai?zBWfQD{O7^Ad%`LI;@!=7J4-_q~1Va_>&iGjXHe61LunOBS*$Sdw_f z&i0sLosPEY*SqspD`e=;=vzFw$b#3^EWe%@53olV%)qD}b zakqIF*F9%ew>^91BxZ^KbNE#5=(u~?gZYt+k6YySus>dQ7U&)ZPgg&ebxsLQ0Q87X ACIA2c literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/32/brush-line.png b/pcsx2-qt/resources/icons/black/32/brush-line.png new file mode 100644 index 0000000000000000000000000000000000000000..bf94514025e875d8f17eab93c077342cfbd62da1 GIT binary patch literal 337 zcmV-X0j~auP)kdg00001b5ch_0Itp) z=>Px$3rR#lRCwC$lNo@+Ko~}!(>T>>oZ_-|8mBnLrFCgswl0lJ;<9l`T=smF0y)3747(TQl$q|eRM4v^_NMLhNqYs7sF_iL0roF3uiX$2ZwgPQ-Z|}H zZ;6pd>&gz-@Nz1r54*p?%aYvfpo%XD`~for zFHn0F(QZ%g?Ti?V2)@sS%pUE2#KBJg94GELEKdI+c!NsL%!~hjvutsC$dOjfx*9%A jrlzG+HPTU{?0=pBp`FvI(Ib`V00000NkvXXu0mjfXqT4s literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/32/close-line.png b/pcsx2-qt/resources/icons/black/32/close-line.png new file mode 100644 index 0000000000000000000000000000000000000000..8ea40455eff10e14e4b955cb44e7a14e464d2e55 GIT binary patch literal 167 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UjKx9jP7LeL$-D$|5~) zow8N*fCG=Sb91Ul?zGz`XTETke%C2a%jFstXE^B=DhUv^r))~#*2IevnsyBSU9DkudY4@prhLz9f+-Vm&Ak4UjKx9jP7LeL$-D$|ialK%Ln>~) zozl+BY{|4vjg}?3oV9G0*B_l6JYw%ou2KDBSPR14fl%ZDy&zowBTDPTwSc>|y6SzK5Ol h=ARAh$yHCM+Gkdg00001b5ch_0Itp) z=>Px$IY~r8RCwCWlu?m`Kn#XI36Zc8kWeW&C=QB);^5Xn>!3Jb9UKl=2f3FFb7V3A zZ~ZdUhj#PLpRk25r?Jn=BpysCBLE}H#Dl~sgePjX3ap;Uap(oLDzr|ZEc*ksYimcs zFhNJPUt5T|t-%Gr2|&npB1|C`J_CdTfZhfX@MPB>3t)`TB^0HS4h6!DqW)P-euXdfCKVfuJZ8_^-xoyER(zpErsk{sHD-siDB0#mx!aRC-;|?Fi;Gf?;Qb z_r3u9ULjFYtBM3m+)(^pfrd<_l1ag(D+HlhP7q(D6#PkIgx}~YV@vxGk4UjKx9jP7LeL$-D$|CVIL!hE&{o zJJFqy$&rV_ok?hd0J}JQ!~sSj2PQr~PKy&(k(>t|Pjug_-Z}I7)VaAeoO{2pwJ;|4 zcs-bY{;I*EqaCvP4l9oY=B9QROt9Qok4UjKx9jP7LeL$-D$|ay?xfLn>~) zz3k3-KtZJWp@)NzP+?0;`{H;1)W5A&-f0%U{h!IBtvO$%&hh_o2v}0au*{=*R-soE zmv7M}k%N0`*9$zgJTA`AacB>(gUB;m2A0o(3{5{37!KAjDV%5Y*|>QcquQ(8bql`r be@$nN|8wqmZQe>Hpv4THu6{1-oD!M<@km2@ literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/32/dvd-line.png b/pcsx2-qt/resources/icons/black/32/dvd-line.png new file mode 100644 index 0000000000000000000000000000000000000000..ae7a5ed678c9ba4b730f0b98d94fb70f826f2e33 GIT binary patch literal 378 zcmV-=0fqjFP)kdg00001b5ch_0Itp) z=>Px$G)Y83RCwCWlu6O+Fc5@42!db;Vh}+HgNVTk1DnA!0~?AN*bE#776bj;98^=C z?Ut`^T3VW=h7Tuk`qkTX^sLlID?J@A5tyiG{U`8*$YF;IGXt5-z)Z2#gUAE29lMdm z-pzJ1XrdjM0nKbDqK^HL{(*@9<3UjF`t^k*&W$)ai`{djIs+^huBd~3Uu7l9R1eUy zQ7QQ7S7`u+%1Vojf25j$&R0VcpE^@b|2X|BxPX&4$oQhRT3i;M7%c93A!q{~LLmL> zN;RDULFNQXoHO)%DBl!k_J&Xl4|8$7+e9}$}+CW87$~?BX|M@A4bGS zpIuSqK$+edD0-yrT*Th)Mw%ox<8z|t!iAox8MToo1V$=aqljJ7F|bh^Z47k0O)`Bn YUpMLdq(m1fI{*Lx07*qoM6N<$g2q#wegFUf literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/32/file-add-line.png b/pcsx2-qt/resources/icons/black/32/file-add-line.png new file mode 100644 index 0000000000000000000000000000000000000000..8abe032ca62b30f4c41a243e67b84f9acef58199 GIT binary patch literal 223 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UjKx9jP7LeL$-D$|W_r3fhE&{o zI+d02u!D%RvZ9b$BHugfEY*3I0Yij|Bop4GtXbF)-~hJksjJ z>$i}*>WJkUr{$|_oi+1BBwEEST;mj4#dBV>ZH+beDarK`u#&pr|Cfz1&}H#r-mZkG z1?uv``XT~~QL?#$P5BzF-??}E`?lx66Mhz1ucmiB53J4{NP1Z)qO`8z$BZ)!hh-EC UoIRGD2Rf3$)78&qol`;+0K9EZoB#j- literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/32/file-line.png b/pcsx2-qt/resources/icons/black/32/file-line.png new file mode 100644 index 0000000000000000000000000000000000000000..bd99b76cfa00a8c56d279c8cd47f6b34240944c0 GIT binary patch literal 190 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UjKx9jP7LeL$-D$|Dm+~rLn>~) zofyv8Vj#j2eyDhgg@v}dA)gR?Io||_|IxQD-9D#p?9g=i+yl0ahPF!bPt;$XF|j?^ z>?xD5w^Cz4b|dqe*UJrP1-3>X*zP&B%yxLg m*>U0Dqnnl1B0ilpUXO@geCxJdqj8u literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/32/file-list-line.png b/pcsx2-qt/resources/icons/black/32/file-list-line.png new file mode 100644 index 0000000000000000000000000000000000000000..36d657d85976956c3f75c8fb33dae74cdd9a1a04 GIT binary patch literal 181 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UjKx9jP7LeL$-D$|@;zM~Ln>}v zo$AQTpvc1#?zHp@V`-fQ1LyM=;RBPlynOuPT)mvLN8v>l-G+qQ-J%Hzx4RCq3O`Gj z$0|GTD;LvI=YahOGRnQVwOpV4chYhdk#PK1-|8hOI(NZKHc=Mlvj;6Wl2Z6jrz}6q d#If!syT?iUga4IGCIhWz@O1TaS?83{1OUD4J}3YH literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/32/file-reduce-line.png b/pcsx2-qt/resources/icons/black/32/file-reduce-line.png new file mode 100644 index 0000000000000000000000000000000000000000..020dbc9a3b1baa1f846b6d704e1035b639d8c20b GIT binary patch literal 195 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UjKx9jP7LeL$-D$|YCT;XLn>~) zz3R@`WGLYBP(4vqfssA(W#I&m1B@0DMT-t!_`T;hd;RlXF_FtHlV>Ylo};O*Hd)o= z)su}j3j?42Na5g@IK|nf9aCZbnZK7uj=m+UZ!{hBwQ6ZvY*_;OXk;vd$@?2>|f{OQHY( literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/32/file-search-line.png b/pcsx2-qt/resources/icons/black/32/file-search-line.png new file mode 100644 index 0000000000000000000000000000000000000000..95ef70ac26724badde17f0ea85c86a03b7230ca5 GIT binary patch literal 290 zcmV+-0p0$IP)kdg00001b5ch_0Itp) z=>Px#+(|@1RCwCu)n{RZKp2GKSHenI3F`nPL`o#Y0XP5$!9j4aI0z1cgVagCySeY* zm6@xQhYp-j5{+r*U{;<@pD65)nb{En7TSF^gn*g7v1{Q0Lj(X39=PS62L{S7K!gJ_ z^~dBLFr%`)4?74XG&_sF2LNUEK(Cn-P_)FG0H9g2{+0KC(^0_A8DRZ^;{mjv6WSxu zHGoO`UOP0t53=@srQ=b*O9X(HZO@hH0i|fdjXOp}L;!$91^^L#6i`Hr-~p2>04fc> obq^4$d9c_n{7a$@G;uO{0ny|^zT^#^m;e9(07*qoM6N<$f^CO#3jhEB literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/32/file-settings-line.png b/pcsx2-qt/resources/icons/black/32/file-settings-line.png new file mode 100644 index 0000000000000000000000000000000000000000..48dadf061e8d1ec6b73c50f976bc94366119809b GIT binary patch literal 272 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UjKx9jP7LeL$-D$|PIbynj)s0sP2!t3 zaOr7ODz0|Wh%pGXZ4cU!@Zjefr-iH_Bq+W^N$o>?IHSRxf|>nnEN*lAuSt{}XEYiX zpF8vF$GvP8-q*q=g^q``%lthZ=WiDeiebpTr_Z4A`I}_TLWPI+8&1!^@X+3<_MyLt z*sVXxviFtsK1lT1M~11yPJPTYv0g#Kl2u~9;2u814IXN8M_0r(eoWrPn7`Rk?w72z QI?z`Pp00i_>zopr0OS8-5C8xG literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/32/flask-line.png b/pcsx2-qt/resources/icons/black/32/flask-line.png new file mode 100644 index 0000000000000000000000000000000000000000..a3ee5d8af5ec3e457655c35543ac7fe31eceece5 GIT binary patch literal 291 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UjKx9jP7LeL$-D$|?s~d7hE&{o zI+>lZ*@4G(e~X6$6Q2bqh;(pLRbXU4%4ng%cfj%Z`zXeuyN_$X?Z~OoX!4kNpyzPV z#OjAko1zyrMz6m4SMNikedwN56~6zn7$5qZzK;JK^`P}~cZT}jD1}yW-ukYa>>u@H z_?wcs{4Yry@Hr?i^v1Iy{>RPyLa*14o$I#LKXlG}-O%?<|K&y(_KmmB^PVr2$Xy>H z^lL*ylj*gYlcbYQo2@o}>TDDEKI!^=uBziN?amxm-@qbi`fx$er_{C#mzx^|*X4zn mMmQ}F;t@C`De`D>1S9wKi>(#^IQ@YhW$<+Mb6Mw<&;$V1?{)D2 literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/32/folder-add-line.png b/pcsx2-qt/resources/icons/black/32/folder-add-line.png new file mode 100644 index 0000000000000000000000000000000000000000..8f45e967ead561255d4fc6ab840906af109bcb44 GIT binary patch literal 198 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UjKx9jP7LeL$-D$|8a!PbLn>~) zogB*8;2^*n-_q^Sl6$~yC)))9sXJy0T<_l5eQxKO^HAMg(c#A8V?GiO8>&1V9+;mI z;(HUfQaZupB8U4{HKxg&^Y|E8-d&u_;d5v?>!;WAc?$mTnlG{7kBP_Qo(t;JCw|(v w{o!soHRk+f%hWfVDRXcX+piaJ^R&I->^q|0bvMW80^P#k>FVdQ&MBb@0BQD0b^rhX literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/32/folder-open-line.png b/pcsx2-qt/resources/icons/black/32/folder-open-line.png new file mode 100644 index 0000000000000000000000000000000000000000..a67baf13cc3e76dcb816da1c3f6d470033ee8d44 GIT binary patch literal 258 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UjKx9jP7LeL$-D$|_ISEDhE&{o zJ3&z3uz^JD{NOpSLZ%$de8#|_56mnmuv_RM>;Y&w~5aY>D{S2QsJMRHP|wJhvZr;mt^?zIMqb8~rL~8S_URk=%Eup8aF+u5gTd3)&t;uc GLK6VFPGsx= literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/32/folder-reduce-line.png b/pcsx2-qt/resources/icons/black/32/folder-reduce-line.png new file mode 100644 index 0000000000000000000000000000000000000000..3b0ddaf5d1f0278bfde0001fade3f77ecc54feb6 GIT binary patch literal 186 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UjKx9jP7LeL$-D$|N<3X0Ln>~) zy^zkxWGLYBP+d`I!Unzw%|Zsg&gLVGK`-VmEdKlK@Bbi)@`!|!ULuvn=eT13uxKnf z&3B|{<(YSk&#NY0y{slxG5^DL1_`}c`}to~@*d!@(4KgmL&7s9_Qw&y6FT>FxF2lJ jYjjBbn%H_^$K7i)-!ZZC{h1*Pw4cG#)z4*}Q$iB}O(sS< literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/32/folder-settings-line.png b/pcsx2-qt/resources/icons/black/32/folder-settings-line.png new file mode 100644 index 0000000000000000000000000000000000000000..24c0a16ebd38adbbf72b901ba11fec300f7ee9b8 GIT binary patch literal 282 zcmV+#0pkdg00001b5ch_0Itp) z=>Px#)Ja4^RCwC$(-~mJKoE!V&uN|Fv@RW|=d>=3Q(O|KIMrq2lDH&Jb(voO-q-Bo z%tyI?xj9FZ*zq4K1jq1SDlNdq@-Y#&-27Pn4j7-O;D6Tr0)z?sAApSgQ2!ec9g6~) zN+n+pT+sS)ciUTvQc2-=?aiGJ|qf-a>ba4O-F#XbtmWTp@^-&;U7%2|?GErcq23vms+WQD3 gHoUP7J)3hL0Q$!=t;?7t<8 literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/32/fullscreen-line.png b/pcsx2-qt/resources/icons/black/32/fullscreen-line.png new file mode 100644 index 0000000000000000000000000000000000000000..f31b05b320ce87d4a5d25a0ad2d8c7d5373ceb4b GIT binary patch literal 146 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UjKx9jP7LeL$-D$|d^}woLn>}1 zORxq!IPgK9Rqnv5R}8bX*jGsxFs@)>Qco~VV2b(26vo`3QrF=8cLn3(1d$60s(n%g s^Ahb@9%)^A@aefC8_%?$N)ZNzF9|*TO-4U21I=adboFyt=akR{08cM1U;qFB literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/32/function-line.png b/pcsx2-qt/resources/icons/black/32/function-line.png new file mode 100644 index 0000000000000000000000000000000000000000..a2735832898bcd04899e65653198ca4301ad3795 GIT binary patch literal 161 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UjKx9jP7LeL$-D$|qCH(4Ln>~) z4c^MgV91g3zpHmgZ$fi<@05>sLOJtHJ{}ZO;Nx`Ks6 zwy|?e;OAsd6=hP9doOe({ideM-6Jm@?ps###dTgQ>lM~^YKi-3EYTkdg00001b5ch_0Itp) z=>Px#`$c_6KznZ!uo!+ZG!efR@0 TRr8gB00000NkvXXu0mjfcvylU literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/32/hard-drive-2-line.png b/pcsx2-qt/resources/icons/black/32/hard-drive-2-line.png new file mode 100644 index 0000000000000000000000000000000000000000..6945deff0a7b434444dd2545964a10f6dc99a894 GIT binary patch literal 167 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UjKx9jP7LeL$-D$|5~) zof^n{K!JzV-)ZTC2Cw6c9y5g17AQZgT^qgQx5`fWS<_r3LN9Q{_#E_0$g*Q++xIYn zUCMgPeRjVGELNq5_i<#@70O#oRr=>tQ*ZjfLiN4Oi}q9jgP02*hj~rQ6+Q?(>FWYo O#^CAd=d#Wzp$PzO#W=$N literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/32/keyboard-line.png b/pcsx2-qt/resources/icons/black/32/keyboard-line.png new file mode 100644 index 0000000000000000000000000000000000000000..26515e8b204307d506185ab25ca5bb93cbd1b9a5 GIT binary patch literal 159 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UjKx9jP7LeL$-D$|B0XIkLn>}1 zORzR`Ss2`CR^;GtxXG3Ia literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/32/layout-grid-line.png b/pcsx2-qt/resources/icons/black/32/layout-grid-line.png new file mode 100644 index 0000000000000000000000000000000000000000..08d986c4f323d2fa409f69b099ae8f0da7373e16 GIT binary patch literal 170 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UjKx9jP7LeL$-D$|QaoK8Ln>}1 zORxqg94MIo(7yA7U4XcZ1p})`JKqbom;{RkHvI>J$4wX%9U^KzNEu`~*frZqojI=k z&cV@Z$wWq02Psjp2SW14UM5^=_{{VB;)BYVM5Og!Qq*XkI<*7P$mOjPr1I`<*o Q9B3bdr>mdKI;Vst0Pp`ci~s-t literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/32/list-check.png b/pcsx2-qt/resources/icons/black/32/list-check.png new file mode 100644 index 0000000000000000000000000000000000000000..a4cfbf3bab3f1ea014150e0a407e1a6968990f99 GIT binary patch literal 151 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UjKx9jP7LeL$-D$|0zF+ELn>~) zy%fsG5Xj+jv2#OU#sQP|wAb(6J^yFVx?jraav1k#A*R_`HH=DgUNx}Vyf5#4XU=wE yoWax8&t;ucLK6V?`Zihs literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/32/pause-line.png b/pcsx2-qt/resources/icons/black/32/pause-line.png new file mode 100644 index 0000000000000000000000000000000000000000..f6a825b92ef6e9b417dc1b457ed81cdf397d5076 GIT binary patch literal 127 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UjKx9jP7LeL$-D$|EInNuLn>}1 zORzQzSs2_fmPnY)&~=Mh(eZ;k2Yya|ZIrjZU6uP literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/32/play-line.png b/pcsx2-qt/resources/icons/black/32/play-line.png new file mode 100644 index 0000000000000000000000000000000000000000..bc5c490f685a2c22ca996692e52413c9224923a9 GIT binary patch literal 193 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UjKx9jP7LeL$-D$|sy$sCLn>~) zyqJq`ROboNEoES=%G*~4akX>*1;1~0EjtdnUU4Gp29=!b_yjPa-7GGok qHe-j|k_+DQTqxtpsJ?&cJM-FArHOa9#(V%egTd3)&t;ucLK6U<4M)NN literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/32/refresh-line.png b/pcsx2-qt/resources/icons/black/32/refresh-line.png new file mode 100644 index 0000000000000000000000000000000000000000..fe3b33221711d87ce51da677f7d494b88c97dba2 GIT binary patch literal 355 zcmV-p0i6DcP)kdg00001b5ch_0Itp) z=>Px$9Z5t%RCwCWlv%mLFc3zMgpm*lbwDKG;Nk!rPzTgOTLoW27az|U`@b<(DU;|8-xmxti~gW6_Ln_#H3#{4KG;~vx&L! z=XA+}myB!?EQD6CFuf2>k~lofmQ`Yh;<%#Yj!-^FJnV~Y;D%1C?O|SQTW&b|GOUdE zj|o4j0in*e*j^E@x~-*7s~Iyd$POP?#s#kU!>JpM3%t(T7f?PV>d2K3a$m;5+lfU- zJFgx_4cKVq9Uh|9%8>AP;+|xt$8kjfps{kdg00001b5ch_0Itp) z=>Px$5=lfsRCwCWlo_DIKoEvMr*T@RxOAQBlDNF*l684;$-1;o&n4@Uy0k9u9LTX( zL;c3~ocZVA#j%|J>}d==6JHCP=rL3wjC>{R1ED~}T@OF-IL=rcS01{0ry)b9$8L1U zZtteM9SIseFd)Pbyq;*H+v>a{hu1(PwdE->sFxdz#x9f))xxmCsXR4wu~5Tb;_8sU zq)sgPd-F;qs@DQr)IS!mJ^sLZ{DCti3|i91J#g^{>P3`=55j^FDbRewg~NrUmFQxn zJR=lQ#Z0_7Mr!!Pr{#Gghv?*x+&~#K({id)c&6Q5ZkcO@ZWiP7LeGhop37p?S_+8H qoS1opDh!UWx*8ZTJdIT|yfEKn{mL3dOVD%x0000k4UjKx9jP7LeL$-D$|N~) zy%NgEY$)P#QG0EW)>5HI3PDyvuT;)ER?d`67h`^IJje6s$DB}q*LiDSuV!@SSbgJ% zgJ2*l2SX@>_=8{n_kCqeT4c`(Xv;Y7A literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/32/screenshot-2-line.png b/pcsx2-qt/resources/icons/black/32/screenshot-2-line.png new file mode 100644 index 0000000000000000000000000000000000000000..1fd192ab3696aca99b6553b5fe8017dd47693342 GIT binary patch literal 289 zcmV++0p9+JP)kdg00001b5ch_0Itp) z=>Px#+et)0RCwC$lUHHHFc3rs!a*^85b%g`!SyVAXZ^}q+!fTb{0cwFk4UjKx9jP7LeL$-D$|@;qG}Ln>~) zof65|pdi9x&J>u#FtH;^MJhx2{qKl*-*+zlVEUOe&zjX&;Dw-2ljsW(^^3_fG;cNd zhijxS5ZqEOE%0)!!LFTtE0fhdEP)kdg00001b5ch_0Itp) z=>Px$HAzH4RCwCWl?k!JKn#XI2`d2!IA9$-Bp?9^NWcL&00*rD>R{Kwu7kWQnQ1zC z_xbnEeND>0Mq;B!f{$a5%#J-D#@QJ-^Fgd>2$f8xA~e?TQgiCU4uBovOxQC}7YYM6 z4ST{cDdomMLCA5mN6VmJ+8utVboSmp03}C1&q~Ds5(H6>9GNEf;Y@S$Iy))r!Xy2 zD3mC*mj~(a&LW;-^-gka9O($7pUZd_j~^MNNOq+ezkifdoJ2z?T)1P2Efkdg00001b5ch_0Itp) z=>Px$3Q0skRCwCWl52ItFbqa71Vab}Ggufb3}yx^g9btvqzqOFgAfJ|1BZduqrG)p z)qC&i-)H&y$v-D>b*jLp=@EqfR|S#T6-BnrZCRxltG5><3qYdR~Ko;rs5jTaqE z?h%qEg#ea&v$_$OUEE=a8m~>Suxi>-@unt0s_a#*D>wq%oq2~K9GeA4&9RMRvtTs& zcL{o0Np-;v2Y&}>aKOR8L|70qt*i)3ZBwcJ2JDr08fF5Q2;|yVhJ(fvmgc^|rbD-Q z?6haA?=&J>v$wid^sh(ATw@u?$mOe!Y)@^4nLRhB+A~w0(v5iemM7#)O$3_g7+Ky; iT^Q;3F#S{gYaRg3x51`e&8Tny0000k4UjKx9jP7LeL$-D$|5~) zos!Dfq9}4e?4j$zgL~aV_HdXeoO=H5-R=JmKTK4tXqs8o!lW9pz>K-?ze{Yn!_2NY z>kq8^4v25~qn35~K%4!fND+eo-xof-vL}|vPI!1%HoTmN!^-htP>bVx_KI7TDLFv9 O7(8A5T-G@yGywq3c0E7< literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/32/volume-up-line.png b/pcsx2-qt/resources/icons/black/32/volume-up-line.png new file mode 100644 index 0000000000000000000000000000000000000000..4911c65109e4811bba246d253a64a4ee172c6d29 GIT binary patch literal 341 zcmV-b0jmCqP)kdg00001b5ch_0Itp) z=>Px$4@pEpRCwC$lv$a?APj&bp%RdQgGB-okgyUe0SV(^$HC&D>!3Jb9L$j~%NJUMZ@*3!~g}?wB68vVfE#BOK#N(E)h;$B!+-%yX|4pdA2hzj=iX75~ zQo{izuM!NR*#>vL^blkKnLXr~>;RN|wC5lrcA(MXixIm;9c$+vX2rr|cA!M)mPc9? n0S?XA>xODz0FKb7j`d4kk&Wuetm5IG00000NkvXXu0mjf2o#jA literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/32/window-2-line.png b/pcsx2-qt/resources/icons/black/32/window-2-line.png new file mode 100644 index 0000000000000000000000000000000000000000..c084b1da1cf423c94f5795c022317fc8d80ffd7f GIT binary patch literal 163 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UjKx9jP7LeL$-D$|Vm)0PLn>}1 zORxqg94MIo(7yA7U4XcZ1p})`JKqbom;?(4l|2nt85BHq3KR}Aq%7y3z}O*R(6DN` z4Ub0Ov4=DHG|UsOGz2oAGk&8kG;euTnh--OztBV0h>dn!426G8>sDS6X9rru;OXk; Jvd$@?2>>n;F?0X` literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/48/artboard-2-line.png b/pcsx2-qt/resources/icons/black/48/artboard-2-line.png new file mode 100644 index 0000000000000000000000000000000000000000..130efe522c8a877a9b220ef77f10539501128cec GIT binary patch literal 138 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDH3?y^UWFG-ijKx9jP7LeL$-D$|*aCb)T!AzY zEKT2e9!N2k1o;IsI6S+N2IQ!Ex;TbZ+)6&c{X?2$iTwxc-Oj7(g$RUr>mdKI;Vst0MGp;vH$=8 literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/48/book-open-line.png b/pcsx2-qt/resources/icons/black/48/book-open-line.png new file mode 100644 index 0000000000000000000000000000000000000000..d10a82eef44ff025706cb05238bb991bf98f316f GIT binary patch literal 217 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}a}t#^NA%Cx&(BWL^R}lRaG=Ln>~) zJsZi`Y#`!tQJay|qqD`rk!g~g&?^R+24(>SEdVi*N|74jt)k!m_Wj=Fjnl;H+ z)%2jn?)N7>_MZ8o#jr|yn(n2NtE`20Kk+%d^LtRzdG+AuHW$WQdl@#kn|PdOxKuVd zj#*?=rO^Q+24*%M34??K4UNz5us1$jdB5Ca=DW=M{3-7;e;<^U{PFnOnyFKKr#xet Q0CXOMr>mdKI;Vst06=$D4*&oF literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/48/brush-line.png b/pcsx2-qt/resources/icons/black/48/brush-line.png new file mode 100644 index 0000000000000000000000000000000000000000..ca8829afa8cafbfdfcc0678adf4ede7148c762e4 GIT binary patch literal 467 zcmV;^0WAKBP)Px$jY&j7RCwC$mTCQ?Fcd|PgeM^q;-GQxj|3dF4vGVCP#jDhfP>%*euV#K2u zMpo;Po<#uwV0-JoQri{JnN002ov JPDHLkV1kTG&L;o> literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/48/close-line.png b/pcsx2-qt/resources/icons/black/48/close-line.png new file mode 100644 index 0000000000000000000000000000000000000000..f7e711acb13204c6b4ad29d36efbbd72b8102ad5 GIT binary patch literal 206 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}a}t#^NA%Cx&(BWL^R}9iA?ZAr-gY zPTMKi62NmbWRiE$f6kH(;rkXp+%M@ars)y$KOu6O>f($8nXi26KOPc3+udz->3?6? z-`DPx$$w@>(RCwC$l^KBKKoo{Qr{`2oQX^tw!3wp|jJ#HHJ1?)mqs z+xGa1a=riE;Xi)}o`EwfpRAl2@Q8Mdft{z1^MS5+28?Em2bl^8=sdKEbn-kQph!9@ zIsatf#=^*bNI>V@YW|>C3Vs#nInok&^AiDnO4(Q|G3oUU4_N{5Y{;Q}X=PrC$q9fV z2~{eHaaHCG@&W;gD^`*ok4Os$pG=ed*DK`$V#>uZDLvE)h$HEsCbw@E7&X}eoTkn~) zy_7C{$biA+qq<_yqXv%yjAE}A07(l)z6lO83J-5ozb}2iZ`=NQ_k#GyC9b~nyV%7t z0xqXET&upbx$nZkyAgNluemeHmZY)D-kfmcW=YCSInhpr7Z!!G0VlR(3f=TOUT!&o zBj;|zII)9eiK~!M!Kdo=54VOsdsnQn#AL_apG+(q0tyXx&+z@fGF_ncEbHg9 zoKyce%)hW*mf3*AkugEIN$G$JOKXEGM;BwWz!vs*EepgMjGK-%#5XYX=G$yAn;Tz| ios?;|-)Lgc=5KCgSGD#}oFvp++ue{Tvy#BVERhEnt6af}jGQOpkeW@&EcG5At@ z;-Hih+Ya+eM!D|oKTB&Fa@2GTU6{l(#*G&@r!ujXSdk53w zj58K5+L@m4ePY^{1opJ&98c2Jo}F|MII8s9l-1Qq%(QEz?v2#*f)+LIx0{$H-Zq=P zZF~GONr^R2-#vC$HbacNtmCzkscbUlo7Y`pwm8GgpwIjF!nSR!mSwkAHD7Le`}zUX zgjr0M=Wj8tUf?&suWIqC0ewSSLl!(rL;Y$}M=%t-feR%9uBp1Wy+cNJZ8a!%RHpkt}m0Y;%ihHqrk{>fPHE8y35mA$c}i2J~MR~8FiMqS5*)L19B z2@@DqQW!WT9e~UL<_d{|KX)1!&-gP-1k9B-IIxdb!l0nwJM&e|`_D5K<~lslIAptu ZZ9?+Q%hg_6KL9Oe@O1TaS?83{1OT@RIrsno literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/48/file-line.png b/pcsx2-qt/resources/icons/black/48/file-line.png new file mode 100644 index 0000000000000000000000000000000000000000..120d666c64e89c914146b8aa6602973e8cf416d3 GIT binary patch literal 192 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}a}t#^NA%Cx&(BWL^R}Rh}-6Ar-gY zo(*Jl2oPz0DD251m}e{)a_K^3`3p~_^%{Qma3|bb!SHM)4E=PN^E13bOZS0T#esDn85x;aI0O_N7#@CS@;mw4 hMu4Lw+Td+qHsi`*e{qxEs~{&ac)I$ztaD0e0sypmMWp}$ literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/48/file-list-line.png b/pcsx2-qt/resources/icons/black/48/file-list-line.png new file mode 100644 index 0000000000000000000000000000000000000000..c24ca803766399063aee2baf260a187f79b6844c GIT binary patch literal 158 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}a}t#^NA%Cx&(BWL^R}5uPrNAr-gY zP7h~1pdfHm>EoPc=7OTo3wv)r`5S+Oxl+PWv3!|e&!rR0K@zI`r~WqeYDhRb`aO4P zWSYM6w?pIUE42#}n7Kr1p0!`**Nm4`U9tOgIa8$w^F^|WF#IWzV%cer5asiGANi_=w zPCkbw<_QlNR4f=em>X*t54>Y#DN0mlWU^^AWMpJ)oV#281z%Dz{Q-^ALI%HZkh=d#Wzp$P!^i!!_b literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/48/file-search-line.png b/pcsx2-qt/resources/icons/black/48/file-search-line.png new file mode 100644 index 0000000000000000000000000000000000000000..c37e6c3b94a68fac5e26e9595b2d5f9e6ab37513 GIT binary patch literal 332 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}a}t#^NA%Cx&(BWL^Tm z6t1e|yT!F@Y9$tC=q%jW!Wc8tR*AOklGss<-e6@<)z4*}Q$iB}3Lu1F literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/48/file-settings-line.png b/pcsx2-qt/resources/icons/black/48/file-settings-line.png new file mode 100644 index 0000000000000000000000000000000000000000..2a23cc5f4389a5bb2e146afbd64ce91636a4911d GIT binary patch literal 306 zcmV-20nPr2P)Px#>`6pHRCwC$)X`1FFbsv^e*}-fDBXZ;KsISMVU&9}ARE92$p-LpsbrxBtW$|R zKMoiGPwYp3f7DC>+=ksr_(j;r>@+qVxD9vEO$G9=Yc6X*q94^x#(?Z`1=*BqEo*A2lxI9o$b11 zXOyE~#APS+rv-fe6Z<>MP#kpKVy07*qoM6N<$ Ef@GV1WB>pF literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/48/flask-line.png b/pcsx2-qt/resources/icons/black/48/flask-line.png new file mode 100644 index 0000000000000000000000000000000000000000..efbebd09e19cc25b73b315fec54ecef6dc77d7d2 GIT binary patch literal 324 zcmV-K0lWT*P)Px#{z*hZRCwC$)MtT&Fcbvfkq`-yuo9545+Y?KL_#FQ0XP5$-~bd{r^C=*uIJ;= z)>kNl=ofh2IZlWTdhz&G-$TWgTXoCbnSlYlMh@3Xa5x}i4r_fRTXE%ts zwv@-1Z$EY6?w~uy?xF6aM_Z=CT-AQ_fPg~XmNA$!?Kd@)eEtS&8u?8-Ut`S}j+ z{QTz3^TK!F0Y?aej|3b+f=>`|EkW>+fa7@Z@taO~Bwy5kD@5fB?9%X;%X?@#EHsCWeM`cD>O{tVY%>KxnRxv>%tm3w|_S~d2Q3% Xo!+q6E`9qCpsfs^u6{1-oD!M~) zy=2JPY#`zCaI%o5hlA4+1=XMe2ib-eE05(lPq)6kJLC6pv3(C0?%eqxO!iep=rzB( zz5XS3^IuptHGW_HD*oQeP@Wq{6xo5GnqaluVP@X zV`Tfo#Pfqy!r*<<{r$^k$0{y(m9|>$+v)jPvu1a{XI<1gMO0DZpAOI;44$rjF6*2U Fng9ZeU@!mx literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/48/folder-reduce-line.png b/pcsx2-qt/resources/icons/black/48/folder-reduce-line.png new file mode 100644 index 0000000000000000000000000000000000000000..99859c135b4732ff4a41e86121a14d5a4a8a929b GIT binary patch literal 175 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}a}t#^NA%Cx&(BWL^R}nVv3=Ar-gY zUgBhIP!MT;=vpI~!Na@YB+moO{M7$UHCzXF3Uj9v6uy)QaQxuaw7};@gWrn-C7dE* zf>H~FBUwtE7+9Rxzr=A$7$h{D7G_|+yGMzcjYsBwvjek#j3jd-)4VNh4aLh%8FXX= VF7Fr9_Xb+Z;OXk;vd$@?2>_X?G=%^F literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/48/folder-settings-line.png b/pcsx2-qt/resources/icons/black/48/folder-settings-line.png new file mode 100644 index 0000000000000000000000000000000000000000..67743bef639b525f3e75d95caf8ad43973517258 GIT binary patch literal 304 zcmV-00nh%4P)Px#>PbXFRCwC$l~;MhKnw;4f*=HfA(%nLAPXT3Y-TwIHiJ6`YX<3ArR(9p)<$ny zeHwc!SFUUkXX5eSC`hc6_NpS<2nt0I3RUnsg;3gXV(;H!z%3Jz^CRD;u==l literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/48/fullscreen-line.png b/pcsx2-qt/resources/icons/black/48/fullscreen-line.png new file mode 100644 index 0000000000000000000000000000000000000000..897dd7fd49c365fb6e48be033337d465dfe9a12a GIT binary patch literal 133 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDH3?y^UWFG-ijKx9jP7LeL$-D$|*aCb)T!AzY zEKT2e9!N2k1o;IsI6S+N2IMGsx;TbZ+)7rE`|v+K;m3bQcGe&VHdkhcbPx$4@pEpRCwC$mT7^5Fc?Iigh+^!A|Vp$pg1TFS_j2RaR3g2gW_QCoxQlfy_+}x zAeqUXkVqtY@$5KLbF)A-XLk5G<|l3pI*o?}F5DP(Auzz!YA$W3>{|*l9P`O2Xpw_@ zAv_u!3Ry4Zhz1JJ8%q7ku#m~81ccY;Bf{JZkH|IlcAVxSM^qYpuv0v_Rfsq|{_61D z?`R(C{=)VEr+;4*z%L278PE(;Kp*x=o@Ms)0nQ%KztLKBRM-P_R5agr*y*^AIP63n z*F6_b@tGISH(r_G>GbQsIvfXibZWl{+(?l5WT|v%f6)00000NkvXXu0mjf?l_ZR literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/48/hard-drive-2-line.png b/pcsx2-qt/resources/icons/black/48/hard-drive-2-line.png new file mode 100644 index 0000000000000000000000000000000000000000..4086783dbdd7d217e1b0b55234b6e6ffa9a9f986 GIT binary patch literal 156 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}a}t#^NA%Cx&(BWL^R}VV*9IAr-gY zUfamapvZGHpnt~Me%=J(ye1PBA(3zIKU=YUi#T{(FHdNRZ`onHOJ$eeF}H7Jf`YG# y5B9(KksvGZL3bDbnrWY#w`^dLRqhrt%jR}2i=BGON#zM35d>G5>+ Kb6Mw<&;$TNb02^J literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/48/play-line.png b/pcsx2-qt/resources/icons/black/48/play-line.png new file mode 100644 index 0000000000000000000000000000000000000000..e17f83465b216c311c3caa4ef6eccc4269d6e89c GIT binary patch literal 224 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}a}t#^NA%Cx&(BWL^R}vpiiKLn>~) zo#@Eu;2^?U-_onppkMd|(x}N^x{)=L;>kw7^-c<&I+`1l@3COl;RpaR12q*f~Hkz~7`p{ZZM& z$yeWsoVcwyVSBJb{&I(VevP~4GUbJ{-pFfuYjz{A`K{IFJ+_`g^JV4v1Xk$DlyCrD O$>8bg=d#Wzp$PzU7gA3E literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/48/refresh-line.png b/pcsx2-qt/resources/icons/black/48/refresh-line.png new file mode 100644 index 0000000000000000000000000000000000000000..65e36cf6e29fdc21446d7d3832b6569a49919f44 GIT binary patch literal 488 zcmVP)Px$qDe$SRCwC$mQjuCFc3u#1Vb#bAKw;&P>KLH|CFDg5$=E7c=o&BM&}toCnJ%D>0*)FDz;dcj5uvF(|l5 z=5?)HXyl8y(?q~a**sUC$$K>MmW>Mmn<-&nBZ?j^rE));`pPE5unW<^z;>{*o=HOV zRInhwOhYqEsme(iRxBEvgjh?7^+aCi@bV4=8SKvjkB&}4*z$rA9fqxZ)U#x8MMp0l zmFs+2V9;RY366w7L_~)QF_4h`D`_^}K|izZBO1_f%{NMF&i2$aB*a87+A;M23X<{` z19;kT$>SFiCUHXz197K!zD+wch+*g3v{STavR@QRb+me`D_I3BCvD=IIbxx2vI#|s zHnBSG9GiNxc~FmBcjTZ}ociN$C~!dx8%v$^ierz@E*C+}o$e>_V*E zGC?di=EU5U=JntoAClz5v9dRB e0**g^1^)qr&(MTVk^Kq)0000Px$lu1NERCwC$mT6(7FcgJ930FcS#6cqg3D3dcfajpkL2&>Mii1}N-~b$C_S-f4 z?9=c3%yKSSPT-xt1k1q075`0FCI&3DW4L9-2efjFp&H>1pP@U13OaUtrgl2YcvbsM zkJDC1&;05;vJob{YNN*l`kgrNstF;Br_*1EBV3+Zlr7GNBL~ZoT@c-q=>?_0q9cOn z$vLo;V0h3IB?K9@4ofLc$BqOr+T~0Qy4{#~hXbJoVT*3HX3mLPdd(~>(2()AAne|E9SXk(>yz^J^5f!M*Nmy#E QkN^Mx07*qoM6N<$f@e+9t^fc4 literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/48/save-3-line.png b/pcsx2-qt/resources/icons/black/48/save-3-line.png new file mode 100644 index 0000000000000000000000000000000000000000..aa174bfed6307f05cdc7a3ada3a73159bb194eb4 GIT binary patch literal 185 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}a}t#^NA%Cx&(BWL^R}#hxyXAr-gY zo=s$Qbr5lR*!g8q&XVgcDz^`qEjZXPpWz+rvpEd%Q;hT!*vsc7y0UUq39;02Rtdkk z&$8`r056Aig2SKQM#jJ@2HS&m`V7k&8s2hBs5RU!V`0%Lv3F=-U}THuR=Mz?<0O+( g#fKJ#r9P_}AK%nhI+hiF6=*$!r>mdKI;Vst00jp~) zof^*AY9MexiBWTELyOlTL7^4QuNGKn@V$FmE8kPS`=q$>nQ2Z3B`h;G*eC`twVwaS zf2Lob@$~-#6DMd3^Eqo76{Rmb)FT#{V|YmU#jmCMCogL+@1E0f>RQWuIggG-k6VJb zf4|JU=thBWv&K3Omv^1QOkwutuCv(4N-kWyjps$Zv#qH?tq{L?V}kqy>Cc;)AG6yi zHBZm^Ka(3rLZDX|JYD@< J);T3K0RTY>Eakt zaqI5Yvw3R_c-j)h<1VXgN&Jz!Fnw6f}vcdkSW89VK%S>C&&c4$; zFc&7YnjmJJb-P%SGPAPq%NA6kuzZO-O#9+2_1LNwB%tlu|odYV% zPCrbKKl%HM>N7@%@Ff#OY%BzMlHIyt}-uZsa(~{c0FOkR|k`L3%JCx zcdoT!ESqucfMU;1p@y)dB~QC}6ptBnT>O-gQ_gokrpM-FD`$?&>&(4z3nFSSZU1p) zy7nz~|JSQUWtAgWyH+jFouba)Bd?=l5cNDDKE-wA27SLHx=%wZ!^(e7>~=WSz1{NX wRJS*~`hQKg7g;Rv_xAink>42vzJ9~p00i_>zopr0QhRyK>z>% literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/48/shut-down-line.png b/pcsx2-qt/resources/icons/black/48/shut-down-line.png new file mode 100644 index 0000000000000000000000000000000000000000..3016d0f07a1825354e163f087af74b8b52242c28 GIT binary patch literal 459 zcmV;+0W|)JP)Px$g-Jv~RCwC$mTAGGFc5|x2_qpA;-E;t0TQ+jS_wEP4vK@;0XP5$;9&24_q#%e zkg87hD)OZPdC1 zT0OsEJmf0(mFNl&xliz@pRJt|~I!V!=qF=u_?Pg9>GW1W_*M ztVTP_9uRL;!<^m8tE%r!CY?W1KzEq7>Sx8W1(a&4w0d7O?D}*RI++Rtbj*%d+;zDE z0Tt_=R}6Z&KtMi2#><61GB_v`FiNnrz^Eh_pd^ou3eG~^<` literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/48/volume-up-line.png b/pcsx2-qt/resources/icons/black/48/volume-up-line.png new file mode 100644 index 0000000000000000000000000000000000000000..9e2f135371d9c1054cac54492be42df4f5957dc1 GIT binary patch literal 444 zcmV;t0YmPx$c1c7*RCwC$m1)7FFcd|Pgh)U_Bp@LYkWdFj!Z;`nS_j|&9DoCG@MeFrUAD~j zZRh?AuRYnr8*jWU0xfseiCiF`$cSMZ-)D@9Oe$tc>l#-u<~c~wFn$~}V=#9!vS`%*Qs%MJ511R+oxv(RHmKGP~S04~_s znD4w(;EWhL3kjs4!sM=r-U^PPGZ6tTfD)6tX7Wke5p)Dl0_Wnbg_xvT{8%7rL~k$P zq%UocOo1FjC#J5;7KlIV#WMGq0^P3Ko`oQaHHd#wP;D&o@FxVlxGs{|MKUeN4_eqe zXgcHORgsw0#m4ySRWYq9X40x)@Cz|)FEJ!9R=J`DfkC`pjTn`r!92HL9~@UyAp2jQ zdop_=P_%y0AEIQ*eDq5WQZVcY)bw<4oWNsCAl~SOf|Jw-1f5q@WRO^tKy2EYOd^vD m))fup6X7yk(`-3IB`ycPB7iN6OuPC7@Wn`%##%6RYV#0N4X7zopr0F1dX A>;M1& literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/64/artboard-2-line.png b/pcsx2-qt/resources/icons/black/64/artboard-2-line.png new file mode 100644 index 0000000000000000000000000000000000000000..6716c8ea437ef381d481867a86db962900fabe72 GIT binary patch literal 222 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I0wfs{c7_5;#^NA%Cx&(BWL^R}Gdx`!Ln>~) zoz{5tn1cXoJZJQ&vI(y>UK(7#`S-x*bXM;Nc4=%I3{>U`ANY62?5b+%?ejX;S5h-> zo)>UnU_9KmtEEl!%gm^2_hVkq{-^%W`YRVB%aUVFVdQ&MBb@0PELNeE~) zy_~-Akb{8hL*>LJM-Cq56uZV46v6J|B=g4W0ORX~xcAQQpUnF$K1rW<>6Bj+4Ud;c zBpwuboPKNyE9?El;tua0a?ys5H%n^Q^s64KsyQ#pv-m-^p0L>ib^)$^-`kWK4xVFJ z;d4!IVrIn#H?`a!>33xfHdu1M3+vyw^C5>rz|s0OOfg~?9@<}vm18)){=WXhq=p~g z`Pl^=7#LYl2`+!@HE(|ZmSuQ;_bl_TZbqZTmq`sSGbU#Kn0ft#O!S|~SUX0R7c*+s cILqEKe#Nxv==9$0N+8dBy85}Sb4q9e0LeOWb^rhX literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/64/brush-line.png b/pcsx2-qt/resources/icons/black/64/brush-line.png new file mode 100644 index 0000000000000000000000000000000000000000..f2cd41e140a1d0917aaf7ffd473eeef022514dae GIT binary patch literal 646 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I3?%1nZ+ru!7>k44ofy`glX(f`ga`P9xB_Wf z0KVyybAjf{mIV0)Gbq>#^#8waaQ?kR{rZIY4-Pae*#9Bm!i4yO4f+26Q!iHl-L=Kj z#WAGf*4wGAlaw8KoNZrTe86kc`v1Q*e@yk>6y4&ui%u%dF|s>92!=?j>n~)R^^NtZ zHN%VfZw@Vi5_YHW#>Qj~Qz0Z8XhAzj$pS}j1VBKmWAIVtfxTr8RDd6Bfg{ABbi)XA^ z>TGmSMNL?8!JC_%ybK8fN24|ih}~P1E;wDaB2lrR`M|a_f2S(fo@i-ZQ?kY7nWAls z(kpM@6&XE;58oG^H?J!E&C8YfPwUDm432cJO*nLz*TjnPa(laU#LT+aEAI9EWJ#@x zc>gmzGkE*5d3(0oExd5`d3B!3r|TD19<6`0W95#PXAlce)0IErx$tZe0O*nJTVLXmKZBM;o4PyKfRa-f7l$WWSpyf)aQc| Nkf*Dk%Q~loCIEc`0>}UW literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/64/close-line.png b/pcsx2-qt/resources/icons/black/64/close-line.png new file mode 100644 index 0000000000000000000000000000000000000000..31d74e8425862a6c4e63218f1e1d556a4bc3f5f4 GIT binary patch literal 262 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0J3?w7mbKU|e#^NA%Cx&(BWL^R}`~f~8u0R?Y z`13vQ1@c)-g8YIR<|q7Lpp~2S6DTmn)5S5Q;?~>CTN#-Q1X>?>QwZ>!MOqYce+TZ^vFZO{@_6$o5)WUK7iV^%X*7zIGC zy2z-)&>+De#O#p7-~n_N!;zD=4cmk$vHaZBl{x{y~it;{WAx7z|+;wWt~$(695$yV!Z$W literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/64/dashboard-line.png b/pcsx2-qt/resources/icons/black/64/dashboard-line.png new file mode 100644 index 0000000000000000000000000000000000000000..6fdafb77d5d25e0aff769f5f24f43013914cb857 GIT binary patch literal 213 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I0wfs{c7_5;#^NA%Cx&(BWL^R}{hlt4Ar-gY z-Z;p}Xehz@aQ~8$3D*NJDau|DR99zacyq?OiT}W~nKuJ>ubyr-<+Xk(w?jiiWMeE7 zi`jeTB@4P4w7Qu%UTpa2#_&Frfssk>l@UqKr)QDrz3gW%w`4Epat5-@@+0wRjgMmKihOGG5MgJs7>SL>}Z^Pgg&e IbxsLQ0C+n~3IG5A literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/64/disc-line.png b/pcsx2-qt/resources/icons/black/64/disc-line.png new file mode 100644 index 0000000000000000000000000000000000000000..7665568ce286bdcfce130429044acfd9670c8d74 GIT binary patch literal 720 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I3?%1nZ+ru!7>k44ofy`glX(f`gar76xB_Xa z1Ca-{JAj7Dlmz(&GYHt{EBMd9|KH(!{riCR1{)eaTsV+WFkwOegZ)JtvrHKnn3z0W z978H@y`6S@+H3)#EXtbX_lf@8p&H?t zC*QRF=bq)OW+kcyU;Xp1(|p3C$i3ogmbAJ|U%ipD$H*^msu#<$6$PzVb}hE#2+2OE z>AOyP?db-G1(n57`(^L%W@+P`bN^O`(yO@)?2Jb$lNNl9Wo}_|_*L8-@t^Mj(}9~Z zEIhOSH>_uscoW@V!1C2)!&;@v2}Tz7!U=zj*$y&huRlDM!S=p~PSy3MBbSve9zA91 zDal-=9hb!NVb>(Nbqnlf3aA#81%E&DOXZ;U$@MX&Ykdo5T$d@k@M!AncL(~y!i(Mr zm`&eZbkOYBgF_qF?O3q!sk^GDn&^#kiT>iZ9tJB8Tzb*B`gelwo(2D<9e+ ztp9vt_50;5cJ;!Q0WwMt__@S>1r>RDi*5AlNLa93)PI?Sr&o3TOF_9EPd3Un$Ul@b zchp~Fy6V|#ErFpS?c%3AFEUWfs%!SD9^f r_oA=lJ-1C4cWx`qJHujMqrRVUnWye)Ye^wrP*U`C^>bP0l+XkKoJ~) zospi{W+1`l&m=TKfW33_HAbPX=4+0sTlhMd3j#Qg9{l}wulVPkzfW43u_(5+%%63^ zKyw~{oyNwUti2B^A5U(o&$;jR?du%gH`&IFocE{Pko~pTD(?yZ?pw3Cc4wU9WqGqm zbpI2UOWM!P?>uHSov440vz$fx;k|~l`_^R|C3M%Zy!kkbfmOqSkt?8qN#^LOHfFx% z{U#2KeLIQ*wl8E7SpbnN__0)S+qL+_vQUSz%qs>-{sr>ZNh!TdA_^@6!l9PxvfDz| Q>w~=N>FVdQ&MBb@04i@~<^TWy literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/64/download-2-line.png b/pcsx2-qt/resources/icons/black/64/download-2-line.png new file mode 100644 index 0000000000000000000000000000000000000000..3a50a4e69db5f1e08d4ebd96085231445129406f GIT binary patch literal 256 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I0wfs{c7_5;#^NA%Cx&(BWL^R}yF6VSLn>~) zy?jyfAcFwQLv2URmkPQ|Ra7+;Wn;9jGcqJFaBr$!Z}-{x&}Wk44ofy`glX(f`ga`P9xB_Wf z0KVyybAjf{mIV0)Gbq^S*RS{QKmXrB;Qssg4+jh`6f`VIm=Lgk!-M&g&uwpGU|{0* zba4!+xb=40#jrI7Jg&_LrV3<7M>!t+|Nq?UfNNTpg;#xkF#Gq+H=e}`!M`4dXU@B| z?)C3=+4H5UX4Ic&o-Z-ay^7&woJ7UTC&!<*g`PPtvVNzG!TK+i5~9O1UDv!Qg+Zw`1~Eu|@A> zC0WG#-w8$?+!vrw)<5Z8#rZIgYcC&5buYd*!N@U;mG^!`SEROJ#bj2ABMs*mIO8~& zzF2?a~hA94_P+6!MwTeLO1x~2vlt1d5DJ7uu%W3nX>UO6E|1Lja+rav7 zkGMnA7f#+Q|6*bMz~YLcSdsw5gTe~DWM4f(aIS# literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/64/file-add-line.png b/pcsx2-qt/resources/icons/black/64/file-add-line.png new file mode 100644 index 0000000000000000000000000000000000000000..93bcecffa1f9f4079d84919b4942658b3f462e4c GIT binary patch literal 284 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I0wfs{c7_5;#^NA%Cx&(BWL^R}*F0SuLn>~) zy~56T*nr37Vsn5;Vh~qj07qJYu?9=G<0+N21@HD&2QGUjTp?*zv(GNCPgUqDm+_yy ztRgj6|F=lp(skTf(PZYGz>s}H{vwNQ_=EYHo(~vR8rdhX$Tas@Aye(!9aI@+;LSsDYX3$ub_>X^Q5p zop+e|3L2OXJ-rjn*wM@M0IXg#$T_s0;mj+01!vyVKXznrb^J(DRXVbuL&z@v|2D=| WdaEwK_BtpE^0lX{pUXO@geCxNVq%W~ literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/64/file-line.png b/pcsx2-qt/resources/icons/black/64/file-line.png new file mode 100644 index 0000000000000000000000000000000000000000..6eb9b95c01c4c80ad40345e1b5779a5a0db89162 GIT binary patch literal 256 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I0wfs{c7_5;#^NA%Cx&(BWL^R}yF6VSLn>~) zz3Rx=lpx^pu+xD>S)!4{&45WT}Iri&#v=n@~)$A8fL!z+rS{VN{8Xc`{=nU<_pAj%x7Z}aA06W5InE=Zf)Rt sAj!@Wu;!;yL2p;p;V!ArQ%~*Q&bzxv-cg#@`X|U|p00i_>zopr0Qp^9umAu6 literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/64/file-list-line.png b/pcsx2-qt/resources/icons/black/64/file-list-line.png new file mode 100644 index 0000000000000000000000000000000000000000..2ad3caaacdbdb5aeb98547f9ccf8c76484665f97 GIT binary patch literal 227 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I0wfs{c7_5;#^NA%Cx&(BWL^R}b3I)gLn>~) zy~)nlY{0;JadN{H3ynSpE}=&ZoJSZfGG&(t-T3`avSO~zoZa`hw4!#&X06nah*fzX zky285eP25Br?m{snTy*}8z!v{%VQQ$aBz5Fym14+!bJ6HEF2%|A1q{GOVWn zo3E-q!r6c1Eo2oA{BLY$VqrPe$@-sNj!8ga!SdTH>KGH2v;4{XaDpvMDYIS4^jvIy Yy{oVEQkjOwydcMWy85}Sb4q9e0QqK4`v3p{ literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/64/file-reduce-line.png b/pcsx2-qt/resources/icons/black/64/file-reduce-line.png new file mode 100644 index 0000000000000000000000000000000000000000..5ea5612233d0a2ece4dbfd496a0a945e87140dd8 GIT binary patch literal 254 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I0wfs{c7_5;#^NA%Cx&(BWL^R}J3L(+Ln>~) zy%NZH$UwyTqKCt!ISGkE69U+;FbajSALT6mV8Ae8D=&k}^#eb-Y~BlRKQ8DUuKwo2 z9_A#6t#b<;_e>XX336J&Y4f1xl7M3Zqi~cMizBxJM-+qO1tyLru>%5M80IZt|G;9a z5OANj!=Ay%e3mPNoGDvE10$P?PKqb92FIpPiy4@OxFV&PKk!R{#KA;}PV*{WhRFH+ u5rT)pY+|P|8FidJq0wZNR&nq7Tdvuz!NOZyPx$heJ zJJplw^qG}&YGeP{fh9}6W3Fg9T+|(b>}^32;`VC^x9RE$w*o>YV5HBeBjf`r zGLmr=qySIQ12PD5fF5RF1+cvw5E;02 zJ<$+A4`>pr-T_eITbBZ?xlOj^{ZwTD|IYYU*eU@BGi|0O0)Z7HmJn2cXr>&A*mP!E ztpe1ktHT_HK?T^qj_>hVndPDcU@pK7KEW=h0UVP6I#dEZ_Fj%r(LOVRwH|`?`5; z@2pwwXV{q^@86}C;MafV!rZwAir-Z^isG9r1bQ0ex3y_C$qHHW2Uaq{NEl89eL0BE#_K?D+n+&v@Nu-^x-jiWz5$wuT%6x>w?`( zVLo5t8lJtcaxMKo_Za&IH@!Evm<1+kOFd`bl9IAsT8XjamCTe(TRBICw|{GAPWs^c zVHRt5citzaJq*X|H6->u=d8HsE+El&?(4>i^be9Px|5^wrm-ZPToHEU8>_g|4F&PC z$Gas{pLlG3pT=T4(|>{Ug|`iTKJ~MiJ(a}6=k4KF`EEZcw86H_fi(n(4)hf+d&k4D x-Of%yx$o>8-y&8OJrDP!*B4yeea}9ADtPx$R!KxbRCwC$m04lKKnz3&f*}M#AuI$#2!uc|goR)TVPG-HGH@6q40<2aQ(JfB zUio$NWFguN^{3ZV)AeGPnYE2xadI7!k_J6>wT>0}70)tg{8Z^W($M@6PD_c+CtAes}(q^!<0h z+B4t{&(%)RL&n5syxJ?K2m>DBF=|YCxyN0wU#bn*P=g6f*%_(elj-|s3eP}jzyjM+ z1rQ?$CQ<+X4D~) zy%EXS6d=;_u+u?A%7aB&pi%9XgVKcqml+mac<|%dzW?veozXsh{M_Cmnd3LT7PfwR zb*-vGYU!uBEq*`aoHUMd${gU7F<4=BIjw)go|VSF`pizxgS7*-*&ei5G8Z@*FsK(C zm?dCdlEN^Lfq|Ka;q-r@13;SN+s>yzN+a}biG^jK#{Va_2@Q;FQ^e<0Gkn-_pdoIu z`j-7nADZfn7F0_vNS%CjPjBC{fYpINpY-iM{@$W*GZTY<)0K_VhHN0;dAjPx$9!W$&RCwC$*jaG`Q4odUBjHLw0uF)%B#eWMgh+^kTnE7c;~+Q)4j2cW*UtX4 zoUDENnW~SfpH;P5t(Mkwr@%Mp;u{sS literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/64/folder-reduce-line.png b/pcsx2-qt/resources/icons/black/64/folder-reduce-line.png new file mode 100644 index 0000000000000000000000000000000000000000..9d70dea9bd8676860a006c0dd32e4e31fc4f18c7 GIT binary patch literal 245 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I0wfs{c7_5;#^NA%Cx&(BWL^R}>pfi@Ln>~) zy%EXSlpx~ru+u?A%7aB&pi%9XgVKcqw;2}y;a%7N?)8r9b0R-tB3xazgf4CU^lEEW z#oVBueuoy+=qpb7#A$ax{YS&pS;nuwah#rQcKM5uLdm|c%(WjG>=NHG);L8l%zwaa z*0Lk&hk3Y*0-R=l&5GuXp(p;~|S-%_in krE#u5pG>`b-rr>l+rMOOk@|D0J3-1lUHx3vIVCg!0FP;1G5`Po literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/64/folder-settings-line.png b/pcsx2-qt/resources/icons/black/64/folder-settings-line.png new file mode 100644 index 0000000000000000000000000000000000000000..35f46d28d5fca93a58f75de2b7088bb4fc3b751a GIT binary patch literal 423 zcmV;Y0a*TtP)Px$VM#EaR3gc4yXg_fJ(ptagcF99S{eZJ*Q#M znSQLf_h+}KdqPf5PR<|&OZ>&1F?wn|{+5Il{gh!ENYfC#~)0n!QbfIu(_btwRen6UvFP%`;#DGf+i|D^yntZ%OcP~k^1 zXNwf+Vt(7^soB#3EE9p+3jngcd%)^yfR2NyuLh_&m?He=B2m}q6zS>W7*cU7S%Q^AfrH(Nho_69Xj(W`Zps6P*2aUJ2c<(SwMF=xBqU-4_b3WnG4U)~%ys0z kNB&@jXDWPK*Bm$CVz_kN$z32+Dg)#iPgg&ebxsLQ0N!piZU6uP literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/64/gamepad-line.png b/pcsx2-qt/resources/icons/black/64/gamepad-line.png new file mode 100644 index 0000000000000000000000000000000000000000..e7e6c698c449df4ba35454a0db624b5d0844b823 GIT binary patch literal 449 zcmV;y0Y3hTP)Px$dr3q=RCwC$m(M!ze3*2PUws_|674qsG)up` z2i%m8(~_CnKy$i%`Spa5pDJ#yilviMht&VldD>I_GM zY>glM{qN5uFu`leZ)MD>&(_Q5t$BPN%aViIL&L`Dr^;d-on&5_xd%m8mRuzz7uk&(IJ%=Zdc1e?}gQql^p-X3OvS W#~Nq!YW-Z0Lp)vmT-G@yGywn*b3XY1 literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/64/layout-grid-line.png b/pcsx2-qt/resources/icons/black/64/layout-grid-line.png new file mode 100644 index 0000000000000000000000000000000000000000..2e4cb8d7fe6c19e1bd16335a19e2dcddd17fa13d GIT binary patch literal 218 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I0wfs{c7_5;#^NA%Cx&(BWL^R}Q#@T9Ln>~) zz2VO2WGK+`aH4>_f@Yr(r$vISir*%_LsQRPx@}+V`%`0mmw?l@Ng*cj7mBzhn8shJ zk=alsxj>Hn#%YFc9~;)CF8JH_QSrdT2KL^5W{_PV;QZR*IPc?ThP;yuJ9Gl}SN?e} z-v*IkXjtaD;O(=I$8>Mrj=x&>;`FN}TJ^u4?fdQJcw literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/64/list-check.png b/pcsx2-qt/resources/icons/black/64/list-check.png new file mode 100644 index 0000000000000000000000000000000000000000..d5973507c3762d6bbd2a10aaf4125782e1e5c801 GIT binary patch literal 172 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I0wfs{c7_5;#^NA%Cx&(BWL^R}X`U{QAr-gY zUJGYzP>^7FXfMR8u&8%|q}?uc>36%CPKhzB=)XI4?)|L+iw<-DWKg%;)4>0L`A0*{ z89fGvm*RoG@&=u}{ofhn1-5a-R`BLHGc?>gI=Sb&gRA@jb`3+pswZWP4)4bP0l+XkK`S>^V literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/64/pause-line.png b/pcsx2-qt/resources/icons/black/64/pause-line.png new file mode 100644 index 0000000000000000000000000000000000000000..01b32a30b4cf5ff2e8766109a37037e0db7c3bd3 GIT binary patch literal 172 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0J3?w7mbKU|e#^NA%Cx&(BWL^R}LIFM@u0R?M zJkXY01QcQ`3GxeO*l+OP;lR!LH`fA1%sgEjLn>}1ORzc%aBy)cT68rvNpSV_`U=RT zCqF*8Bq8MBil#u;#avF#%>pu4F9fs&8hl}3=~) zy_Cqv=qSMYppjuw4ol!VPTr0Z-VBH4-E0rk8~79MiT$`Y`>ng$7zFJ=iW7pN7ld0lVkOg0D0Tf)z4*}Q$iB}XrFIa literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/64/refresh-line.png b/pcsx2-qt/resources/icons/black/64/refresh-line.png new file mode 100644 index 0000000000000000000000000000000000000000..cc8e735e24d66ffdc6b99df2fe0027605031bb17 GIT binary patch literal 661 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I3?%1nZ+ru!7>k44ofy`glX(f`ga`P9xB_Wf z0KVyybAjf{mIV0)GYHtn*Uw*{ui)Q*{=dWf`vDgYd@$Hh@Sq`K!h-#!4L_xT?mF!0 z;uunK>+QAhsy7NetO=TlzYgwAu>KwY{Arz!c+r$^X8)5G{^gg-~eEeE0YYm*M>UU)zCrRlU5&dL`2FigOUf6X ztT~rsJK@Rmn48xR!f|62BZ(SL! zb?OB#=kxlMzsEXdR`$LA7~8#N@-5+X?PyoE7yd!vOTsp)9O+mdxk1~vJly=zqbk44ofy`glX(f`ga`P9xB_Wf z0KVyybAjf{mIV0)Gbq^S*RS{QKmXrB;Qssg0~ZVu8a_-YSnwcV!~XeYueRI(y6b?a zi(^Q|t+&_O^VS#$uwL-4K5a1J=nTI9|L6Kg6kX#=o?&uTru0;){*TYACz+i7yy(Pd z9p4X`|K(I!Pr0+cD2%)8Id}7~?`@&EpWbJEK*VC%-JF~f7N;?RU7XG zNiEN&<#8PjmdKI;Vst0EeLb{r~^~ literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/64/save-3-line.png b/pcsx2-qt/resources/icons/black/64/save-3-line.png new file mode 100644 index 0000000000000000000000000000000000000000..c6687e8cd9498e9f261c49e85b13cb6e30f48f22 GIT binary patch literal 244 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I0wfs{c7_5;#^NA%Cx&(BWL^R}>pWc?Ln>~) zJ?GBI6v)%^aN-vk0nXPck2y4LI4uq^@g2IPc)NOby`SY%=H#BITown}?g+judY`T! zkrUT+)_22C4!H#ru5Wp|*;pmIX|?he7LI$K-VR4LKi~{z{=_m@>H+8UlZ^8W*cm*Y zv3Z0uFtU{71v4;lC>;2e>jC6xEO}TW)36|OlOT{I;K0DB$#?V4-3-k)#`V|Jl-HXv nJzDWzIg49aNQp@z`AjMEXQg?nzdq&}fPCZW>gTe~DWM4f%|}!T literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/64/screenshot-2-line.png b/pcsx2-qt/resources/icons/black/64/screenshot-2-line.png new file mode 100644 index 0000000000000000000000000000000000000000..ef9c1d3917c4c48b21c823c26df721e58b0d4b81 GIT binary patch literal 399 zcmV;A0dW3_P)Px$Nl8RORCwC$m|1nhFc1I-f*=Tj8N>`42w@O2h#9mD8U`r?i$TqxWsoq~{;hAl z7gbfSo%!=Qz7Gt;Fcyef>#!P82Z9a@_swqsaV(%#oep!<>OiH#s{7_Qj5s^hkqb90 zHSID?XL%V~fCXfxqC;k0d881IE3)c^B3zJ7_k1~fB;plUZ1?9q44qmr;5pGvd_&Zr zPy0PLMC~SCIO5VS!}!+T@C-Ca#B=g1S;U4ZA$Dfu4+zZh!(mvOJK%* zDjCot3)_+bFOj#QO#cU#3y42@Lg~x!FiGg~r31iorbh_03G>^-DEfb3xqw}Q#|)QY t4$}iHz$2LqX#sHHJ~) zJ?Gij;vnF1aYBI5ga(!+4^j%kEST6YZ@;&F&53*UPc;4icW(5LUVp2k{#DVZ?tjwx zfu1L&%~PJ#Hq8IdSXajv!}P$yUTXuxffLdU_VL@V%l|KQu&I~X@$Yvff5NlJ2Or-* zV=o-w_xU_4&+(t(Ol~i?GVGONI53AF?@rc azu#e775MLN@3pBtAl06(elF{r5}E+5Cta}s literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/64/settings-3-line.png b/pcsx2-qt/resources/icons/black/64/settings-3-line.png new file mode 100644 index 0000000000000000000000000000000000000000..d4e5e4b73c014067d4af56fcd05c57a578710649 GIT binary patch literal 755 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I3?%1nZ+ru!7>k44ofy`glX(f`ga`P9xB_Wf z0KVyybAjf{mIV0)GYHt{*YDr(|G<5N1px`~9Te8j@Av<3{zAipf(h}AW@kP!Ffb{4 zx;TbZ+cHrAB%e&D`O9S6als>Ss4Lxps{o=u( zX>;bd^3K&PntA6c7NQzcPk#`sGa0tWMboRV)!VS_kkmO?d(Roz7n&}U9Z2~RH@Rjcz5uD z{PaTGyB-!M5;r~Gm)mR-jE-D;$Y5!}x%n-LQBzIMDKkp@a;Q}?DfbxL+`J$Q0uQ-%RQ5lWmP|l_C7lO z^;E*7cFrBFc{T|@w(R>Pw}t0OohGxyyUZ@-_jA@)-tR5+`pyu}pYpuuq|}ce(=I%H z{&dn*9b2>3{_A@T0w=FjS^alWr}6Up2j~3zaoX%$`qsZ)^%iamze?$9x+N$1aR2@8 aT+6uZrnXo6r{kwVsnyff&t;ucLK6T4tT?g& literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/64/shut-down-line.png b/pcsx2-qt/resources/icons/black/64/shut-down-line.png new file mode 100644 index 0000000000000000000000000000000000000000..ef2f0af3a7ab1cb26b639aecf5104e81c6450f4e GIT binary patch literal 640 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I3?%1nZ+ru!7>k44ofy`glX(f`_yzccxB_V^ zf+LHTMFNeJDhcunW|*-5z<+`I^Xm-)-YYmPC}>Ey@ZtW32mSN&53Oq63UtdZPZ!6K zid%25h96p^z{C3B!JHf1%GdtpU-?wdbn*7QcQyT7?R;N8t)H2a{^GmshU!20jB_@L z9ngGf;k=IZ$%z>vu51f>*rzaTa#|Um7=CqQ!>_mXCJR><7;R)|4!#;75N@&cqv95e zP#;B0zqw|eV$0_+&9kueWLdFv52sW0)`?LyK3mieE_}Y4;a#DJ3wuGq(XU~8GJJL7 zJbd5IMQ*qkwO?{WagBO|Y#H<4fGSx5#!cIO6Y^eHGnOXo)q3L=#B}6&*FIro0mdS! zm$Th^yx2}Wdc=K~H$m`1;`RG{3+~yMngxAWSiAk6*1w{LiL9nZ2~(Ueyl~!-RQ{UH ze!<~JPsQmQgUj2Nh^;>w78ZH);l0^mcQwAx6+9jp$i8k#P)+cR3q8II#oen9E_<-2 zn9&O>j8C6pkYg{vfE9hg#`$@9< zq-Uvd$@?2>`I){38GW literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/64/tv-2-line.png b/pcsx2-qt/resources/icons/black/64/tv-2-line.png new file mode 100644 index 0000000000000000000000000000000000000000..36f1eea19feeb9b33cda58d3f85935e678340a9c GIT binary patch literal 210 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I0wfs{c7_5;#^NA%Cx&(BWL^R}J)SO(Ar-gY z-Uw%Gb`W5DII*S0!@@lxg3}^V^}!RB3D3WH&Hu?;qZxAMj9%8t9^v(uJ}o$8aha?9 zbsBTY9o=;1C$AWpmvfr!;rCdkai5(-K*6Dbfsu&?#?bo9vU)wkzD1ok88&{YKGeo8 z+BB!av3YOgh5wlc!=D~$zx#`k=~4gjeK)JDnX~JY9KTg)wt?L0>FVdQ&MBb@009F` A)&Kwi literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/64/volume-up-line.png b/pcsx2-qt/resources/icons/black/64/volume-up-line.png new file mode 100644 index 0000000000000000000000000000000000000000..1b0f472ca2e5f04563478a198cb4f1738dd992bd GIT binary patch literal 634 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I3?%1nZ+ru!7>k44ofy`glX(f`cm()_xB_Xa zfYmK)W&;h9C<*clW)P@1xc~qC`Go!J8|Eu4nBZ{X!-EY40S7*ty<7lv!yZo;$B>F! zZ>O?OdSk%jvh?E#zMG1>{`-no9b6Uo!uXAL&%@9twO^0A=kwp6bMZupSh@Ir3;UMM zZHKx~Jg%8Ixo!5Igfrrr=Z~yEB6KAq_eF=q5s?6=V(;H2aYl`)FTR~tXJEQ4kkRh; zjpfQ|Zqu0yF8pRv*Z;9&Z?!`ZQ{IewnHTqOuAV0uGo9PQqdp@55qMnV@#5spVT#c%wPXVW*i4XI>O#DRM<$unuCH zQ1aA0v?y*X8KbaaQZ^5 z(3w}i%{c@T|Mqcsihta7;IYx!>4tHF38yAKy?8oeU+_lV9jA?(r!E&hdpEL4>U`s= z$vKS`k1Zb0m0t8~_fdg{*?Xf?&Gbd)2>Bk4YS?_^b<-Lbg=??XnVinB8hpu-&-FDt z@IaO&(p{yLmp|pP8)L87gsO;d_srh$&)e1^+IT@Fv+-})wdwQAZTBya-IW*|uyF6< z+NAx88Amu04o=o}*wyJ5;G7^?rN&(CFM0M`O|}2b+qsEW+}Hc|{dg4qfkh!jPI#-_ RC3aAv@pScbS?83{1ORf|03QGV literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/64/window-2-line.png b/pcsx2-qt/resources/icons/black/64/window-2-line.png new file mode 100644 index 0000000000000000000000000000000000000000..599967840d31c951a99f6cf9990251388a721cb8 GIT binary patch literal 208 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I0wfs{c7_5;#^NA%Cx&(BWL^R}U7jwEAr-gY z-biO;Ruph~II&@h1;?ZU79piX*#;4roVsP-W9svHYBa-LT}r|NruA=F!>;<%!+Y25 zL=%ULcXLb{&dOa7VUMVmPmof&&2ixUnPWh!1m5R-*KGLAAg;`Chrd7$WH$r^Y?V*= w%GdC`NpjDsxtCABTChk@zT)qv&%%PTjMHmx*IRk8z5%K9boFyt=akR{0A~3`wEzGB literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/black/index.theme b/pcsx2-qt/resources/icons/black/index.theme new file mode 100644 index 0000000000..97fdbceaf0 --- /dev/null +++ b/pcsx2-qt/resources/icons/black/index.theme @@ -0,0 +1,15 @@ +[Icon Theme] +Name=Black Icon Theme +Comment=Black Icon Theme + +[16] +Size=16 +Type=Fixed + +[32] +Size=32 +Type=Fixed + +[64] +Size=64 +Type=Fixed diff --git a/pcsx2-qt/resources/icons/discord.png b/pcsx2-qt/resources/icons/discord.png new file mode 100644 index 0000000000000000000000000000000000000000..e3ba8fddf8cfa6942a0395faaa991f715a19f7e7 GIT binary patch literal 1053 zcmV+&1mgRNP)Z^wZ~@|8^Z%c9jjJN#gqWv0 zllA7&uyo+;k##B$8Gjp3)ylW1J~dwVkM0HaM{oy^^`qdW)_%K3@Yb%q>;-?o-Tcl3 z_4fUOcKxC9d`JD#TRc}9pcIWtt(>4%sg@H|E5FJKcA5GSzkDxmbHl{`>1vD$C_1=f0dB9AHas^VYY~?{X z4meE~1IyL>eO>ov^NBG{1`v8H#sIo4K)^992p6kl+=U9Aa2SUWT81MV)CyR<+r_^; ztvF_(4q?o0R)EA5Lf7lT+vq#PRG?cG;5lqI;b&y)b9}1;E$zVq^inNffbb9&Vu(?$ zfZKX8fo}6$0c*j6v{urPth5#VKUfsd~RRCrJ#T!!*@ zUo1X!$P@HRK+=x%k_L{jasDPt5k*2Su#=K2fmlgdGa`y;WE_Jz*l4?$ZU&NJC9VW= z3aPc0_X5nD$Zw*2w)@6ql&N~yG^-SussR;+0!nOs&m}vrB+GDM8{{Mn76YE%U zU>-`O8HdGn=lFQS7*Yh6yMYM&Q16VA2mu9zSEG&|`hhShx_v*OHfA(UZEy|J(luq+ zrs#_+4ka1^2;{I^p#RM^?;C>Bm+jKm{n7(R>5-@O&{cNqD?fOaAAQRY-x;s>kN^Jw X1g8pVGszx=00000NkvXXu0mjfv>^>9 literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/flag-eu.png b/pcsx2-qt/resources/icons/flag-eu.png new file mode 100644 index 0000000000000000000000000000000000000000..564a732674415f48c3aba112292e6e076439ff20 GIT binary patch literal 282 zcmeAS@N?(olHy`uVBq!ia0vp^T0ktv!VDx=uuZ%Sq|^g^LR^6~gYnGz`g#V#*(~~V z8FlBg8_u56>rh?iUsvbX-s;ia=GNWrdhNK6I8Y^HNswPKgTu2MX+X|GPZ!4!jq}M0 zoEnKt3sx*ruo6(x(3+Ffp{VsHY0s$}M+IWH9R5;Lz`komM&$x-fyG_3RF*TIG%-#W z3G8;-p!G3L2>4J&?zhy)6{J4&<)h?W_9_S{DQB6RN^{rfx{Us z8W$D{q)&by_uRzU@u7*Z`xXOjb3bFf;~bl39$f8{&bH{dtHy=33nLz#Wv$K;5;!{1 b%Y%{O{W)9p3tJ-=f;{Qz>gTe~DWM4fuB>8R literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/flag-eu.svg b/pcsx2-qt/resources/icons/flag-eu.svg new file mode 100644 index 0000000000..a605f684ef --- /dev/null +++ b/pcsx2-qt/resources/icons/flag-eu.svg @@ -0,0 +1,31 @@ + + + +European Union flag + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pcsx2-qt/resources/icons/flag-eu@2x.png b/pcsx2-qt/resources/icons/flag-eu@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..b2c0e2eaf65f67568661f302bbd14eddfa796b0f GIT binary patch literal 619 zcmV-x0+juUP)p$a&Z7(k2wD5GU>T@X8w9z=*vVQWxiYH@c~q?I=dI+7DU zjxthw?!*fD%mn}10RGbe03oX`Oo1p%fjCunL0fW1UT%ScOof9Px#0d!JMQvg8b*k%9#0hmcdK~z}7?U(6p zgD?<;28+_s5xyt9HC%MmnG+=ey2Q$!3j!nLiZ;?`knHW z#N+~%Jllr_gQF%FsrWthXZt#e?-Cz z714XXvO|rBDkPXukb+B>9|Z>dn67vp+MUGi6kDP8{Or~(P+NZ5>vkw~ek$4OcBsxz zC41ctRr#sBx&99qUmE)@VED7KFsl4YdB^Fh+5K#HqdM_wXH=C5V`Ki1k zRAV`Gek$(?Wq{64DDMpI=I1c(Q3JY0{t{?!8oe|ieF5#mB;7WYNH+ig002ovPDHLk FV1frZ6+-|3 literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/flag-jp.png b/pcsx2-qt/resources/icons/flag-jp.png new file mode 100644 index 0000000000000000000000000000000000000000..7a2502d9a8b37f85e77c1366c92d60b7fbe6721c GIT binary patch literal 274 zcmV+t0qy>YP)$1PiKw`~BVYL7m_~+~S=j-04 zDA@o200DGTPE!Ct=GbNc004?fL_t(I%eB+X4ul{Kg<(4_1-#Guzw9`=DCR_M()h2x z1<=yH)yLIA$x)ojmWv+DFwhqEIroOIDGgI)uG2sdRHUf#mTD2Xqe5j#=}%Ky3JFq^ zqwa9WIyM&R+|n7T$+dD*UCjc`d@56%;sL%!9oLFK7LFi3SX`I*65?vdKMWS9uDX}{ Y0S-C{S|5M*EC2ui07*qoM6N<$g3}y#mH+?% literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/flag-jp.svg b/pcsx2-qt/resources/icons/flag-jp.svg new file mode 100644 index 0000000000..5b83bd7385 --- /dev/null +++ b/pcsx2-qt/resources/icons/flag-jp.svg @@ -0,0 +1,6 @@ + + +Japanese flag + + + \ No newline at end of file diff --git a/pcsx2-qt/resources/icons/flag-jp@2x.png b/pcsx2-qt/resources/icons/flag-jp@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..4a064d6bc86b14d2163096f2e0afcf6550cd20d3 GIT binary patch literal 421 zcmV;W0b2fvP)8`uwoUPJRaL6uE!WTce03^#k zUGmM<^32ujwZiGGyVqlY)@6RoJzbk<3hMv>00DGTPE!Ct=GbNc009L_L_t(o!|m3~ z62c%11yCR2Os{YMsD>I^0#+oJlDS*_5m)+hm!dNR)yW^jDNg!YNTA z|C9(F9e2Jq&GqcYWeSu)b=w;&F@0OhG8$#Vr4!EVBu41&0^&H7QQhny+$$qJ*jojT ztJEF$Z_=~-qXWmFm6+xuqE-==67Jrd)_MnNQ9^CKIc04PQdimyVRY!Ul@bnPq2W>w zASK$)qpcE!>&2Mx+NnJ+nk6{z3RxlL7}%{R2k30->qTt3v|k;>=rFa4(ZG!#Ya z(>H1JYSMXKCldDvU3@uS(cxht$FV0cpyfMCPKMq!jOPXYV>DSdB`eAkuN@m*7p=k5 P00000NkvXXu0mjfjts#; literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/flag-other.png b/pcsx2-qt/resources/icons/flag-other.png new file mode 100644 index 0000000000000000000000000000000000000000..fe5210eea5ca818dcb5dfcef3faa3dca07e61254 GIT binary patch literal 682 zcmeAS@N?(olHy`uVBq!ia0vp^T0ktr!3-o7?C#zKQY`6?zK#qG8~eHcB(j2plRbib z85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP~v=mPl#(#LH?nG2d7P) za_;Qe`}gj>dHwp+$B$pXe*N_6)617H@7%t9;`s58A3y&8|Nq~=f6tyhn>%OrwX0YE z{{8#@-Mb}A7Wen|ynFX<_Ndfe2edSG7oCzV?Fyd$&KT`JWzSVY+cvP*3?kabC`N_T}uJNli0O z_$=Xn^_jhCVSt5DFXsyVzWISdtE7FGeek{{X?NC0=yA+EcW&1|(}en`X|4##W|{ZO zX4S>q(iR>o@23~o_L#kybt!eW%&n~1bJscL++@BfzazHMtcGW?;f2oExi@ECT6FfA z(yjo9Wz#k_tb1|2|9VrIqqNrcwiTNL*q`L-=QzpodGS4}+nR83Rr@tNA$4{BFWvIT ztgLrdEa0|S{WJZ`t004R>004l5008;`004mK004C`008P>0026e000+ooVrmw0003d zP)t-sa&T|L!NHc5l*-A;)zsAA-rnWp+5xMa<#Rzo}HZB+}qvU z+|0|%#>K_Q#>SVHm3Ve_@$vDdrKFmen8(M*va+$Vv9aLa-}LnKxVN^-%F6fm_q4OJ zzP-HV<>aTPrh9pK&CSi^BO`S|$M)6?qoUT;)!NzF%gV~r)6>+{)Z^mf>+0&}=H@xZCKLbw00Cl4M?@10 zyIht4000McNliru`=7qvgq>Yu+#42`+tdElnl>WFw5>t*6@Gj?9Bc~VfNX-9Osc9kMw$k`#m(m_eEpz zgb?c=7!-$Cpwh66+!;|+jS?LlL!C+L8U-2|N8cwk5g1Kp&`&WY1ES^>^2l@`bS8^D zGaCS%oA)-6u|SS4BERH_(Iv$fMa!hBVhBMgW6zypA0=XbZt*LCieP{h>z)U zmCx6ddJ-X_Z8qSqmrH>se?t`8pHAQxJ!No5oz=0!e@>59o7jO;>2K2ue7asCh{$HndmTEchn6<$IZ$;bwe@n=P6k9wWo!dH3nUZ$4s3?L4)(&cwo}{ zU{85uQYD|@J7UvM({M7eL7%}B)!3k_8*MVG=P$M^@$UX3@mJ8-H+KqbPcegFHKLe0-i%QGYr) zZFY5;a&eYOM|VL$aXvk8EGuGqd7Wiri~0WlZEcfTS%gYSc}7KcL_>5VAzO!rri6o| zPEC6?G-)v~W*r!!Q(uVN806 zBUzSYImEp;LJEZ5`};o-47m#slNqtdH)r`T?%cgy*;lej`IuM^`WkX9DV%v5uNmDqD(HiA;om|||Hi@V#t1k(6gSb0kFB*{sdTjIt!+jc7w zcpBI=FwKD4!4!+dLrVs!*TlwD*I`E7%+_rgqzwBp_FT*uc0C+>wsCRZl|YJKBAV4^ zX4{!rXEX}jt$$@z%8LXQgcK+#cv?N5oE}IZH75FI`o#F&ENP7zYf9pU2q(cReD%snSPjQ(ow$5T|T>t<807*qoM6N<$f?ydG AfB*mh literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/flag-us.svg b/pcsx2-qt/resources/icons/flag-us.svg new file mode 100644 index 0000000000..93a01e5c69 --- /dev/null +++ b/pcsx2-qt/resources/icons/flag-us.svg @@ -0,0 +1,28 @@ + + +United States flag, reworked as 2:3. This involved shrinking the stripes from the right until the flag was the right aspect, then shrinking the canton horizontally so that it only occupied 1/2 of the flag's width, then transposing the stars so that they were centered within the canton. + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pcsx2-qt/resources/icons/flag-us@2x.png b/pcsx2-qt/resources/icons/flag-us@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..835db3a9a898ef079e6fedfa1bae5654da157702 GIT binary patch literal 1248 zcmV<61Rwi}P)DJca%F5V)f1!PS zoDk`kz{jeoz|oVQWn6^Ebb`TTbGb@ew@F&A5+swh4KM%z00DGTPE!Ct=GbNc00U@A zL_t(o!|l|^&f72$1mN`EHoN4qS(fD9?YMX6Z|S}Fx#uSwdUrn?U<@#Mp+^v^|(vz2=R5-14Z{2);EtOYlqE#m_AP?7|k=sRJ zhGLsh!}>^q4mvAU`*<;(|M>)HM@s?V8chWdJ$MiSsAj_fh~>y4nDEB&bbhIj+U?DN zq}m?>34L&&1;KD%NrKs)(?W-p%P+2^@k&XTELqFlP+F^d1!Ju#^t81y?6OUcN>JGP zrrhnnWx-*+Dy4RJ?_=0gHgX*`*0zhKUCZ}3K+zE6)^Im1&GjEFmkY( z3*g|W5hv@PO68Rb6`*Ss2{2x^6@WTC{Fd!yj6lVf!+?@^n$9mJWo)GiN<@1DR3snm zOHw%4i-dw|B{Y;g{*&qaq0mXnm%x$9moQd59e;m{fR>sqd|zQ=XZK`>-DvpGy{WFeGq8o=~K`eo=%x{@SsX)g61UJzEmod zs+c1*wB8VF>U2mmZPsLvaz!Ok*f;0W`9le13Y8%$>Glq^BDKGtgdX&rR9LMPj1sk* zk58xiVXDB zt!m-c%`aO%er=yL0->WvGZ4lMhA1Tt2gV4PJ&Lpl8c$sG?tHpGhKkB2-=iotTq*#yY{md|xNsPV_T1GzUr+bP z`jr`!$@Pa=R#ZRGQKS#*N|kZgcjRDJN!H)?odSn00Km{e!rI?5M1a7BxP56Zm(M@U z{mR^reE2ipf7RWcTZz z$;j>>|Ama~e*G&M+5Jr~X2dJKmJzS?YDT=$OBwM>Gjr^}f5=y#-o_!(V?opa0000< KMNUMnLSTY7%x(1m literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/logo.png b/pcsx2-qt/resources/icons/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..f715dd5c49f56b251ce9809decc26f887f2cc820 GIT binary patch literal 2108 zcmX|Cc{tST7ax(C7R*pcmJBH&SC)oMg_&$)EXiKB)F|69ZU$E=%Z#a!8-pk@Ldu{- z=_)ZKu2RBSuPhnH+MVorf8TyRxAVNuc|Yfz_nh-N&-vq}I}j};wkm9e!C(^BRyZdZ zOauX81B57~%_dyRAxEkM!5I&kN&g8f926B5K^i(N77NlpBR|2^)YN|uf829(b2o3^ zguueW0`wp?H5Jl751Y-Fz5FAW%jMS9)yX9P!2Vata&mGY0Et&E7#SJK&(BXzPKL~x znVG@CK^~8H`56eW1W*b-8^~S-h$z5dFrb?MPfFtR`G)rZz6i9px0~gI%F0T}f3gNZ z{{H^{FJHbu3_pMVtX}}^9)j3f0Cm?&2hwyZZZ1(X_~ z#RBE!Cnq6#(69o5U}a@RC=@~+CnhGw$H%RofW^f{ zXngYu;7|>QhlhuThE6;Nc2z*{0WjqN+b00?2pr=8!&0DC1T+ePZVA{^0FXIAArI&j z12r}f$pGS+V19mnY-|jcGVAUbr3$UYjp9T+3DY{IdWvMB<%6)WQng(~98?mU^HTY$ z?RiL2Bj}8DN=o#R&HjeFzE7NU+BaES&P<364)AotDQ};u;^bvU*sTbAyIPvKwV7!N z*P=tQ%d>*k`l7pOp~Cd2UKmVV%o=CrOd6WWcD>a3n|zX%F$SlnE}<50e&r)ea&ahT zuFlf^p!nu#9{HBWBrFc|Co`>&M;|-HS@#m=dgOWczpqrcoU*n0CCgGl-tai3^J+^I74FWh{h$Q~JTeP`MBZFmQp-`(0YS!#BMF6q0YZSEE8+nN?;8Yqn2ERt8U z#4{6Sqg4a2ID_;I@9EREy57jj@I8Afo33@;7WiKe?k_$15q^kax}7gIp`BB;!a0Jy zWpm3Pb^!04VAUmdYt5NHXyre?*Lv`dz6{yV{xOeIitQnN5)(j#d(wruL(|Sk{DdZHXoa0nHXts>P%6{1o=6ns^)u99XV#kd*LpBQM`Tc(S>2^ zPE&h={OWh=bd;=q+DCVKTH59N_2cyAzumV*KNUxF<7tc?1w^m>aQ$5=W&|H}1DS9+ z?iF!0G4v3MN1PPbqOVwyaj|2#lwpXYqh!3t^wtiQw`pnNZ?x_0Fx8egXTm_=U(}() zTFg;uH6BZemC9V}!L!3t7x1y`5h!E;+w59F1VUyku>?u*YUDj~K)JMt&d6Wuoi61M z1f(oCXyWO=ir>C-v7nb4sDpFVD1moUw7({skQ+d{8Q3Z;dsE-uW(1hs< z0`sXw6fIBLk{ctrlWdS`6NU<<3c6!`1F7W<5}y}i$QiFm#^p9?z>lENM6IMZ(P|pT zdymKb!#`^PE$LnlzO`|Frcl~EAq%6RQS4{**Uc?qwmNG{*Z*Z-OgXu!YRNA0^H^9& zAXOJ-fS5I@uHD8)Coaz@YWJT#i}dF*pLgsLF{1x#>1r7<%R6Y5>=%j3!|V++cn>7> z#7$Z)&yVz7uW#KQxwgbDBz{BNK?2jB zlHRuJoj*{Hx1fAYKc2vs3+{|KkS^%aN&dsqxsOsm@X}4}*alq8!(byoSZTO(uLObz_ZUb_yyvc@2_lk0~Cqdav7_Ay!V8hz`3Fw~U3deAGut z1+O|BCC$|PXcTG&e@v5rYu@h^QvAC%rW$X@+b4_{@oP|1ZUJRa9`rxrw)}=#r=Sbu z$j!9STxrao@DBt<&4gAdC6C>&TgkP!`}z49*el-DtunrRxRH$0r3q|*HRAi0x-8W; zlxr+wV^!#}iAw8Od54w{){(px;%7gT1TVLhcinI95{gU0Zp)nyHKA;9eiJLS*d)>r9=&wq!;;y!#0A z#ejtxk}Po#1iA|JX-^uO3CnB+tQ>&xxWE z*V|v|np-Hn2z8Qabnl(@$$ry{(YN|i)4@_%LJFCU3pMMXRIE+i2CQoySuZiVY62|_ q7OA3A1Ke<<_$IZz>c3?DVw^~?c!$3k7i|RnIWTKH5yvt2x%xkGfgLgc literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/media-optical-24.png b/pcsx2-qt/resources/icons/media-optical-24.png new file mode 100644 index 0000000000000000000000000000000000000000..db43e1f1efcd49ac9302bfbf9e11341104ad86b5 GIT binary patch literal 1124 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 literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/media-optical-gear-24.png b/pcsx2-qt/resources/icons/media-optical-gear-24.png new file mode 100644 index 0000000000000000000000000000000000000000..d71590d63724d1ca0e932ce848e8b58d00120450 GIT binary patch literal 1344 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 D8ayJf literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/media-optical.png b/pcsx2-qt/resources/icons/media-optical.png new file mode 100644 index 0000000000000000000000000000000000000000..bc001cc21aa6e47b5c1ff01d9e2bca5f026cb70f GIT binary patch literal 1731 zcmX9(_;chTs8SJF4z8+pLe)g=a ztPHkMSXfksLgnS<<>uyAS67FIhK7ZO9XWC&Dk>^AHul5vvREiK7-z@F%_Ac^8KhAv z6%q+35D4fr+PlSf3v=_0o8zV2!bH7nAb-po6Ym{^Rrsr z?CkW6(>XQen4FrNn6TJQx^a_nOsgIpH5e3-L9fyEi?te!Mx|2qi-tf@2}vasK*-~A z7&JPGK&8|BC=?=vLBMq4JJ8s+)^;|dX>opee(wGB)Z*miqRqBwx6i}9Cmar?%5KtI z<+AbdF|$!?((CoQkzo*0s?~!$(IA&Qs8kFvgbIaRE`j6{v5ZZZQP_}7Ar*2#fXOG3 z_zVtLAmmWl92S*BC9~NqmPp8?Fd1wX9p6K3ZKLve6o3H`Fl3>C_?AHIqxEoEST-9? zqoeWIwxy-_(=!t`v)yj984NbHZrpA+X$OsbzF}CaA5!YYBK??2H>lDLtF+2NjSN%^ zMT1PAiz1V^Qic2KSsb5$Dk z5WpRf3gtwSTquzOlzu7?98!pTh$1RSz+(#tI4*_C;_;YF8UxeK;4;W<=w3dDKmxik z9j(3iW`NYti?2)i>6i_^VKEQRCtg6fuPtp}*}i^tj^*aMX8nB2+SG(1^}?A00eg3O zt-+YnkRtvPvU6>1S;K0ot^Mgr;MUD+|DCa!OhXiaboJZtgMpRDCwmbHmlJU@QOTi_ zjMoP|JuRjiw=%5eEncksSvh-%NZKN~Vr*1dES7gf|9~JteM+s{ z>Ba=)iG3rtZ;f}*)(lS6>(;)|sI22RAN!?M0E~?h(#0R7C*}1D?`<{48UKCxA!&E8 zB{@pIX~TvYG-c0Ml~p?Lni}K3K=160r#{r`H$FXkrgWpN@s)eB;uqa+Peig`yW7iJ zl{;!SEG>15W#uvC414=4niNp9ZQuU8abIuyBCjN-4@x!e>P=+4_|s-GgNv-@JG-+wCJX% z(42qWxg$rzmm&;PRVq>DC+Ar&z3d8$*VvP$?!rq^RdL^7zpHjG?v{m9!*vS}PPs21 zzo!{i49)!5)Q!4y8I=%!*(?Hr!-2*;Qd{n9!L|_LpQrE1+#?xDW*2m5WiSNfDu7FR z;QH_$zkc5hh{Kqyz7d9gVb(FYwN7)24c1V90Pg5l9!}Q=PiAfzW{Gre zVpvN^szScKxj?ATj7U6r7&v*)b$jUU=F|+6I!E`&Hdg+*!$Hr14qYXEc;Z;SgIkIA z^6>L>n6__E^O^e7s_)S>tkZ@YC11o)2sc&K+f9{~H+vTD&n@7P5C5HYCiu#e$5LtJ z%a>gjYoT9<$VofB>u&{R{&shw#!IcK7A`u@`(An~DCZop&diZ(QAD*;Dq4ygUFNjJ(+2*SY@eH%=7v z$`F)t%D?dI@W4k!$u++~ogx3o7<(>y+kQ6eN)Qg)nVaeU$4xGsp?Pagj|&IA;{JK# zua)bvU$6T#WtXaD=v{WgQo(^Y@nHhV^{&qS;$W@_aq+rGa+B4U;}`jZ`b`4$?~r=j z6YnPZ@hIz>A2&FlDZ_W~`u_Upy{-qg`0AbQbrlT_@XNko{f?c$XZI2LGY8AFuj9gh P{~O{?C&pl-(+mF(k;>{O literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/media-optical@2x.png b/pcsx2-qt/resources/icons/media-optical@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..1c7a8f2fd7fb08ac522b8c4d1720eb9a24ac4e12 GIT binary patch literal 3250 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 k&%dz&nqBY-wElt<807*qoM6N<$f@-0(&j0`b literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/star-0.png b/pcsx2-qt/resources/icons/star-0.png new file mode 100644 index 0000000000000000000000000000000000000000..9e45aab1ed5eda7d699e99f761a697b38e302899 GIT binary patch literal 439 zcmeAS@N?(olHy`uVBq!ia0vp^2|z5t!3-pg%sG|;sR;o-A+A6g2t-ANIXT%OY#_meE;_8hJw3 zZPjI}>%=lzPj$Xr&bR2WC+BIFMJGHscc1V0+&jgfmgU8&Rm>8lL5G@l&Anm~cK@xh ug6f(2nHS5Ii$8gCUgoK3bMvZ>{>7jZ6?sADmNqCR7(8A5T-G@yGywqIjhdza literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/star-1.png b/pcsx2-qt/resources/icons/star-1.png new file mode 100644 index 0000000000000000000000000000000000000000..fefbdd7edfd1de2bcf76e5f13549499d15d103a2 GIT binary patch literal 743 zcmeAS@N?(olHy`uVBq!ia0vp^2|z5t!3-pg%sG}ZFfeii_=LCuxey>KD$K{r17UM< zaX=VMOi)=MrJx`uE-uQ%#KgnR#mvma$jB%r0x}H`u(h?3larQ}mEz=N=iz4LU}r>D zTvIIK>cEwqETkyM%FGNj4C&ZDt5z*r zwrtU&Mf2y+@9gZXsi~=|s>;a7NKQ`v@>uiAl`E%DpFVZ!)X}3yj~qF&e*OBjYuEPn z_V)DjWM^k*Wn~@Ns<3;V{DfAC(p=%ESJhANQCc!Xdj1sYH@7uj-_j5aescn7y>Us9 zUoZnx>(>uF60eT2?lKe+`2GFU+s6;ib=G=29H|OPk5Ksc?c0lkTj$QK&-(Y}@55Eg ztpEM{#3}Q$2^gf&o-U3d9>?FFKc08UL7*Wq=AcW8|bCt&TCapsqbM~H5ZZs;A(#Ra-8QF9LcSgI5q{{DT=o3#63SFEBt zVp4^lFMr0cllRQGNTm|ZOOpgRQo_Z)rL5ib@P<2kWmQH0)Qpp{3NMV`WG>@oocLbJ zKQ?ZOy1C=GoEayZydPHNn_ug0f0;Qu<;IK@=}Xg;4%+p6Z5FIj@-+2X<=N6AJi}V> z1!t1hjF7~Hbm?ceglrv`TB@D(ZZQg;F+1%A=cQ{B`m3(89?R|(uy%QI(R^lRv|%VE>3i#Ai6|EM6#2GfcB!9dGqGYKd-beUc49^8*680_xX|L z<;$0k9zD8h)v9I7mMvPeX#V{9y}i9vRaIG8Ss57_$;rvzo@l0tYUDqDw(8k7I%-npyGqyG2pU5N$HZtr$l_<+4?FN^(@SL>qX7p!>gHuK5UeeECY zYnQ|?AB|HC5zoIJ6Q11CPF!NEnn%Vz_Kc^VVo{n|CQmSW` ztMR#L!ji7kYj#$@8MfYkdCkXr!*iDGJBtgwXH0HVDV|eM%v3w~4oA_1C~fxW1J|z| z?8@bvbdyV{%E2$rAzs{brrWm7kLSO$FjMcW_neZ^GAUhu?`ggCRI9CXV)J*t-Xbv7 z)xI;;YN5$ru1D)idR>E(`*prcUD8i|EO&IX$)U{20_H0>u8dK>93>fC)^j0ZnwyFp zTcP1R->B(6yPuqO?H9{**|cBb-Q~34k28GwyJr~5rDnKz-FK>(7H!x3!R)NsW6_4U ze(BN;;>^LPlv~}F9Qv8*qCfS^iI|6ckgvtSor0~madge8gV+u1Vb}iR=G`@ zdH=nta;VwN+T|w~&hoo|#N?{@b;~PlH|8%A^b9=d8G0jSE9X|-8i7wOTui-gtuLi} Xx34>LlK-|oD5ZM3`njxgN@xNAxT#@P literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/star-3.png b/pcsx2-qt/resources/icons/star-3.png new file mode 100644 index 0000000000000000000000000000000000000000..3b924f3403136732b199ba59aaca79d34cbfe816 GIT binary patch literal 912 zcmeAS@N?(olHy`uVBq!ia0vp^2|z5t!3-pg%sG}ZFff`1_=LCuxyXQ#5z6D{=7z9q zibZ(17}?nosu}+FVV!l1me0o*=^d6;S+Z7LPR@l2dkG9*S9nlOp%^9S$bl-#Kbm@Cjg4 zobhyV4DmSr_DXp1CI=C=2du7QIRefqvAtilXC0X_FV@v$@9{79{{8pou$mV+Mf%$7 zJ>~}I&se6PJM(VQ{U7}reICi7{7WyNnEmqQMUT_Zw<>a}O?LE8ee}@KQ0S@E+&cnm zORN;nH{7hMoz|bgc=}YfGk3(a^wtByisu^zJ$5ep5+%7HWO`7B!m*gS6Mn@^o4#eO zX?IYzZ;CZb@<~0_KkaixYX7#p=6VH%uU;gA@M5fXtjKJ3MwS`m*$H-O?{!joD{9_PWpfJNrYC&a~`1@0Rb(`1tqC zv6(#E&fgRJEmFE7!dpbPYm>O`9+P{~`a*J#l`bFC-uqnmw%VC3r)S@NpLX+_po+?& q`4W$m_Hu=^<^@SD1HJ&t;ucLK6TKCT1@H literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/star-4.png b/pcsx2-qt/resources/icons/star-4.png new file mode 100644 index 0000000000000000000000000000000000000000..f02a46a60f5f23acdd0d12e85703e9bde6f1cc01 GIT binary patch literal 733 zcmeAS@N?(olHy`uVBq!ia0vp^2|z5t!3-pg%sG}ZFfeii_=LCuxey>K%*@CLWpi^e zx;k)iaKQKs4BXt@Kslg-nqrZPe36_KAsH!FE>463QBhG=R*-gic{!kLcCru?6O*$& zmy$fIq8zKRAhVDlGcPX_)B+?5s0@h*W&U}kee>qc&yO@u?@>CoUGd;%g}v()cCL|M zGDCV|yF_1$L`k;rrAwFAtXb3D-CbQ>9UB|_?TOacCz@|=YrMXt@${)tq{1wx^(I9NB7irXh(nFRaXDsT=SfX zN4xa#f|upRM)&_ro_uB|^S2!S({9T}SX(-eE(%`7pN6@ETyu=l`K2jo;i{bG zyS_cv+i?6)Y-W{iEUSX;9(TrfZJ9usYq`Sg25)y=ZxGydFeW^qEL>?z0Rwl}fo9Dw zW*g%g7X}K=Nt`eFN1Av3o5>OOCoiuQn=|?M^9`=g&di^1$IyTEDyhr`p^g4cPlT?l z+N;F7?{=NfoC$YamV|zqVlv~@?A#jH7cU=tkX$(3V(p`x9{ZT@!pYT}&rCh%Y!<0C zb#wn*@2IOA?oHbe)OfjkH6w@DvpqZ3xhfvMsP&iI?D|&D=~fC$B8rlNx2#$wP#Vo# bz3rj+-HxvBif?vb1|^Q!a~eE+>A_2 zAc33|A&7WQv1nz1h@u>;mBP|{V`cCdS_CTeZJY5_^JdVFTYcAGez~g#x<4GP% zziGaFzwh6l;l%F6wezv*rJZN@$Id}2 zUb9+JaeL!KuK>^M9l7ftv@Btpx46MMsix2=i22T|{u|MAri9o?mPrfw`Dm$6%`hrG zk$G9buq1=iSb#U`&??cwwH}k44ofy`glX(f`#Cp0ohE&|z zdy$p#hyf470nd&p78x?ReEZM6n>jO#qquE)zaD={RE%^0`>q{k8ifm-=dv%4ndfvr zU8m7AcRlCAC)}oIk`+uF18>JEJ5AQ#T3H?N_N>zS=~vss&kOziH`TaKmTeZ$A_h-a KKbLh*2~7a3p*WNP literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/16/book-open-line.png b/pcsx2-qt/resources/icons/white/16/book-open-line.png new file mode 100644 index 0000000000000000000000000000000000000000..577c7bf5f244885256ea9f9f5672283bcbed6a69 GIT binary patch literal 148 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf67>k44ofy`glX(f`_<6cGhE&{2 zUcmNa{{wrTJ^$nX?`QNlbbzU&iTS_&pK^x7OdARqTi8Wx8A2Hn6E?ZeY5M&C?ZOW> vM$*d?4%NpbFxl8m=shZ5$o8K%z?H%3=Z*{CZFl7ZO=j?P^>bP0l+XkKKIAks literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/16/brush-line.png b/pcsx2-qt/resources/icons/white/16/brush-line.png new file mode 100644 index 0000000000000000000000000000000000000000..aa9ae8ed8d426b0cafd89c62bfbbb3225cbdc3c8 GIT binary patch literal 219 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf67>k44ofy`glX(f`O!ahe45_%a z_JU-?5eI?RhZaXy&V95+PkOmU@986{K$f-N+etfoc9-m~sgB+H-SqswU9U^)r`(JC zUs<&w>F<&wOHNIC>)Sb3RP=cDzDrA{e+k?y@mAz&!l56N=9xNctXrkpr~k~sb8@oP z!$sVa&I-DJi5JZ&^6{P#XS2KR$c=ak44ofy`glX(f`*m$})hE&{2 zUZDI#zv0y33IF~-tY?}t{lkA-Cr+mi_AZ=Ft)ZMwQtd)BOcGgJ)+#*kV7kZhiG_nz ck44ofy`glX(f`#CWk44ofy`glX(f`Ec0}645_#^ z^&)%XVFLk%15O4l4hAfa2F+qkl3N709!jz}9XgN{V7Ngd=3BXb@BQEJ{}txg+=_XS z{xqRGFXX_r4{>{6EHjQZ$P`XS!Ju;_b-(=sq}fqiAK(BBTLDT)7FN4KPle*t8f})Sy`Mw j+xlsy+==H(>MhxYl-I?t{P?^H=wb#>S3j3^P6k44ofy`glX(f`#Cp0ohE&{2 zRuKGC{zLx%e!1X?2lapdfBk>{e>-Effr!H;c9A{v; LS3j3^P6k44ofy`glX(f`_<6cGhE&`- zdZv-}fPsM9!=5R;c}GFS)SM4ql@HcAywH~VX)n1wdGmi}pC*Nq-|ejnHXL4a-fPa@ t?+S%_4v+3ItybuK=6&$dYE!*03=>YYk44ofy`glX(f`%<*(_45_#^ z^+I?ei=qJA0}l&M4~J8p5$xSlEEv?gn~!o@{J5mP_w3~_`|tcwPT9Tc*3CbBSs8^laCD{Uy_vMa-@Gv($$9$khWSH{@2o`*yd< zce}y6?g);K485PWN2({qR-Q9kG9mMnzEX2ub@mm5`;w-;{c&am77oIXIXo|#sBZW# aRQ~hL`hZy*E<^y`$>8bg=d#Wzp$P!40$(8j literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/16/file-add-line.png b/pcsx2-qt/resources/icons/white/16/file-add-line.png new file mode 100644 index 0000000000000000000000000000000000000000..bc3576b858bbe00abceb3924182be1d723d359bd GIT binary patch literal 174 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf67>k44ofy`glX(f`WO%wbhE&{| z+Q-Rw$bpANnbA|7Q*&Yivx*8|lW>8-pWN;B2LgQQA#%DM^|GDKVJz)7R-l)%c^08~zhS!{bpG;a{sVCAHk44ofy`glX(f`gn7C+hE&|T z+Uw5PV8G+5%*yGp!g)dfr-g^CiQj>P$?t1(?&;Q_>{oU#7jfUo)?<)ZFva%4wEpI1 zoud2k44ofy`glX(f`M0>h8hE&{| zJJFr-fB^@CGAH}d#EAmx2OS^1*!BJ0-RZs+tP@L;_Odt}EEkJl&$!Vj$0V>S(2~DE z?T^5Rb*&TK-fA6iKhJ#akF=1Z`&0MsnjbE4>@I&?xbrg@>iEvRxFb9(8E6fIr>mdK II;Vst08ocFX8-^I literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/16/file-reduce-line.png b/pcsx2-qt/resources/icons/white/16/file-reduce-line.png new file mode 100644 index 0000000000000000000000000000000000000000..0e5b2236612d33140522107653b75f77131a51b8 GIT binary patch literal 157 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf67>k44ofy`glX(f`gnPO;hE&`- zdft<1V_6@HkJ(!*%>t% zE*NcyR$ijuqR4UYk(a|cE`=NN4j%)5u?kN*Idk6HFXC&b$-jT%3ABR2)78&qol`;+ E0OmC`djJ3c literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/16/file-search-line.png b/pcsx2-qt/resources/icons/white/16/file-search-line.png new file mode 100644 index 0000000000000000000000000000000000000000..be4bfe33cdd746ed0126a597e39b73e17ca6ca00 GIT binary patch literal 199 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf67>k44ofy`glX(f`G>28zBwWL;c>hb!zk44ofy`glX(f`q$O=$V>bJw z80K4y0+nkts>~d&Is^;9b?jfX;6KMIhC~Y%NyQ>N)|veaB+YZZ1AN(Kcd7qm)H{9R Uy%m?(b)c0Dp00i_>zopr08U&x6#xJL literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/16/flask-line.png b/pcsx2-qt/resources/icons/white/16/flask-line.png new file mode 100644 index 0000000000000000000000000000000000000000..faeedd8a6fda7e7809c1050cd1125407a5e0106c GIT binary patch literal 199 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf67>k44ofy`glX(f`GSV4qMnUQm15a+~(7B2@U6$M82O1>2=GE4aMs{JdDKdZCb)UjxZl;(PtA6qYQ zJ!h?n%9#GLDSxMs2191!qnH8@d+v8{tmaS3P_XBkX0V?5o6h>@cR%v-B<_9QADJ&| vnkK#?@bowCidCX=7hcOpq_jJy=C5KDx!-N8vSgny&@l|2u6{1-oD!Mk44ofy`glX(f`#Cp0ohE&{2 zUcmNa{{wp_m4x7e2mf3D^Cd(uglv-MWIG@t)1d9g@G0Q~bA-%7hRZ1-tOaJ3TnkKi zWDcx5!0@7k44ofy`glX(f`6nnZjhE&{I zd&-;fkb!{f#fc&8E=)pNi7F2mR6?9&Q)B`-CmQhS)qje8w(qI6k+NCQYYqwahqkPb z>aX*>=?{Oio0(0)J>$Rz9t&ai+r~Ev*36u)P&iRd=5HfQ%d@zKr|b-wPK6VB-p!fi h`b~a^pk44ofy`glX(f`1bezThE&{2 zUcmNa{{wp_m4x7e2mf3D^Cd(uglv-MWIG@t)1d9g@G0Q~bA-%7hRZ3dv>GlM6fo>& zYW~j`kl|2kKIeaaKkK}9%^4r$#b-G5u3%wUU9s%ju1LK(K;s!aUHx3vIVCg!0Ead+ AcmMzZ literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/16/folder-settings-line.png b/pcsx2-qt/resources/icons/white/16/folder-settings-line.png new file mode 100644 index 0000000000000000000000000000000000000000..9f700309c4ac7c212689c97c8c77e296670cbc8e GIT binary patch literal 193 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf67>k44ofy`glX(f`RC~HOhE&{I zdtR2I*+Js?!#U5U1nXHZpHnpJ^s^GZ)0)%gdhg8f+iCOrU){$4{_}V3&gy)*_qywX z8B3z2W$d!%FW6JCe9x?@^%pYi{{7*-GU@SBH`!)Jb>TJ%l{I=3lP)CZciXYeoqJO! sTe)Dij{ACvwav%WPjpOQ&05B`pylK&r%9{#0G+|$>FVdQ&MBb@0Lv>*R{#J2 literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/16/fullscreen-line.png b/pcsx2-qt/resources/icons/white/16/fullscreen-line.png new file mode 100644 index 0000000000000000000000000000000000000000..90d6936212bbb37bdfbf9a9d1d9db8d3b455a6eb GIT binary patch literal 137 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf67>k44ofy`glX(f`ID5J{hE&{2 zUcmOF{((J<#Q|5D1xoA*TzY@X8Jrk*FfkhZXL35o_(n~5X~F}>r~4WV&;8%d?zE(V j>0{#}b`Ew^Q3eK`${6#B%bxH7jbre1^>bP0l+XkK=xZr` literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/16/function-line.png b/pcsx2-qt/resources/icons/white/16/function-line.png new file mode 100644 index 0000000000000000000000000000000000000000..4d3269d1a452c7e6cd09cc67b3692028eda411bb GIT binary patch literal 143 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf67>k44ofy`glX(f`czU`xhE&{2 zUcmRGpMlSTgZ%^-*McC9O$?!p%pGl-44Y*=0v&Gf@)c+>v=qo~Vyfm~(caMR$P^ZE oTl}Yjbt2b+H#JIoJuMj+u8N*FstL=;2O7%Y>FVdQ&MBb@02mf1fB*mh literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/16/gamepad-line.png b/pcsx2-qt/resources/icons/white/16/gamepad-line.png new file mode 100644 index 0000000000000000000000000000000000000000..57dd85521077496487a5011bcc0f9a7a13dc7388 GIT binary patch literal 195 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf67>k44ofy`glX(f`)Oxx&hE&{I zJ29NG*@4Hw-)YGa#^(!!q?R9be6+xVfxUn4!}np|{Ip5~R-L}O_)?|SJ_$h+iJ~qI z30}M3&9=)-9fT#*^0QtEiz)cCb?;dE)cjZs_a3Q|lG*vC#y6aodv05PL2$3Q<@T2~ sdR`At&yQdiwV7+-ZydjFT2wNB%azTq8zN6e0Ug5N>FVdQ&MBb@06S?%B>(^b literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/16/hard-drive-2-line.png b/pcsx2-qt/resources/icons/white/16/hard-drive-2-line.png new file mode 100644 index 0000000000000000000000000000000000000000..a81f471a88fb1b4c10d4d78e8ce02f67a66bad03 GIT binary patch literal 137 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf67>k44ofy`glX(f`ID5J{hE&{2 zR^a**|DoPq>_&gUhyVXu|FZ`u20XFf#Kg`MEZerA>GO+)3?e`M>ns?S^8T|H2yj>= jEynOQ;Si_GQ4*a3)z4*}Q$iB}$ww{_ literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/16/keyboard-line.png b/pcsx2-qt/resources/icons/white/16/keyboard-line.png new file mode 100644 index 0000000000000000000000000000000000000000..f71d6c1000c88d82281955f7f5e648382860d7a8 GIT binary patch literal 139 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf67>k44ofy`glX(f`xO%!chE&|T z+UL&LU?9K}&J^T#$g5{@_vVG=_Z-sd!o&3)RaLn>dDK!@u4V91HDG%c6n!v4$>8pS k&aKy?+SK`(&%ctb`Y+qsS5W-v7SKQjPgg&ebxsLQ089`pc>n+a literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/16/layout-grid-line.png b/pcsx2-qt/resources/icons/white/16/layout-grid-line.png new file mode 100644 index 0000000000000000000000000000000000000000..4185e61ad510bb06feb8b7454308d9d9a2495f8c GIT binary patch literal 145 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf67>k44ofy`glX(f`cze1yhE&{2 zUcmNa{{wry4GfDO9Qe=uUv|L>3T+4YPvq>f s`t+y1TEnLQOq=G62ICd#0m@p86{Qz;PR@L}252mUr>mdKI;Vst07n-!>Hq)$ literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/16/list-check.png b/pcsx2-qt/resources/icons/white/16/list-check.png new file mode 100644 index 0000000000000000000000000000000000000000..0a3e5900192e02c670893486e27fc4043528cc09 GIT binary patch literal 139 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf67>k44ofy`glX(f`xO%!chE&|z zIw6#i!BFI|RwiFq=ktu>X~}6`b@E~N9n?Ho9^Kik44ofy`glX(f`Xn49fhE&{2 zUZDJ={=t66Ik_L|?UyzE|1bL>r~Utb_a#hc8mt_STQD(9m}lRy=avZ@P!EHrtDnm{ Hr-UW|ucsur literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/16/play-line.png b/pcsx2-qt/resources/icons/white/16/play-line.png new file mode 100644 index 0000000000000000000000000000000000000000..4bd91ac109759a46c23591958bdfe2b5bfd8ac50 GIT binary patch literal 149 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf67>k44ofy`glX(f`_{z3i!dd5Fo|Nig)f1h!F6SsmZN3nsZ48tVW|MmyY9t>evCaUo4LGT9o6$wmv w+V}p~H=OSJEU`(b+;_XZu;(jqROxqU35cBvf571}^Pgg&ebxsLQ0MCy#vH$=8 literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/16/refresh-line.png b/pcsx2-qt/resources/icons/white/16/refresh-line.png new file mode 100644 index 0000000000000000000000000000000000000000..5c8b0672cd3be7dce1b4e918dc34b6408e1521f4 GIT binary patch literal 218 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf67>k44ofy`glX(f`O!0Ja45_#^ z^rdS)J}q~g5NJ~wGNC0n$u&}F>)E#zY%aSyZs*%2 R%mKQO!PC{xWt~$(69BM)TIv7* literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/16/restart-line.png b/pcsx2-qt/resources/icons/white/16/restart-line.png new file mode 100644 index 0000000000000000000000000000000000000000..3bbce0fb2e69bde59224ebf9972e13d5c42472f3 GIT binary patch literal 217 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf67>k44ofy`glX(f`O!jnf45_#^ z^+I~$5d|664;~IoLP`sT&N&tqcpPN3;N%NpKgwA6;q|@G_qPA9N#4m2mL6KVtxlFr z)cHDZ$iv4K=1Zk44ofy`glX(f`1bezThE&{2 zUcmR`|3iDV8U0HbPUtc?vFu=Y=IaplfT4w>fI)*Tq$$3Uc@|Sh_{7BwcZF|=h%k6^ z=p0ygfHBcVlIzpK8**Hq6_r*v_Hx<k44ofy`glX(f`w0OEWhE&{I zd*0LWh=V}u!$}u}CS2fKrgDTcC{5)7gUk|DAfdvy&ob3N@8|1~-_?cu9-b}B)_&q# zsbS<48ve6Np-e+=w)pSHtKFtn25wfBwB<>j@ckQ#e-seEMbix!}8* z5x>nIZI$;pYVyJ8l^&Z-*vFmqfo~-ri`{$ka*_64qi_eHdl)=j{an^LB{Ts5m3dGa literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/16/sd-card-line.png b/pcsx2-qt/resources/icons/white/16/sd-card-line.png new file mode 100644 index 0000000000000000000000000000000000000000..f3795efb54323fa3344dfd7b459e5ee5ed772855 GIT binary patch literal 150 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf67>k44ofy`glX(f`1bDhQhE&{2 zR$%;N{$sy9=eqyv8}$DE```MX{lSO-_3R}tSrxURou$ea~cN;J;Y`VO}KV;t@Ad7*))78&qol`;+0H|#Px#s7XXYRCwBz(FdW!AP@l1N{9p`;G{^1gg9s=L_!@T4pIlf0XP5$@9#a!xV`Ui z(=l2s#-G%KqNk literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/16/shut-down-line.png b/pcsx2-qt/resources/icons/white/16/shut-down-line.png new file mode 100644 index 0000000000000000000000000000000000000000..4c12ac0705c65c30d73284b4a0b6580369b1d41f GIT binary patch literal 213 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf67>k44ofy`glX(f`^n1EEhE&{| zI)R<>u!4ZAHY4Z6hAAEnOhE+~iYHALV4ups7x(RbTtW5P%k%r4D&KM(nQFc7(+`P+ z-G`5!|Nibo(^VT}TAR)o9!FoI2G{c@X_xA7M)GclM%^01?2y_~Q Mr>mdKI;Vst06S|^)c^nh literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/16/tv-2-line.png b/pcsx2-qt/resources/icons/white/16/tv-2-line.png new file mode 100644 index 0000000000000000000000000000000000000000..ea2329e85d3ad400c64bb55d93f8b5e5cadc34a3 GIT binary patch literal 137 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf67>k44ofy`glX(f`ID5J{hE&{2 zUcmOl{-M2|0pp?v2mW*amo-of_`=|Gka3NgyhI+?z5n<7S@*STaxiEe%5^35)2H?9UEV+zVt-~XdHv5tDnm{r-UW|U5+dq literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/16/volume-up-line.png b/pcsx2-qt/resources/icons/white/16/volume-up-line.png new file mode 100644 index 0000000000000000000000000000000000000000..fd8f211cd933ebd4caae6e8d428281c2f39cf4c8 GIT binary patch literal 222 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf67>k44ofy`glX(f`%^Ae9zFk<1=G9iCoRo95kcaPGMn`&DU7SHAwb%GtTowZ3q{ W+{yzN<7Wfi$l&Sf=d#Wzp$Pyfidq{0 literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/16/window-2-line.png b/pcsx2-qt/resources/icons/white/16/window-2-line.png new file mode 100644 index 0000000000000000000000000000000000000000..f8f21a1c731b2e3243d1fa134c9356014a824b28 GIT binary patch literal 142 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf67>k44ofy`glX(f`czC)vhE&{2 zUcmNa{{wry4GfDO9Qe=uU)De|;0uG#K}HkfMh2CxV8)33jGLHZ9x&|8OK9l29D4BQ o|8EfwDrYotTI{uKlnUfz;QqhA_o(H90HB!+p00i_>zopr0L8B`wEzGB literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/24/artboard-2-line.png b/pcsx2-qt/resources/icons/white/24/artboard-2-line.png new file mode 100644 index 0000000000000000000000000000000000000000..4aaeea1dc23d1772286d6095b0682e3ba3a5366f GIT binary patch literal 114 zcmeAS@N?(olHy`uVBq!ia0vp^5+KY7Bp6QcFoXgrrjj7PUgTe~DWM4f DD3Ug2 literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/24/brush-line.png b/pcsx2-qt/resources/icons/white/24/brush-line.png new file mode 100644 index 0000000000000000000000000000000000000000..720c95509d730b507f243d7d9a7f908011779c3c GIT binary patch literal 276 zcmV+v0qg#WP)Px#&PhZ;RCwCe(HWrQP!s^r>lCLr#VIZmm#I^n>e9GOT_!GlF0D)JlIL>%zTR&5 z|FO5;Eq(&6olV@!U;+Q)9@b!1?|7oI17n0v#T1#9{}wEcsQP$j#(eJ2uNK{VYE}FF z`X1G&dtR-au;}z)h_7sM(0uu#V6kAGyA5sHt)RqrMr_YF)=85OW(<+&Q6f{&-~B)X zn)@G2X)M=jvjTlY=7ct1XRnLI7A0p{07n~tJwMd?V~2w_GM!14&EFc4o{eTjMKKxT aH+TT*hasW)t4`nm0000`vPPC`o^~c0Z zh3j8^wcfWc;<)pRE`8;tkL|l&sj6C6?YsS)mCzopr0Laud AtpET3 literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/24/dashboard-line.png b/pcsx2-qt/resources/icons/white/24/dashboard-line.png new file mode 100644 index 0000000000000000000000000000000000000000..b7dff85a8d2938fea72483efae5ec77eba05b7fd GIT binary patch literal 126 zcmeAS@N?(olHy`uVBq!ia0vp^5+KY7Bp6QcFoXgrrjj7PUVx2P|v~ZYPX4cs6YOV{Fj2(*1-o1H+4@ WA`4qY^*#f2F?hQAxvXPx#+et)0RCwCG(OF%?Ko|sYBt!xd>Y$Yn2gCt5XdF-n#6hJH2fGdu2Y>GUF})r; zFW-I6!~J1!RKB(9=;?NHuK?c`Q1sW*Cr}-1T0sSGm(mYl{O9?eWCVxvWmpa2LyM7% zsnIf35sNqWb6NZQdHM}O@I(rK_Jxhwctxjj_Ss~IB ndKHNp%Ump)%wRS}@}2Mj8kkudA;ZJm00000NkvXXu0mjf&Gha257SwN6Z3&4GI+ZBxvXPx#)Ja4^RCwCG(i?okP!z}UIbEkZ&82Z!oa*k4OY71&&1o*VE{V(L@|~Se|9xS9 zcHYmA_v7GV0gH>T*UqCA$!dJ&eg@rS&@_Wd2dC%m=hSMYPmFL~+~JX}uw@oIzggXy zo1sU7PBhk|h8fe9!-NVb%^dj*B@G}01}r6lR^?ZUxwLp3@J+F48IUbq0r-rT`jeq6 zV0u3E0gDy^Ke5&o%$8X=X`yXSWc3VTakSSodq=BuPmDGsTQ&1E gXx2eDm-8F>2lYK)LP{{5k^lez07*qoM6N<$f?OYb-T(jq literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/24/file-add-line.png b/pcsx2-qt/resources/icons/white/24/file-add-line.png new file mode 100644 index 0000000000000000000000000000000000000000..8b09c15a59a02074ee66de6ac8d04941e4ebb5b2 GIT binary patch literal 140 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1#^NA%Cx&(BWL^R}Zk{fVAr-fh z7l{9;e`v2~AitZfCc%!6w}NMu|0=c?u|rYO5~;W)=kskUj$1rttW lDlizB$|$U;V`iSqz~DM5K>l0yW^JH}44$rjF6*2UngAQ>DOCUf literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/24/file-line.png b/pcsx2-qt/resources/icons/white/24/file-line.png new file mode 100644 index 0000000000000000000000000000000000000000..06c431fb50a8e148e22e8924b4f494a095732a23 GIT binary patch literal 137 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1#^NA%Cx&(BWL^R}&YmugAr-gY zcAGOY81NkCF%3ApV8W$)cdK7-Q9a}C@n^-7JmITzj|*JV6ue4B)6pSmV7bP0l+XkKkcTJX literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/24/file-search-line.png b/pcsx2-qt/resources/icons/white/24/file-search-line.png new file mode 100644 index 0000000000000000000000000000000000000000..62db1e9dc9c410c802274cef26e4ec0b78adf580 GIT binary patch literal 209 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1#^NA%Cx&(BWL^R}-JULvAr-fd zo)2HxY#`Ba(Zk`;)Fu{{2M@xSgbEx~LY!0;7V@oVzP$JN{k!iuC(Y^kvi8fmtFdeq z9S*xLhzc?7V$kqoxEq>~a^S&#jvY)}rkCyInzF#=2bV+l9?8=gN*8RnyaJXkVX9YG zs4yvIn3j3QV5*~R!{IA0m<#532{wGLV{JNj(B+Cn?Mln&B~6hB4A<=dI*Y;6)z4*} HQ$iB}Sxry4 literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/24/file-settings-line.png b/pcsx2-qt/resources/icons/white/24/file-settings-line.png new file mode 100644 index 0000000000000000000000000000000000000000..f2ffd9c4f9594780cce11442b1451ca0aeaff790 GIT binary patch literal 195 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1#^NA%Cx&(BWL^R}wVp1HAr-fd zUT_pVWFXLXacU4}smGipd{XQoOnwIrHhWt#Wc@&?HZI(Z4NbVYPNdN_Cf&^2gY9m;yMOjL&8gaIvBElzp@GZkXzXL4Ll`_={an^LB{Ts5;tWFy literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/24/flask-line.png b/pcsx2-qt/resources/icons/white/24/flask-line.png new file mode 100644 index 0000000000000000000000000000000000000000..9c0517a5aa32130f681263df35811c98870b67ab GIT binary patch literal 209 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1#^NA%Cx&(BWL^R}-JULvAr-f- zPGV;~tRUd3{7^+{DH!x$Vz(>83-B_WLh!wol&L#P~tDzgfs!%E0PIx5MEr ze~UInFX2~nu({dm5XvjmdKI;Vst0Ky_MlmGw# literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/24/folder-open-line.png b/pcsx2-qt/resources/icons/white/24/folder-open-line.png new file mode 100644 index 0000000000000000000000000000000000000000..ac957c67de35576dfb336ee469fdefcc052b895d GIT binary patch literal 193 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1#^NA%Cx&(BWL^R})t)YnAr-fh z6D4XM*mKDJ=;u23U!0*Usa1fBjU(GJ!-0cwYTSXOBXvByEn5;Y4zOpgWMn*fudNOv{KQOt&pz)jGswZE9@CAl8owue6e;jVGOC0pNQPaYE pq=Dth3_FK_pX?F;Bw834rfbfh_-B#ENuVvmIy{gQu&X%Q~loCIG)cF5&7Z%kq`yqBG@~&nQjc%Up9e zYY&5=CA%Zz1l=?S-3)ccs|*PSY!2)iRnZsRtW&0J@$a8=sk)KRAmUbo-o6sXfGgka qUK>XGrSG+pDtx|RR*>)in|v-$OD)bfXI=)ngTd3)&t;ucLK6UJv_+c$ literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/24/fullscreen-line.png b/pcsx2-qt/resources/icons/white/24/fullscreen-line.png new file mode 100644 index 0000000000000000000000000000000000000000..61bfff69356d8f5ea7f25971ef7bb01c1ba826c1 GIT binary patch literal 111 zcmeAS@N?(olHy`uVBq!ia0vp^5+KY7Bp6QcFoXgrrjj7PUy;>0qbG6qjqKbLh* G2~7Y|3m;hk literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/24/function-line.png b/pcsx2-qt/resources/icons/white/24/function-line.png new file mode 100644 index 0000000000000000000000000000000000000000..b44cbbbcf216ec6d6b3f566e51ad66ac29e64f77 GIT binary patch literal 109 zcmeAS@N?(olHy`uVBq!ia0vp^5+KY7Bp6QcFoXgrrjj7PUw3xPa#m@)J?re;m7F1^X!+=@*%KN|+eTdVLRne6W83 PP&0$4tDnm{r-UW|UC$r8 literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/24/keyboard-line.png b/pcsx2-qt/resources/icons/white/24/keyboard-line.png new file mode 100644 index 0000000000000000000000000000000000000000..bbac489cd832113d36b3509a55dea8ae97e60ca4 GIT binary patch literal 108 zcmeAS@N?(olHy`uVBq!ia0vp^5+KY7Bp6QcFoXgrrjj7PUgTe~DWM4fz?CFB literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/24/list-check.png b/pcsx2-qt/resources/icons/white/24/list-check.png new file mode 100644 index 0000000000000000000000000000000000000000..43fd6cb686be2e2953ac633bcef4bfad8077e96a GIT binary patch literal 110 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1#^NA%Cx&(BWL^R}8lEnWAr-fh zCAK`MKm7mK|F26j7?=&X)fWUfGjm+JaewhaE2%Y$4{|ZwRl21q)x2mKP!EHrtDnm{ Hr-UW|OJpL~ literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/24/pause-line.png b/pcsx2-qt/resources/icons/white/24/pause-line.png new file mode 100644 index 0000000000000000000000000000000000000000..82dd074d9656d8666873a9f4c96bfd1426999f8e GIT binary patch literal 101 zcmeAS@N?(olHy`uVBq!ia0vp^5+KY7Bp6QcFoXgrrjj7PUrbxLPYx`v1xhk_y85}Sb4q9e00=x94*&oF literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/24/play-line.png b/pcsx2-qt/resources/icons/white/24/play-line.png new file mode 100644 index 0000000000000000000000000000000000000000..960845739723e84478b0e7256b338e819e241152 GIT binary patch literal 173 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1#^NA%Cx&(BWL^R}>7Fi*Ar-go zUUFx2GGu5@R8d$MG@(I6~?VodKJRP&43y^@`O U-u5ZeUZ9l>p00i_>zopr04Ym7H2?qr literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/24/refresh-line.png b/pcsx2-qt/resources/icons/white/24/refresh-line.png new file mode 100644 index 0000000000000000000000000000000000000000..21dd6178ccbf4b9928ea3e674063dc4a330ae575 GIT binary patch literal 273 zcmV+s0q*{ZP)Px#%Sl8*RCwCG(;0xnU=Ri1I>jkYajMJKW#iI1)unOSIIT z&*nbg?*7oaxLPoJ@{l_>MiH~amiqxrTFtv$J1O9DJD{>UN3D|;T~zN?Nuw*FF(u@V z>6rg+hDDWx41a2P27R{dKXwIBr%jDNBEU7Nfgq&)`x`J}pR5%qKdK&WQlk$`(aQU; zts$innhJ1M;^VPsHUld0XLVQ{|K~Pu{D2P3j_S|tJQ^WmL=iEFjm99o39CV;-Q688 Xv6c=ljI*Is00000NkvXXu0mjf1p0Lu literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/24/restart-line.png b/pcsx2-qt/resources/icons/white/24/restart-line.png new file mode 100644 index 0000000000000000000000000000000000000000..b48dcb93b6d1a89cfae067560b405eb6e3bdc68f GIT binary patch literal 265 zcmV+k0rvihP)Px#!%0LzRCwCG(pO=FP!xdSD6bU#865^mZ2vTqW4!91AgRgtA&SU!ZHQ6qoK7qtRjj@)NWTHyxTZ8OYr+QG%lsxbvkIzWSI zxfx0dFfMB7a0>$(bnEy48N;6l=M(}<;SfDQ+|sfgD9fR{phm~SLyaj2IGa(2hg;&N zY+r+X7;sYUJ>RSAPVUJmdhfwi{>=I0Y9QF4O0lMwQ-n%q#H`n<)5CcKE#3|B-T@U? P00000NkvXXu0mjfJk)Eq literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/24/save-3-line.png b/pcsx2-qt/resources/icons/white/24/save-3-line.png new file mode 100644 index 0000000000000000000000000000000000000000..818fb6e4f945cf73fa5a6833b0a48bd15983e7b1 GIT binary patch literal 138 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1#^NA%Cx&(BWL^R}E}kxqAr-go zo_1wC5WvH_LA`X+vjt(Va(d>^EaIQF^+S?V(FR4=0QZEM)9${JdG|=Lp~NxFQb#J2 knMIkM+ww!w;f$+XsYbKimd>4;4m6L!)78&qol`;+0L5V~c>n+a literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/24/screenshot-2-line.png b/pcsx2-qt/resources/icons/white/24/screenshot-2-line.png new file mode 100644 index 0000000000000000000000000000000000000000..5e1a40b56eb54c1a82fd25f32b3e7a05dabea36f GIT binary patch literal 180 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1#^NA%Cx&(BWL^R}d7dtgAr-gI zPGn_tRupjPbYM}6eSDEYs)y~ugWUUfzdkHl7sq|zQkBC4F@|aP`Z;(UR|st7ndRZ? za(>2aBOyAby8p c*xsq~lRis09c0e?2(+2O)78&qol`;+0N-UlfdBvi literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/24/sd-card-line.png b/pcsx2-qt/resources/icons/white/24/sd-card-line.png new file mode 100644 index 0000000000000000000000000000000000000000..bb409dce2f413d10e4875e1ffe974e4adeb449b2 GIT binary patch literal 135 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1#^NA%Cx&(BWL^R}j-D=#Ar-fh z7uf%p|3O~*hQEWQ+JWUZjLtF~99ry#6FqHCIf@8MC7klS#IVHUdIP7MX;0PxZUa-9 g1FQcrFyCchNa$4Qx9e;!1scZS>FVdQ&MBb@04;kbQ2+n{ literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/24/settings-3-line.png b/pcsx2-qt/resources/icons/white/24/settings-3-line.png new file mode 100644 index 0000000000000000000000000000000000000000..ceb6b06f4d4eb820fbeea3bd31c902eccac27e75 GIT binary patch literal 285 zcmV+&0pk9NP)Px#*GWV{RCwCO(+72gKoADtNLUFc1qX)&B;eq10uF+MLqZ&YgIxz-`o1vf{eNwG zpNs#1&I(rD#~c;_%g5qVBuT;RISkHD&Y-`O^x*PNI!5yrK+!$PRsghnhYgcKEA8Y- zYcOGr@vq+MEcBxVO^rIr7^SL@Md{TcngL98(DeJ12jmAD8cmQKq64CXQacbGdd%;Y z^m@~?f~hv$gr;@1-dA8l8wI12qqw-ElM&l%r;&H3k>hDpm@EdfczmPx#wn;=mRCwCO(iva_K@`UEIIUBh)+sJor*ZexCF|00ic8}(E*Y0N{fg~;z0W** z$N$}%TM7I=VD<1C;OzKXcm;C;W^kY33n;HvC}4RWKDnag3}_rB9b7Uzi+3qqHj|L5fu=_AzsMsKqzJH+W#k(G0000TFs7 literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/24/tv-2-line.png b/pcsx2-qt/resources/icons/white/24/tv-2-line.png new file mode 100644 index 0000000000000000000000000000000000000000..79ad7dfc9178de937c56aff341546931e03c243e GIT binary patch literal 117 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1#^NA%Cx&(BWL^R}`kpS1Ar-fh zC6+w+-}?VZ!)Fd&7bX$jmKet=ZGpUtr8-!asJ-VFNmsUMDB&$(VYn}7CY^h#{RvPj NgQu&X%Q~loCIH1KA;16t literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/24/volume-up-line.png b/pcsx2-qt/resources/icons/white/24/volume-up-line.png new file mode 100644 index 0000000000000000000000000000000000000000..668131369740672cdfb3a0423bdb1ce45917f28b GIT binary patch literal 271 zcmV+q0r38bP)Px#$w@>(RCwCe(^-LoFc5%YBt$|bAfXagLLF=+;G}i1NWcMc&^iDI-(D%<=DG8f zWB$3PcpGt<+=A1da?monx5Acs>A?($Xg5O9k$^c;E!8V55*Z$SQ=!x7R3PE z@s*6#q*BPzJExbIhbOKnSn3y$Bc8ovH6_{uAe|ri^$>7^d^!d`&`s zsMpH+nE|Cy5j~A(g^VtzK9gjh+k)SchtB_?`=(LEIbfxbP5#qXl{&`37{^ VH5K54qy+!~002ovPDHLkV1gKLZp8oq literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/24/window-2-line.png b/pcsx2-qt/resources/icons/white/24/window-2-line.png new file mode 100644 index 0000000000000000000000000000000000000000..79302a0eb7213ed2cbeff795cb24968cfbe0e116 GIT binary patch literal 122 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1#^NA%Cx&(BWL^R}CY~;iAr-fh zC6+w+-}wJX!(R?w7bX$jmYBwP=C->j84E6Wx->QNEe+)T(v=xxe4){C)_=wT;n>YL T>Wbrl`WZZ3{an^LB{Ts5?>8qT literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/32/artboard-2-line.png b/pcsx2-qt/resources/icons/white/32/artboard-2-line.png new file mode 100644 index 0000000000000000000000000000000000000000..2a36ec60c84db21b3b845bc8594f49f17b9dcac3 GIT binary patch literal 196 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UjKx9jP7LeL$-D$|>O5T>Ln>~) zoqUkD#ej!JGt=6`tHiy~{ldXJtTX4U+OkX tn0u4)29_i5)>|z3w>D_e$!$eH8LZ3yu#^{7L;_vH;OXk;vd$@?2>@rmOSJ$1 literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/32/book-open-line.png b/pcsx2-qt/resources/icons/white/32/book-open-line.png new file mode 100644 index 0000000000000000000000000000000000000000..70ae5362d07fcb51c6c0da3887c81ab23d0111e2 GIT binary patch literal 202 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UjKx9jP7LeL$-D$|T0C7GLn>~) zoe<90>>$AEf5^-6kf(zXpAswQLB`h%G6j5Ud-s0%cJ$ou+eYC*8<>+FW@fD1es|V1 z&eQ{bO=oPyUf)q(ec+{DkVU|f$J<$dzDiVPP*$7!rz?S%m!Wq`ynsh-S(8AE0*kG; zXZ`+lP3D1(5BGE@dsaT=Z`seSwX1a17yqz-vMcS6vVHyobPt24tDnm{r-UW|JC{pB literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/32/brush-line.png b/pcsx2-qt/resources/icons/white/32/brush-line.png new file mode 100644 index 0000000000000000000000000000000000000000..9748766144dc4a8150ae0d0df2dae9dfb84c3d05 GIT binary patch literal 339 zcmV-Z0j&OsP)kdg00001b5ch_0Itp) z=>Px$4M{{nRCwC$)L8+WKomw{Bp?9^NWeiO0SQRJLE<1dXdGl5fP>;-aPW2SbYX6{ zr!I#lVD9;6Z~jEIzf;`Hvhi2TCxBm2Xx-r#6?!XRH2$>0MFzY6-TMgWqQt8eWKTdU z^KTFOp!L$XWTpHDw)#Q=ha^b=R&5!8LwlZ{!DcAmOpcEV9|MN-`~;R0@dj2am@2bf zADh+=;L)7p5iO1ZuvJe3NKF+9^i7yB2Z9;I0%N!%VTjM#5D3Otke~@L2LeO5K;;A+ zwf&zJdla#pm#<(6^EWU=$LFrEmM``E&Z4lodZGJr_PrwBgPofLh3xs^K`9S$u{fMF l|I7tCStJM8C1aHRpVx^y?woT~l~w=%002ovPDHLkV1i&Sjy3=Q literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/32/close-line.png b/pcsx2-qt/resources/icons/white/32/close-line.png new file mode 100644 index 0000000000000000000000000000000000000000..1a698a237257e8f4c2a65a5b6a1d1c7afe290d7e GIT binary patch literal 170 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UjKx9jP7LeL$-D$|QaoK8Ln>~) zozg0J#DM2O-4^+yvrT8OIuj%NY|&$jv>&2It^d7GN(l+HPhwaV{r=05g;oXPNB3|H=N7kG83!NXA`a7!T1B#G4BK6!H|+iJAhtYnO0`8nt8 S1aUQ>eGHzielF{r5}E)5z(0ck literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/32/dashboard-line.png b/pcsx2-qt/resources/icons/white/32/dashboard-line.png new file mode 100644 index 0000000000000000000000000000000000000000..f3e64dfc9b7e8414947a105e68588ad43a677717 GIT binary patch literal 186 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UjKx9jP7LeL$-D$|N<3X0Ln>~) zozlwdY$(EP9AxHU)^DC+?rBzVQ+4{o$8&#ps&9&$Vd=!aP5i=##SY3(QW-Ygkdg00001b5ch_0Itp) z=>Px$J4r-ARCwCW)CaNSFc?K)K@bE%FoZw}gM`5|gM`7F!3%?!LCj#oz+&)iU(Y`p zrA&P%#r@3HmF@JkF>L>Dq4;paoPJ`)&4-d+L3RRy0657{Lt6nX_ECVzIXW!Fvp-~NJ%yUhSVZk!IUk4UjKx9jP7LeL$-D$|CVIL!hE&{o zdnw%Un1M(`ph^gn(4z%~4XiQ>j4BTncpNy`JK2kakMH;W>hk4UjKx9jP7LeL$-D$|@;qG}Ln>~) zz2wfwqA1`JsG`8xY2oDbc!k+<|9!q1d9wFh%9qKq-WBqDz_N_db?JA*yGij=Z?Ny# zly&kGpQP%JbLp&&rDqm1Ful%YV7X?^z_F&BK`0`hS!KgL<{7$-%SHq)$ literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/32/dvd-line.png b/pcsx2-qt/resources/icons/white/32/dvd-line.png new file mode 100644 index 0000000000000000000000000000000000000000..33f4937c7b9a6b367c6a8ae6e809a97e90aa9fe8 GIT binary patch literal 382 zcmV-^0fGLBP)kdg00001b5ch_0Itp) z=>Px$I7vi7RCwCWlu>zvFc5?zArg=f2aSYtKpYeY#6fWYQg8qcI0wYR->==59J_pJ z-|o-baZH$*WZip1l>6-}AW(qK)mZ+S5Tcz|{X^6r#RCvNvs1H^RdH>I cvfNwq4U1t|WM;thfB*mh07*qoM6N<$g4@BO{r~^~ literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/32/file-add-line.png b/pcsx2-qt/resources/icons/white/32/file-add-line.png new file mode 100644 index 0000000000000000000000000000000000000000..6ab4efad537a7b4e3834669de75ca65258b61c3b GIT binary patch literal 222 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UjKx9jP7LeL$-D$|W_Y?dhE&{o zdX;@)bAU*5qKeC*$sc3{+!a1rF>sb%u==t4yT|u4iT(X&*WOd_u)1_3SN%yI44WGA>xG7G5r~!(d@z=gaSm54pXr-cI3hySDnX U+o?H8KsPdYy85}Sb4q9e058Z>6aWAK literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/32/file-line.png b/pcsx2-qt/resources/icons/white/32/file-line.png new file mode 100644 index 0000000000000000000000000000000000000000..0b4d190b45f10daa67ae1f9e95b419799d8d2dcb GIT binary patch literal 192 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UjKx9jP7LeL$-D$|sytmBLn>~) zo#H9#WGKMme#qifd4s6RF~?(!77k7ycYKeZRlC}*Jk@`Xwo0zy&M#~0B7a!x4=%_( oae;|h*WyvYEB`q?DSLR+=jcbV@;A-C1#|_2r>mdKI;Vst0NG4L00000 literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/32/file-list-line.png b/pcsx2-qt/resources/icons/white/32/file-list-line.png new file mode 100644 index 0000000000000000000000000000000000000000..815d88eb99d67411a8e9f64419ce2f89552f6794 GIT binary patch literal 182 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UjKx9jP7LeL$-D$|3OrpLLn>}v zof^(~K!JyKqJU$l5kNKPk zL%AbEkNd>a%uKv5`R{@S^qTTPH9a0zqR!$qDc7OwB+HA^$eZF5-9uJy7u!bwKK eqcx@PA@k~-cM4CA@0 literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/32/file-reduce-line.png b/pcsx2-qt/resources/icons/white/32/file-reduce-line.png new file mode 100644 index 0000000000000000000000000000000000000000..d3d09e469ed7dc8f955a54eeed11cf3687c04aeb GIT binary patch literal 195 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UjKx9jP7LeL$-D$|YCT;XLn>~) zz3R@$WGLc#(c_2EgofstmxU8N4lr8mc=Mv*<=?%3XL{qmU|t z#FLF$j1Jac+np>FT9}_O)>UIj}&dv(u8?(RU10BNP>FVdQ&MBb@0C}58P5=M^ literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/32/file-search-line.png b/pcsx2-qt/resources/icons/white/32/file-search-line.png new file mode 100644 index 0000000000000000000000000000000000000000..9b92c22a0da454847c23944355f648498da3ec50 GIT binary patch literal 288 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UjKx9jP7LeL$-D$|Zh5*mhE&{o zdeyP8*+Hb?;nWR6N{O;6LRw3OUMUm`$cV5XWwgkUX=q-*w?zNUr%l_>e5p_0*T{Zm z*>%q{zGo#oHZE&V2?~e^B@|}*dNgR7Dk~%@C?qAgyEK%~-*;!K>;WkTzqG9DQ+!Iz zELc*E6BzFVpA}&?-*7@x)9k}F=9~}TUuq@PNUadRVq@_24&$1lyBs~X2RPQ1F1%}P z!I&0)zu}Y3huPh0KdQ4i_!>5zZD*X?(Kst6{wYJA;e|5^FDhEx{xi;)d(_yxVP8|T kYR*mOn@f|6s%F+Pga$rS|6(qg2lOR_r>mdKI;Vst03T9seEk4UjKx9jP7LeL$-D$|j(WN{hE&{o zdNn=quz>(ufTIA5vq7^&ljKo>QwtI&IB+z#G#*)y`eD}>{l{1K?)xlv{IN}s0~`PT2Wm^#GoRtQA;r6G!HoSM)Y7iHu~y8i zzG|CshH2Ud*RJ@dbqXi!8Ti5;S;esZQqG7tB;((hb1B~P?+hD;po>r2`95YG0eXnR M)78&qol`;+0N$-=J^%m! literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/32/flask-line.png b/pcsx2-qt/resources/icons/white/32/flask-line.png new file mode 100644 index 0000000000000000000000000000000000000000..160bfec24b3b6894b9040064d89975f7bec02028 GIT binary patch literal 289 zcmV++0p9+JP)kdg00001b5ch_0Itp) z=>Px#+et)0RCwCm(+9QcKo9`Xkq`-yfP<`rNI(J(K*E=R1RVTz01jRpfP?#bYjZZQ zwL7P7FEjBo=?pJK7p)X8DPOoir+|Z^IDn#yIY5mIDn#OME}#Q@gTWr=K4D2dv4xeV zRxyVI7!}0`_R&rcYTQskAFXV`GHzIc?Y#Y`c)+Cn3^pF_|L_c(whTC8iYJ_K)V1!v zN!oKJoOCao?1Zz(k4UjKx9jP7LeL$-D$|8a!PbLn>~) zogB{CWXQudF@V#fgIQ@Q-vLF7ACDO{kDs`_|NFbzeA(+Wy$>mVSk`qTbiozD-yA*R zdgtawr!LXC&}4S$%vRS6t1h@THZKsZ+`@6o;ko#e+vhbLRyv7`aK!OWTPy85}Sb4q9e0CKKNdjJ3c literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/32/folder-open-line.png b/pcsx2-qt/resources/icons/white/32/folder-open-line.png new file mode 100644 index 0000000000000000000000000000000000000000..4b96da026f3bb83bef33703f1d92082fc1b7468c GIT binary patch literal 255 zcmVkdg00001b5ch_0Itp) z=>Px#xk*GpRCwC$&=J(bFcgO2-ziRYs!QYaJH=&ls?(h6G?&$-acQ0A@(!-7z1jH^ z*qQXbn3D&-zcY)#N~u%}L;yu-AT#gIx-b+jlz@0(6Q5fIpa}_B^h9@A04nBR1J<_y zdWQNd0UPyY2hh>d%>xO5ktl8g;NJuG(8;-+Oqv)UWiE{t|002ovPDHLk FV1ieuX~O^j literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/32/folder-reduce-line.png b/pcsx2-qt/resources/icons/white/32/folder-reduce-line.png new file mode 100644 index 0000000000000000000000000000000000000000..0482952660bb0c3982b22c7a36ed136a8bfb5920 GIT binary patch literal 184 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UjKx9jP7LeL$-D$|iacE$Ln>~) zz2M2%q$uK=s3dTZV|}AO7w?vZCJk(EmVVkFtzO&D-~C_W&?Qx+>X4VA2X=;6v9EZt zI`-CvX`7=DNSl4!F_qz-QVc_`gxkdg00001b5ch_0Itp) z=>Px#&q+i%IK^ehXlAGGSA;OO^pWA$!0pJhCuXHLx6_1n>j` z{y1>y)>{E`E@ZT22$=yuSW5Ij&bP>oE0J%x?H0NZ8Q&dl0R92m=MO+(0B{5VZi9Cb zaC)Gi?M_8op$8HmGA!~Su{;C$tStd3zf1aOpjfX39D?AC!12GaA}FVt1LnqnqsCg@ bKAW>A3R>C|1t*WC00000NkvXXu0mjf>;Y|( literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/32/fullscreen-line.png b/pcsx2-qt/resources/icons/white/32/fullscreen-line.png new file mode 100644 index 0000000000000000000000000000000000000000..938d59fc41ea48ac8d911ac5837bbdf1b2bce737 GIT binary patch literal 146 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UjKx9jP7LeL$-D$|d^}woLn>}1 zORx%jxc|^z%c3FlDx*m#Ux@hwrT|VBzXMqZSa$qjYGGXVE0EdkpvD46FB#JZ sfByd!=veL1e0qMngh7PY(?A9Wrx)%?6U;B40Gi9->FVdQ&MBb@0DUAb%m4rY literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/32/function-line.png b/pcsx2-qt/resources/icons/white/32/function-line.png new file mode 100644 index 0000000000000000000000000000000000000000..d6cef0acc1587c90322b0e27a42cd517e946001f GIT binary patch literal 163 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UjKx9jP7LeL$-D$|Vm)0PLn>~) z4c^LlKtUkoe^>90-h}3gPjSa~u6}Sgh&}N`gDaDN$)skpgyn?}lfbP0l+XkKLUujH literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/32/gamepad-line.png b/pcsx2-qt/resources/icons/white/32/gamepad-line.png new file mode 100644 index 0000000000000000000000000000000000000000..b8a0b528aa30903e9663b6eb55bbe4308b1b4b71 GIT binary patch literal 322 zcmV-I0lof-P)kdg00001b5ch_0Itp) z=>Px#{7FPXRCwC$(^;X~KmbPJN{EC=7ze}waZnt91LA-<00}rC4q69^ldsEk$)2IT z_dapvX_o)Y{^`XVBe!?~fzjXZdP4*lWR8*|4LNN{ol;!fL713!9ZIRhq?eS1IErU%VmT03tD3 U=NrXS$p8QV07*qoM6N<$f(4F@wEzGB literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/32/hard-drive-2-line.png b/pcsx2-qt/resources/icons/white/32/hard-drive-2-line.png new file mode 100644 index 0000000000000000000000000000000000000000..5eca6f64625960683ce8df5ef9e02e3e8c6f2a34 GIT binary patch literal 167 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UjKx9jP7LeL$-D$|5~) zonk3?z<`H^*D>IXLsK4`5R387CEu$<@7*;rmj3_J&S62zB{nUIJ-idXFf-mZIMz7H z%%QUO0skMy_PiI(49A)8GfRlfywm#a_fkgdv)}F5MR_i=vAAixG?URVUS0fsTI5Bb OWelFKelF{r5}E*lYB^*8 literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/32/keyboard-line.png b/pcsx2-qt/resources/icons/white/32/keyboard-line.png new file mode 100644 index 0000000000000000000000000000000000000000..abe10c84fee0370a9f02dfaacae402c9183126df GIT binary patch literal 162 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UjKx9jP7LeL$-D$|Vmw_OLn>}1 zORzR`{we>F&)<}=jiKf5eTFl}4WC#hTuqc>U=45;C}22a&G6JxVYPG*n@od4$NGhP zI1}!%Xq;z=yj$SMzW8N=iR=U>LtX{J3kMk3FFi=$`WMK+5Y2kd({hH*J)k`dp00i_ I>zopr0Lz*)fdBvi literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/32/layout-grid-line.png b/pcsx2-qt/resources/icons/white/32/layout-grid-line.png new file mode 100644 index 0000000000000000000000000000000000000000..a56f809fe627440c0d5cfec4b0cad8e3d9999ed2 GIT binary patch literal 170 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UjKx9jP7LeL$-D$|QaoK8Ln>~) zo#f7Vz<`ID`Cw0$8|UF^j%O4CuI&9DXZhFWo$Aw*j0Y2S`9ioQq93et5GYj&{=9C1 z+?pBo{tsBC9M!9`?uo8>UeU-{o}~6H{%xapM9bV8oj*7Fs&HIWQh0i@gK^&k_6H{l SwXOo~WAJqKb6Mw<&;$U+VLf#K literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/32/list-check.png b/pcsx2-qt/resources/icons/white/32/list-check.png new file mode 100644 index 0000000000000000000000000000000000000000..8928e08379574666f07a5c8e241b26028fb7a2b8 GIT binary patch literal 156 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UjKx9jP7LeL$-D$|!aQ9ZLn>~) zy%fsGV93LK!NWqcc!O2`+@~{UX6i3qZ`dT&YR9`RNm1JRRm?JVfpb=gOsAV4NPU*q zTr~gmU-`^;97f_S>I(udCp4?qskqPP-nH%j1_ibt`6YSxre?YVZD8bP0l+XkK D;=MEI literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/32/pause-line.png b/pcsx2-qt/resources/icons/white/32/pause-line.png new file mode 100644 index 0000000000000000000000000000000000000000..36d893c5e72e590c7dca86df16c8bd0073adb3ea GIT binary patch literal 129 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UjKx9jP7LeL$-D$|tUX;ELn>}1 zORzQz{we>F|NlP2>K?{`WAipJDikoRKEpUcM3z^=G~r4^AhR290fUo_tCUAyq6m)@ a0|Qgmoqx@_Zk4UjKx9jP7LeL$-D$|YCK&WLn>~) zyZw`7jl{+q zijB{8v( rpU3X9oq5r1#*4X)FaAAWTg;+1Rpvyr@+}RZI~Y7&{an^LB{Ts5X$3~) literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/32/refresh-line.png b/pcsx2-qt/resources/icons/white/32/refresh-line.png new file mode 100644 index 0000000000000000000000000000000000000000..5d3d6895c5468b127ff75801d4b01bac3cbc4ef8 GIT binary patch literal 355 zcmV-p0i6DcP)kdg00001b5ch_0Itp) z=>Px$9Z5t%RCwCWm1&)WFc3#qLL?vo2Sh?0EDnl;#zApF9BdtI9TW%PAaU^an4a08 z%x^#S^*-jGxp{kKsA%N&AJaV(_ng>K*^C_nOYCuL=AMKw6Y21HIwBb%Q9l=G0my8W zUot}bTw>;+JBlM9)cVhryc#@A%D;$wsy4br|F>`e-Xw7ZD2hri)iw!i00@s?j(Uc5 zn2W=3T$peMfdjx}``icyfgl;v1=6|?bhy=vgA-tQVHR$UP(dt#D@Waud^6kdg00001b5ch_0Itp) z=>Px$7)eAyRCwCWl^KAY76^%0ihkZ%}3$ z?fZUxzjAqjnmpC-zC-J&{W((o}ARjQW=5O zIA+2M4Erwuo*B!7Ub~sk>K`h&y6)g3?BH&=WGl+m3~&br2NA6uP~_Og1u;(njMoQg z)`CN$JehJ(#ftD{>PHq)$ literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/32/save-3-line.png b/pcsx2-qt/resources/icons/white/32/save-3-line.png new file mode 100644 index 0000000000000000000000000000000000000000..296968465c37cde7d6186bf1eec8abc2295b18e6 GIT binary patch literal 184 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UjKx9jP7LeL$-D$|iacE$Ln>~) zz2eTuY{UVs+%kdg00001b5ch_0Itp) z=>Px#+et)0RCwC$)CFO~P!NFON~nZNh=V){kx(g<5C@acImkI!B-BCX;45=)O!Ply zI2iqN0K~c$u&xzQlJWQ6AcS^ZYsGeIC4?#&|85AK_E|Gqoi6>vrxEbJ8{}vV0B%Pw z#n|AOW|X6-RfqaI%s?*$7}VP^eoPZv$MoCr8^_!~rOF4Y{fyob*Pc-JN(*J%v&UrH z*TW0MWTI9l<4k4UjKx9jP7LeL$-D$|3O!vMLn>~) zoqCYb!9ajzean4o3^FO!w{S+?%edoW?b%V;L0Xyz%Ond%?#|6z=gTe~DWM4fTv$LF literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/32/settings-3-line.png b/pcsx2-qt/resources/icons/white/32/settings-3-line.png new file mode 100644 index 0000000000000000000000000000000000000000..52e455439a62cf0335e94cfe294145f4c4c887b8 GIT binary patch literal 380 zcmV-?0fYXDP)kdg00001b5ch_0Itp) z=>Px$Hc3Q5RCwCW)d_JcF%SgMKoEpLFas}x9|(b92!Rj=4ugb&mw}go#6V*3{;krM zC->!6U6Q6}t6N>O7v~Jg_TNfxq3?{?y}rSn5qGbpZ&VbMNkO%F5p6~1`p@nfRQ>i* zIe!JgJ!V=#@o@SLlLrMwWU2(piwrK{GJ5shD-v=W3COFn zD+d*aQIMo|5kZUHKp)XtxasuJg}Lu%uvpgT7LX!s3u@o>Bq;HD+`y{v*c-Hd-uyd{ zB`j+RvK&c!JV-Y$G;tr(o3whyS;M@}Wtc^`uL)96>k{NwL^;JGDzbY&PH0!|Wz@%m z)Qs3m!`wWS}f&MYDN+q_vZg)@d)CjvBJ9t=#EfHP+)F%j+ST{;hOz a#`p;nzg?R;<2xh(0000kdg00001b5ch_0Itp) z=>Px$0!c(cRCwCW)M=TVKokYgCjkj90SAMFw1X)J83%)eazHysONaym_?-=(Dwt28>7Ady50`hJ^23)rrUqCwi zgaj7lWV{CXtV9{SD$9epW(sC-i85EJ3P>QAsdMcfm@&oIz`Z))4&}kVI-m#LZx86A zlC&N06jjO_yajGir7ZC+Bc`Zi##mm8RM|jTxeJ&yaYAD%SNSbBkfTs}PGbcxk4UjKx9jP7LeL$-D$|l001;Ln>~) zof68(peVpUtP)kdg00001b5ch_0Itp) z=>Px$3`s;mRCwC$)ER)oKoo%Cb&6A*>auZ)Q=HbRPIaow)@9?;b?LZFT)sW?dQEy` z-_TtDyWh)G&WYJQpDSyQkv3r=wv8Zumo9k>iZl5P)01KJ%0xb2!ML$ zMS>kSG+tw^!JyScL?rtd&DMhD_&*P@IFTE+gmuK^azJ?xmiGb{*8!lPbU{=?8K4t7BZzOsG>!%1u#lkMOXF;+ zkTwnrsVfvx+AZZ+K0($xbYXQ+6XkYNSphN>Uu-a_?`Y@9z?5hWCkIMWogZnb2n+a07*qoM6N<$f|dG^bN~PV literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/32/window-2-line.png b/pcsx2-qt/resources/icons/white/32/window-2-line.png new file mode 100644 index 0000000000000000000000000000000000000000..56a2e000ed6649097b33c52ac63062eff53d78a8 GIT binary patch literal 163 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UjKx9jP7LeL$-D$|Vm)0PLn>~) zogB(|z(BxR{oxWHr#b8Ss`#{2lhc;%t9dS}|KUl%iydO_D_I|iT@ckcd+5amhVK3z zUZ!gg)EZX`38~*XDE8rO{f77ltZ@mFx;?fvGg@}-S~q3S+fK%SnqBPktY3uyEn@I= L^>bP0l+XkKV+A{9 literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/48/artboard-2-line.png b/pcsx2-qt/resources/icons/white/48/artboard-2-line.png new file mode 100644 index 0000000000000000000000000000000000000000..37bd53a4882872c65c561e28f91682d44774d289 GIT binary patch literal 121 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZA`BpB)|k7xlYrjj7PUL?36?dI><{L6Hu&_JcwQiL3 Q0cv3IboFyt=akR{0JV1^2><{9 literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/48/book-open-line.png b/pcsx2-qt/resources/icons/white/48/book-open-line.png new file mode 100644 index 0000000000000000000000000000000000000000..38b4853de85545cddf2692aa26fe744fc4caffca GIT binary patch literal 214 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}a}t#^NA%Cx&(BWL^R}6FglULn>~) zJsZy0Vj#e9acTq05*MdA3XGnPhb$S`A24th@4Z-k_xtWtmz6IQI#*79%<<8Fx1D0& z94pOjG9H=YCwLj(J}O!u-M)BoQG(T3#+5A(53T2DRu{EmKUF6*af*+i!Je6wOmUg% zF$>;^F!E+NFiTx%U^7|J*ttgH#EhUIz6haZlM+)o;+8P;&N(6amdE@`p=`r_pxYQc MUHx3vIVCg!0Ifhu_W%F@ literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/48/brush-line.png b/pcsx2-qt/resources/icons/white/48/brush-line.png new file mode 100644 index 0000000000000000000000000000000000000000..b082ba6f10a11f35675802143b4eea19e592762f GIT binary patch literal 470 zcmV;{0V)28P)Px$kV!;ARCwC$mRA9zKoCG9VI?489RvqKLL4Lxf&*~CI=DCh2jBo41PAX5Wj1~0 zdVAl?ye(7M{O9k1l$z6z3x8sw`Vqk$HNX#2;OPN=kb*tJ_g3Jj5E>G?@2DW5MJOlb zcTosKHQFidJS1qA$0hglPv^Y@B=;13Amf}a&f691zQ9~zEHA|t5Z;| zh`(rT;YR`)ZocD;@JNAQUunR-Z(TxpzZ^$?t-wXs>ED;o<$bb|GKzu^hV&%YWdcqV zhU6sCX9AvUI^27~k%?iWfFr_4qWPq?m3nkdnXX4JIHjPpwT3h*l^^j1853v2T$pHe z%X7&fEzIZ^2PTd`O3*Xm?QdXNJX)rkMNk``IKI@UUui7+*EN=ST#SYX zmap=6j~h<_d-%aJeR~SLF1+&1Na?txrC^79`m~jH$jB)g9#KI+bZh{t2&a-us^0_weSMO3xVTRPQ=*JQULUnz1g_XTQo<2Yp@9i~C1?ij%*gI#=Q*W!9$51sv;$>yi>%WZul|SuI`Jevk|HVJv zt@Rr}>tFxJdwhTDsxAJxzh=+T{`Teu)0YHRYNbYaURzS7Ijt=$;nD7@&Y#ul9wLvDhQ~! f+j-q?5hKH*ruc=MKh9$YYG?3t^>bP0l+XkKPx$&`Cr=RCwC$mT7^bKoCYt0uqpbgBb~N5FCJmjf07UjDu+h8wcP39Dsv2_qSJd zElqyy@&1MD6?Fys&z}Je9Tx`u9=Ooa*zHi!G4X+pelpQf-SY4U#IFMJuuH;$31Wem zI9M560K`h7=iU>D%ihP&v_GeSPg-V2QS|teIRHu-eW8LApNzRipzz{%&;_$ukw7qq0cvvFy#lrD zWI|ADYAbW$$N<2Q6;!OeQ(M-aGWGe&FF4mF-s!iU5)3$4Ntj&ogF*`%1^2vj&BkaB zBwT_97`AkVU@8_+Ch(be38>Ahk4Flc-4Tq%MQ(#4kELxodhwUF=_wwxpA=Wkdl;s_ zZmW!^kf{I+5V);+UwyA#@QA9L0&+==GpY#!90eZB+wT&oKnd1s-qLTDh0U;A8& zy6`)8aQ;ui&z6B|$K&B(B1lt7qG%YR^u)`#v){lM5NYcRClC|I>~%*b5NT^tl_;_B z#F0Ddk9NMJ<4IGi4lC1-SK%T+Cd-31zuI~^4uvkcYh2cQPy4@E{wo#eE(T9mKbLh* G2~7aJ-cT?A literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/48/download-2-line.png b/pcsx2-qt/resources/icons/white/48/download-2-line.png new file mode 100644 index 0000000000000000000000000000000000000000..4702cae9a6498543ffc229a766c586894b34f7cf GIT binary patch literal 186 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}a}t#^NA%Cx&(BWL^R}C7v#hAr-gY zp7ms8R%CF#II*L}%UMXMV3J%>ih9Z18pi*z=k}U6NE#T-W@KjjaKI~2;mBUUg7reb zs%G8Fz2l)^gG7yg1N)22WQ%mvv4FO#t<@KMw!^ literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/48/dvd-line.png b/pcsx2-qt/resources/icons/white/48/dvd-line.png new file mode 100644 index 0000000000000000000000000000000000000000..5c1c7bada7d9720c2157777953ba7dcaaa0fa0e3 GIT binary patch literal 580 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sjKx9jP7LeL$-D$|`~rMJT!Hj| z>VXN4_{7?cN*gJ1_Sn z&s(H2OKn{IPVGwH2po7}re_{lfX@%zFpZ zXOnU(D?I-nIDN;=i6drm-Jb9Mk;QyN==OBjFcI`%WYStH41n{-B z@H0GQcKE>{VAs&XZ(t~QVKYO$oI=Bb$qfwuY#bO~Ty|(+$otOR?RMo`qg6x3EU&5% Z=|lEbvHZ;oB!KoZc)I$ztaD0e0syA&JL3QV literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/48/file-line.png b/pcsx2-qt/resources/icons/white/48/file-line.png new file mode 100644 index 0000000000000000000000000000000000000000..9ccd173352a5f99a9bb92a030911f54281c755ec GIT binary patch literal 192 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}a}t#^NA%Cx&(BWL^R}Rh}-6Ar-gY zo=s$Qa1d~L*sIdSaovYys>zPt1&j~wGw>wDHGH30q0fz-cFZErnc`Tb5~3e5GqG?8C^$4QIGHoY8Gg!C h5a3wfw9-A4IWl4OwaZ=R4}h*<@O1TaS?83{1OU36LMZ?M literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/48/file-list-line.png b/pcsx2-qt/resources/icons/white/48/file-list-line.png new file mode 100644 index 0000000000000000000000000000000000000000..a935757db61a4a49d7b5173124341a438bad8ba4 GIT binary patch literal 157 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}a}t#^NA%Cx&(BWL^R};hrvzAr-gY zUT0@Kq9AhMg8oNqcJ>D<=?`|5&rVm6J#%621d(l7PYM=nY&Vj*aq??p#K{BoY%>ZH z4m2FTQkifd@s*9ih9*Yl^*bMyd<$r2_Do&v*OvE`ec_sIQ;wb8a2IF=gQu&X%Q~lo FCID-aJU9RV literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/48/file-reduce-line.png b/pcsx2-qt/resources/icons/white/48/file-reduce-line.png new file mode 100644 index 0000000000000000000000000000000000000000..78640a9a51b2f6a0b962ef7b270ae3a6664ba5dc GIT binary patch literal 175 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}a}t#^NA%Cx&(BWL^R}nVv3=Ar-gY zo^@wzFc5J$sIMzj(6Q9xfTN1e_Lvp@ab-L{H#Tdm?8x#AaQWcGqrarMTk43{jt7i7 z6%5>R2b$P7JYdw|dV8!j_9`MICC>}a*%MAjj>rR+ak WZ>?i5FD?RF%HZkh=d#Wzp$PzOlsZcQ literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/48/file-search-line.png b/pcsx2-qt/resources/icons/white/48/file-search-line.png new file mode 100644 index 0000000000000000000000000000000000000000..04ded90a8f58ef0d458f80230495d3b67cee895d GIT binary patch literal 326 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}a}t#^NA%Cx&(BWL^T<44y8IAr-gY zUS>}`Y#`v4=qSMAXvpGh&|**^afK&@tyRF;fO&S~p$DaZZf1L4|0`G_RsQb&cPH0J zaUbV7IIR)th-K;#R^Y#D5yh^6jO+vyE^J~D;-1GL&U{C9!P9_fg9E46@_!6yuzX&^ z%EWR_Eo)El6`nP%e-6mZ{BVwQ9^=fX>{UyOtXE95vsdsgUl5bPq_ESrB4tb6B2D`X zika^xA1|8!CQ`|B`+}qt-6?TfT%4AiH1I{an^LB{Ts5V7q;z literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/48/file-settings-line.png b/pcsx2-qt/resources/icons/white/48/file-settings-line.png new file mode 100644 index 0000000000000000000000000000000000000000..38206fcff8ce502023599ea78e9048313027b26e GIT binary patch literal 302 zcmV+}0nz@6P)Px#=t)FDRCwC$)CXZhArOG!NH__Vf`gue;sl%^C2>$3v<|?*-dumfXuH8ui7_7!b_(wWwY$Pc>oo>~u)Q29&O_NyX>L$qVR zWd7du0|*~m57E2%5G{@W#_R7%ep`A^uXUJCW5TW`Lo5}YjM7=GqhvZ^xW!J$Ov}c_ z&hYt_1Jf=EJb@=*?ef4ULsYi|0XtFA(X*@S3#qu=3wW|==>Px#07*qoM6N<$f{}xL AxBvhE literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/48/flask-line.png b/pcsx2-qt/resources/icons/white/48/flask-line.png new file mode 100644 index 0000000000000000000000000000000000000000..627f8215cfbdd4b74d9e866db147a97a97e09978 GIT binary patch literal 325 zcmV-L0lNN)P)Px#|4BqaRCwC$)M=fbnPHLVM}FD= z>>2X%{?p9+V%lK989(@f0cS$N=Leh#2A>~rCKP;hM0MX)z;GnuIOi4=)+)4e^W)qB X%Zu~80>=}S00000NkvXXu0mjfLy?O_ literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/48/folder-add-line.png b/pcsx2-qt/resources/icons/white/48/folder-add-line.png new file mode 100644 index 0000000000000000000000000000000000000000..b7c439011fee46a8e93fc9f572d922ad72fe07f1 GIT binary patch literal 175 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}a}t#^NA%Cx&(BWL^R}nVv3=Ar-gY zo{bb_Q4nAbROw@pX=W~%BznL%?fsYX@VyJB#2cub>6QsF=vFuASKkn=(s+SaP+jzf z%!AG?3>8UCXE@~)KC`oY=x1Pz_!8p4@R38n!G)RW$32daifPdYCOiynWnFrwiouqJ V%jHw( literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/48/folder-open-line.png b/pcsx2-qt/resources/icons/white/48/folder-open-line.png new file mode 100644 index 0000000000000000000000000000000000000000..02da20c8dff6931068377f67ab63d549a8710fff GIT binary patch literal 254 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}a}t#^NA%Cx&(BWL^R}J3L(+Ln>~) zy=W+T*g(Mfp#$d>2Ll#oP7!C$<|&QL3wf>tUbg(Jlf8R;)(`gF$ZdKecO=417c^UP z81OSja55c?`j{{KlyySqZ`KQ)oDD5}9Fh$zEhY+#Z!T23Ikd(}GxAoIvXs1dtJqNc zuRx|of$7HqQ^q-9+LAE_OdB$OXv{FObvW>af$=gkiw}Q+37jKle?Z|l`yPkx2KMY# lf&m{IZ;9ug-t_b@LsNoB_p-TcKY)H<@O1TaS?83{1OPA1S9t&c literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/48/folder-reduce-line.png b/pcsx2-qt/resources/icons/white/48/folder-reduce-line.png new file mode 100644 index 0000000000000000000000000000000000000000..4d4a674b2d819568f0ddaaf24a5b9be52f6e1350 GIT binary patch literal 172 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}a}t#^NA%Cx&(BWL^R}X`U{QAr-gY zUgBhAQQ&Y1R2DjT?3TmqGi)EqZdBJbE_Vnxr7t1$%#}}L#!=xJ$Axd`PI7qB)-jnY zhwp<+6oZW+<1-fi3AIWJ4h;-CN(_wl)vin|91p6M10F`^F*>(}i;7Jtdd>K*Y1LE> T;UAtr8yP%Z{an^LB{Ts5M=v#S literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/48/folder-settings-line.png b/pcsx2-qt/resources/icons/white/48/folder-settings-line.png new file mode 100644 index 0000000000000000000000000000000000000000..e0bd0699c4348ef9e56a3d39a8949063d1cd1ad6 GIT binary patch literal 297 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}a}t#^NA%Cx&(BWL^R}k3C%+Ln>~) zy~^%*)Igx&p;rfsN{Ewe0bd7;jLEqQJ|XtC%|{NNw=p}uW5egYf35z#^E-Tj-|3iG zZ6j~DedF2r4SAf_2DaTNs&hJIKd5g4VgshKL-GZ`o-^kC*~akj)!z=$twJRo)(4zY zU02)*?_3iiR#`?Ge z_guKI8y&0{2@QGrBuia>a-itXmF+7ZH9WoWIOw7WNAGVbr$vUlnY$dERQKNK{oVNR tKHHyth8r#iS4s(R?R|5?gy-%Y=GOF8k6M2O<^sLT;OXk;vd$@?2>?f@dkFvl literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/48/fullscreen-line.png b/pcsx2-qt/resources/icons/white/48/fullscreen-line.png new file mode 100644 index 0000000000000000000000000000000000000000..caa75dde081dd25b9691631ff191d7ed929225dc GIT binary patch literal 116 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZA`BpB)|k7xlYrjj7PUHRYNbY@k>PQAYcFh literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/48/gamepad-line.png b/pcsx2-qt/resources/icons/white/48/gamepad-line.png new file mode 100644 index 0000000000000000000000000000000000000000..99a318a43820fb852ff1b3abca3536203485507a GIT binary patch literal 337 zcmV-X0j~auP)Px$3rR#lRCwC$)@PxEFcgO2BLN9RdvMdY1v0_KhC3<$OaOX6>CC2t>CwL}eO?V4qNh-@z&F`rxDeP>N)a-fXKd=3S z)N1-tG|2Q}FKCg{B2Xf&y}}`lMWR8R36<8~qEVo|s7&az`j!ASPdXFE!|Ttpqv_#& z=);BMJ1BUTyaV|i9YC0!lsgDXzsceJpYMPozJu^Z)ALj&=|E4#@Qsh1p6ijqqA`Ac zt{qaR{BXYG%M=_lvbp*%+aNRUbI|o_WX|Ar-gY zUfIaYpvdDK=yvAW2F9F6vk#m-DkSp%`ouWl$_vpeO|Isyw%IT>qe|gQZNeWN4xmO5 y_`>|*zopr0NrCB@&Et; literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/48/layout-grid-line.png b/pcsx2-qt/resources/icons/white/48/layout-grid-line.png new file mode 100644 index 0000000000000000000000000000000000000000..e492e600dd90329247b6e597d639b4c0c4381e72 GIT binary patch literal 150 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}a}t#^NA%Cx&(BWL^R}0iG_7Ar-gY zUfsycpvZGH!0pVl4U9RDq!sRdRF&H8yCnQy;KlUo|Nn2_(CrjtzbS`-VfI7Y&pZB|x&>6l;OXk;vd$@? F2>^MrAV&ZI literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/48/pause-line.png b/pcsx2-qt/resources/icons/white/48/pause-line.png new file mode 100644 index 0000000000000000000000000000000000000000..f2521a2e97a2b5558f3b9e986cae12120396065b GIT binary patch literal 103 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZA`BpB)|k7xlYrjj7PU~) zooLO-xR2r3A>rMRVVCb-&S2T@pgOSi|1_F9ln)m TA5Inl-O1qT>gTe~DWM4f*xgyb literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/48/refresh-line.png b/pcsx2-qt/resources/icons/white/48/refresh-line.png new file mode 100644 index 0000000000000000000000000000000000000000..ca63f26e786f3bb976b632361ec0e7a8178f44bc GIT binary patch literal 484 zcmVPx$o=HSORCwC$)=`nGFcik|BOwwZ0S6liIJk9C94rnN2aSW`pg3q9fCF&wzYolr z*=8neICSDH67k!~M8`7%-8_FF-Ig zQEHJCFSHE}h`gKfG3~_wEo?RB6CCTREdNcRKR)y+`QXZvcY~Zm%SNePx$l}SWFRCwC$))|20Koo%C=TuH{Dwl~U?gR4lxhvoACAVyO#GU@R=+Sn@)eS{3wzD<5$>{$Q<~ zh^_3GC#|T17sI2hRANN=^joNlG+;&z$0tNgW9Ns%RHHNJERAqFqdu<$WjaPI%{-@98=z=-!fIy~=y=Lmxo$0n+KGJb;b&(hp#eYFOS2@*Yn;Il!Y* z#m?F_hXx|At?5Sd(n7CS?uU;G3=s~;J)tc~iZd!4kL#P1D1XKNg`*fVLB{tE3^w3W zlBio_sx1*y_S- RnGXN}002ovPDHLkV1m-G*PZ|X literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/48/save-3-line.png b/pcsx2-qt/resources/icons/white/48/save-3-line.png new file mode 100644 index 0000000000000000000000000000000000000000..9ad14fc7f55813384e1bb674a22014468f0b4758 GIT binary patch literal 182 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}a}t#^NA%Cx&(BWL^R}1)eUBAr-gY zo@HlrG8ADxpw6i|wMAIvqig{C1BEv?w;nntX(9Zu>9EjVG1G%wj9HBgI~^K=S{%JI ztY;(&i|kR*c=kj=~) zy^_w@Y#`w9(8GaCXo7&Jg%eYugA5D%!Go{wg+IGE^L_kt<;yCadVL~s=XsVme5#XK z`QMIJj1}g$C7n!FXP3D?r|v;mjP-@R$z5N>BkN~d zSse(hx$ZbwQ|D8KyhO&#!~OgbHo;pXCabJ6YibPIZr}5MHP8zTp00i_>zopr0OJH; AH~;_u literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/48/sd-card-line.png b/pcsx2-qt/resources/icons/white/48/sd-card-line.png new file mode 100644 index 0000000000000000000000000000000000000000..938177af84e7fd1e07b5c509aca8ce873ccb034d GIT binary patch literal 190 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}a}t#^NA%Cx&(BWL^R}6`n4RAr-gY zp54gk;2_fc(0o#09*gvD-aBkL0=x_y3_n+e{PexYV0g{zq2F$O)jzhKdza_#J}-Ea zY09^|pBewjEuDGx{$wDns^oA-Uhc%L@98XFPybdjZobaW!Q~U^z{n~R03<5-xmeyj koX*TA7tLxh`M4&7(2CCbI?uZnKsPXWy85}Sb4q9e01S;lq5uE@ literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/48/settings-3-line.png b/pcsx2-qt/resources/icons/white/48/settings-3-line.png new file mode 100644 index 0000000000000000000000000000000000000000..6fda0b6933aac8f63721bc5e420cbb726639989f GIT binary patch literal 573 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sjKx9jP7LeL$-D$|`~rMJT!Hj| z>VXNZ^1M}?*J|qMP+`q7(;X(ZV4Hw!9{{!7p?&;zf zQgQ3<)w6kP40zfS#p5okY)Sl)yW_vKt^0zASxc7c?3$IH*|Nd@sAJrf=*vu7&d$Em zJTY42@=4kEGrr%JFHqw9ptZk9j^k52!=B0VrhnT{xbrv2t!F9-lU`ZV^mC2(UssdD z6Gs)3PaBqD%5RZ$>S;xUMoUXsKM)%62_r!&e8Bcni41 zvUjevVl10+?0{m=PN9adqa{zfcodHrbX@$DkyFlhKc>g#WGiQm%j?X&aSI}9FKz#E zWxDn)b^q6^MP-#ESG!g%&z+*q-y^T1V-WQ`AU?%)Q4F50elF{r5}E)b86pn= literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/48/shut-down-line.png b/pcsx2-qt/resources/icons/white/48/shut-down-line.png new file mode 100644 index 0000000000000000000000000000000000000000..962006d9ab2e7e0bd4693b81ab81ce1866ad77c7 GIT binary patch literal 456 zcmV;(0XP1MP)Px$f=NU{RCwC$mKlJlUz6UE1nh&ak61qd==iSpqV%{r>1C=duJ;thZnc zEOPe&05Etz&VR>#V3Q?43>gq@fzeE|jh*DtQNcd!Zn7WwvCTZ%vveWmXMl9?@vI literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/48/tv-2-line.png b/pcsx2-qt/resources/icons/white/48/tv-2-line.png new file mode 100644 index 0000000000000000000000000000000000000000..35b8b614c856db72dc635cdf56798e8fa4bfb930 GIT binary patch literal 147 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}a}t#^NA%Cx&(BWL^R}zMd|QAr-gY zp7myIVBm0ZTqJn(*`)@KY=*pD|L%*-V5pgrwR1}m+v+)0Y-!(F3oo!U!9cfk^Q7Rj n35H@zd-5;)wKXu5-(Z+}?fTZB<+dAv1~YiN`njxgN@xNAf59$M literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/48/volume-up-line.png b/pcsx2-qt/resources/icons/white/48/volume-up-line.png new file mode 100644 index 0000000000000000000000000000000000000000..74b65c9579607da38aab9b723a6cac71e2ca49ce GIT binary patch literal 450 zcmV;z0X_bSP)Px$d`Uz>RCwC$mS@4MFcd%|Arg>)gi1gH66&Bxcn*q#)&V#G2jBo4oX&LJ?6=l; z{xMJ19qNlO{wdIO9F!AEwkjV=CIHX#1*laX)GPqt{XLlqb^rmv3Rwy?2M_|EVxds6 zP%wZHgcO~MgAEHG1a`_$#ceDYMw@{0bG_-gF{u#j0Yi)Cj{jg%Sm!{+fSX30;ZEpa z^34qD_2z_&)r>?4YOtLeN^|#B;9{oyeCO>5U^_L;Vv(@Zqvasf3v4kIa*=4{#{!2y zj&}RDqc@KE0y&3XE**WoK>loy+su$D2;b+<-yonw?m_t@1+{0B#~&dWWV)adQtWgI zfu>^uO)_~?Ma6G*yiWwIRl~XWsV@12S*o~) zoyN#^#DT~4IftcJ-i13W_C7p!c9X*^WC*4Gj6c`*O{?<@y#f4k2QA< z`LD4GI504Fd+b)eDRtraa`vBRYbX9tubauC@Zn3>ZQURiz3pl1loV3etw~}5se};o z`Yt^d(_~sMe|FAS4c+3u|0DYtm@X`T(<-KSv+AIh8&F-E%Wc`S*BEZh)!+4L*1Xro RKjJ`c_jL7hS?83{1OSoQR15$B literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/64/book-open-line.png b/pcsx2-qt/resources/icons/white/64/book-open-line.png new file mode 100644 index 0000000000000000000000000000000000000000..965594916bbc763ad86736fa58e963ed4c289373 GIT binary patch literal 288 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I0wfs{c7_5;#^NA%Cx&(BWL^R}w>(`OLn>~) zy&T?n*g$~oVXJ~hT0x=^SCW8+V6#M%#2UpG$1@4*%D3;G^sD~&E2C{H!h)6VNvDO* z^_jjry*Ine!aFBdVrB=kboIeDX2D8}1*)e!RpzWPk_qT6p0h)WtK)aA!||*VqtDz4k44ofy`glX(f`ga`P9xB}__ zGyr{Wd@q1T%a#QB1v4nv3-tfLZ*cy-L;d=M`40{>EZF}c;KGFXf(`lp|5Gnl0Nu64 z)5S5Q;?~=#t&@};d7N!uUVOl7()$0uHGfR?-W1*9xQk9I%`vh&KM00MtLrahoAr(L zsWrok`EL!r9PpNLW6&=s*xxYSVhisFq4t&SM!x5ZW-QcM%TcKCQggMiCg0WI%gy4h zvH~p|C6ieiBCZDnP1D|U&bew{^_{e6tFmAB=g$1mv-`yluPc3X7T*89qu+Dgjfe8O zo;_A6f4_cM@bAT6gP$Aj6lH8Z_@SY0u22 zS?X+bP(@8xa>1LMoxBVQ0!O1Z3y9rYlrA`3wIWfmp!vYIGk>Ql*Pdu;T~o5f<(Z;w zjM6J_-xV1>hY#Nuoj0#4{LRai`A_T0Dh!Tvu1z>}nAgOL@p5~+bi~ZM*DLPz{bWh4 zig^DsJTrLvvUz*9+bz6s^?7xk%BSlWRvxW?v}5Ix0M`qWSCbCvKCU?*28ay!z{gxOjJmK0^e?PsL2Y=Wct7M$3eAMRy PBbvd})z4*}Q$iB}!oXIU literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/64/close-line.png b/pcsx2-qt/resources/icons/white/64/close-line.png new file mode 100644 index 0000000000000000000000000000000000000000..4eb73db92fc69538fb828b76f0edf7b8f69f9e1a GIT binary patch literal 262 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0J3?w7mbKU|e#^NA%Cx&(BWL^R}`~f~8u0Z-f z66kljmjM)FEeY}qW|*Jwe}PtR(odkk6i*k&kcwMxFK=aJG7xBe*!|@O!waSlFE;)y zU)25g>eZ8>7xk;QC7rvmed=b#CcmvhTaPw0%WW;XI=4YfP*oss)sd~TXOCIUU||#h zx#}XL3PXbggAlVr5`zcOSqw)`+BSIaFh7~ROTR1pZJmzY_TPs;oZe}#yMM2F;inl@ v?JoFx~T=vf#=spHdS3j3^P6~) zy|Iz^umO)tVE+@|GrdcAm-0^P)r;+Vz+lL^;@JOq-3L>*@GC7kKSlpuzSGNnj0_AH zMANh#W_-QRP&gxu;kVj57KR0<*W|MN=mMG0U}JKI$6>i!SvG^ZWW&sRnT(g; zWHSB_FA`*ASWpwl%fPTe`-~aW4=c6{B{Q%7lfO|9lyGqQ!X(b5VSJ2Ps10ZigQu&X J%Q~loCIGs)NHhQd literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/64/disc-line.png b/pcsx2-qt/resources/icons/white/64/disc-line.png new file mode 100644 index 0000000000000000000000000000000000000000..2ba7c67af8b08ac9213a754d7115dbaf197e1625 GIT binary patch literal 720 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I3?%1nZ+ru!7>k44ofy`glX(f`gar76xB}__ z)CLCeHq(IS%9I581v3cP=PUTnzyIIieEs`?^#&UnK3q7EP%vRZ|AYNS8?#Iq7?_wm zT^vIyZoQp$d)jOR9@l;~AA$0mb*lgW&sSZYq}z19;d0r%H2dh=(=5uGq- zGAul^|2M2>m3R}~V8HU#Wy4yf$_Yjm_QDB&jM)w{X0JaymBIGDhfdY?rX!b?Egn5( z>M6-wr5%^V@?qB`xpfQdW(uellm&l3^GoHR_Q~}zrfYo*W?YvkyYOi0>~{zH!orK* z2$)UZU3Adw*n>kG*X>xa@u|D2r<&-Ea*6)pw;l#74qSTCxB7R2@16z!r5%4yOHge3 zEGAaFuy=-IrJ0NPvvYIRm>*qL(7dkx*mZ*1UBgE<9-NV_GyNOncWi#`$b0YMRJ${G zkF5WEWA*#xEq3+7l>stJ5BRynegzeId5dlI>quCzT-1M=gQr(@{Yyc)9ZxpOHpoAe zGk4TqW4h|uYAu1NZ=cV+jy6cSxLS{^T83rHF};L&4U?XTy=Aj7|FD0__DNTSnZrE- zvjWcZbmu*BWnR4V>QX81=HMk)WuGRiUX@)N<9_$`^dF3Z9iP2E`U$k}aAg+L?pK*@ tb@!sL~) zy^)^SY9QhE(8GaA=#fI3&o>>^*t(#= zLdiJih(+8Fh4f&vEA_RHruO9PNxj&AFR$Uh6-PrydC{yV>-yGm|5#L|%G&Sk`}Kp( zx(Aa*{9``;O}NeM#guRJSi0fhb=H`F*K3)X&O}aTpRu`$kwc+@fr&$bVOowkGqYZN zyru(#_l>iY%A6Tl1RNNE1Vi_2ze9@QiIcn~) zy?l}JV1R^kqO!zN>5`>=R|I=5@V?Xf&-Rh&0Rz*MJMJgkZ%2A*-kqrrt_b_-)CfCIwQ^C z&``%3G tI(E6iaebBr?~Lq_ld*6A|Ck44ofy`glX(f`ga`P9xB}__ zGyr{Wd@q1T%a#QB1v4nv=hv_I??3ws%omxWh-elYv@%r~CJ3cFC%O}U5wuPQKFS358jKTUZvX?${zTUT?W|8=+ zN1q=qfAI1^yJr8P@YlTRIa8D_XILiIthJlYETk*-<=0BZscEaEpIH2tj{Q`2#?j;P zjQ>8YjYbboG>JT0UFDuJgHvGf_xHWQeu*Ch4A1s^v6Yp$&q}!>y}{srtG8qFRk20y zWF=X|``-yh9o!e7P}V=`UB&q@k83XcL>I*i0Die=ZsGk@%gp7XFfZZg5!@b zABx=^SJa=|5n%BCy*O9yt7RqAtNYBaN(D}+=afI{n<*ur^UG=TqUv_11^+HTVcWp^ zZ;!Y`(-%(OEB|6*FI-pLKl#upYu(fZ`_A51Q<6TcdF5ZFaL~tT_Ks BZ3O@T literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/64/file-add-line.png b/pcsx2-qt/resources/icons/white/64/file-add-line.png new file mode 100644 index 0000000000000000000000000000000000000000..c602339d886ea16bb619f4d372dba11c2bc23a99 GIT binary patch literal 284 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I0wfs{c7_5;#^NA%Cx&(BWL^R}*F0SuLn>~) zy%L^y#6W;8(UC*Nn3L6+gT=5zf~Dz*Lt>KQj4ONN?p#lOUccwt+}WGgupZNXf5h?k zgQH)2&5vn(SG&-l#pv11aEhg`cG9kVgT~~m7lJidTpI*L7@Y!GI2;8PpqvY=4%gT= zs4~p^VgH^l@NR@HuY%%I_4jMs7$pucupJOSe@($ueEpA1hR$tEdzpA1c&2N6Jbfg( zfAaGNMxF$Q`aSPW8O}%B1JxQdFd7vud8*go86~>FAoZ!Gf# ai|PBfdr#-~7gz#)$l&Sf=d#Wzp$PzeAZghE literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/64/file-line.png b/pcsx2-qt/resources/icons/white/64/file-line.png new file mode 100644 index 0000000000000000000000000000000000000000..d9c34f3b41e75544652caa98409ecbbf2b7108aa GIT binary patch literal 259 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I0wfs{c7_5;#^NA%Cx&(BWL^R}dp%toLn>~) zz3Rz$C_te3;luz=4~IjO8(36AoaP$%#mR2rn-Cz=Qtbay>{<%TpZmFoKJUx1Se&;{ zTJQH~;csg%Kl}OakZ+=my9X%nGGH^y0Jg_zwfZ76R(Ejx6cgBJO&34L<8g31=3~= tA&(iDg-)e@Sm?S)JZ6!r@VxJt)l6S5@4n-~) zy~)nlY{0-8sG^|C*QGo`h_kSPQZv1{HT`_mfp*KZ=L6gt=9zWZ Yn+a literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/64/file-reduce-line.png b/pcsx2-qt/resources/icons/white/64/file-reduce-line.png new file mode 100644 index 0000000000000000000000000000000000000000..9b4152821c677d15e589425d50c0618e2d32e677 GIT binary patch literal 254 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I0wfs{c7_5;#^NA%Cx&(BWL^R}J3L(+Ln>~) zz2eEpWXRxr(aYhI#lb@!4o>e4iBI-TO=O@)VJM72is7BuEimWgb z+p@yWH-R}{b-_~M1-g!mYc%`))iz{v__HWEvUjk^DhQP{cwAtFas+lUFW4u2K%Sw^ zSX#9~&RMvDkxjxu<^Ika>;XHre_G7Itn~EUQ8R}BAn^n+;h<_BRV#2|-+CsWFFCt- oJ-+C)-Zq@1Av;Z?xp@9{v8>1HttS?Ct^)bX)78&qol`;+0Af^BZvX%Q literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/64/file-search-line.png b/pcsx2-qt/resources/icons/white/64/file-search-line.png new file mode 100644 index 0000000000000000000000000000000000000000..1a64dc287b762557904a8c1b6d5879b146075254 GIT binary patch literal 457 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I0wfs{c7_5;#^NA%Cx&(BWL^T<&7LlfAr-gY zUg!Kd+d!uMqen-JXGgQrLcTEe!;GaHtQgpjIuFS*&z`b@JQpOV|Unv>^)JEzJd;RpP}9Hl0w(~TPg(!y9co8%@aykN`( zQfhF@Fwvc%Pk6a2Lq<76!p{S98RO5pzwu$1&1ezzh>hz_K10BV*YBPcryQC5p5?)z z*Iu75pD1Je(AOAZuKK_+L*=++^HZPWCfZC7IBW~kghUs{u%3v&Co8)zjCI9FQ?)K* z#pR4VKh~*nd23u|m>29YNAALd)jlGA{3||w{kYi8@5HP5O}D!ruMhQaOjR-Y`!DrN z%Q-2BJ=t$GH8nC4XFUmh^VoA&5NE+>4yFcvZ}(k6%pY2$mF=%xaAjdVaJ+SvphV`1 zf{!~FPGNeK#1yWo;QL~OmBTE7Y1f&Pn&RVFJ%rahFuZet(Sm{f!F|>(Hf6KQI2|t8 vvq}V-En^ftr8T*4PM4d3=uS(kU%!8`m{^_JZXTv60*WF}S3j3^P6Px$Pf0{URCwC$){E(+Fc5(8l#miEVLRBAU{tR0*sTnDBD?La&5I!QYC4uoY8 z+_&=|S@I3zkAOtgj5X=BVum!+u4PZwA6W69A0m8;K`hdF;b^_q>Zi$MH#I;06 zCBM-Fv?MOh@rR9;l?#bh2|xu)iS1;i(gE~5Tdx2h@JeBSKuu=dK)^4bpIDcvHMYPL zk%k*b68_?~PQmG^4LYfug8LuBBZV&o*lz)z?$%jnMC$Mg0BREJdd_&~I5V;?QGd@L zk;K5W4Z4?6W;Px$R7pfZRCwC$*9n#5Fc5^{hF}PWun+{n5CS0(3_%bAAq*@AF$0Hz!{Fb!du~~8 zRCDSRfmU0oda<)nk=6>Y>ZGbjF z8=wt9k2^i|{|VQb5`mmxN{r;}O^G8p2UB7tXZ@@fFq7~zf66Q1@YZXmL;n%zamROG zN3Z_{4E78IZnXO7Sh;t$S8(RuN~gX5&R)Szx&s#OUEbW$g?o!tkhxuMOS1d>_x-dT zFyP|mq!_FgCS1QcD<(w%AM$BgHI&OUj(@mg?Ep^2|DyZ41r#cLGTr?hGp-j0a6Yy~ z1?2L@B))%s2f_jpgmUH~) zz2VMy$U($4P{rkxMS_ZgqR=r;3k9J^4eu2u8u96QSJjr>`x|}AQ(yS`oZBvkC%w*= z=htA!SGjOFnv-RLa7lyT1xDqcXGF@P@0t{+O=m1@ytQbmX@g%x2E$wdAqVvr4ZMnZ zGqf1La4;q`Fdlkd&jF;^1a3VIW0y#9PlC?ZaJwGlHBVPRmvv4FO#o_x BUq}D| literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/64/folder-open-line.png b/pcsx2-qt/resources/icons/white/64/folder-open-line.png new file mode 100644 index 0000000000000000000000000000000000000000..dd4d88a7a72af6fe4d297d5564b86ecf7a13b8e6 GIT binary patch literal 354 zcmV-o0iFJdP)Px$97#k$RCwC$*jrh{KomvcN~nZLhyyC262<`|0SP!@91sVM1L}Y}=r~wB9`5Xq zZ#LJnj)DJS_mXU~EXyLc+@M#+11dsB0tzw0EJ)$h=f(TqXnSHZU7(w2^g3TNI(MEb1>ba0UD-Mz&)<20ARw` ztN;qGx->#!0iAWgMT-WA1+*a#eo++wOk@B1vnQGrKp~n20Inexpa5b4r_F%4iv;Tz~{{EpWc?Ln>~) zy^+p%$Uww3QAJ@XUk8gyh|`<~W-Ud&3cjW(78ZX}&YylZFa5#mJfos>JAHd13u^y9 zW|Vx;%fhv1ssn?SBl`;$*$ql_XP(JBJv;5#7b69|fa)!i*e-Au^DYo}Vgku39^t*L z(jceE#A9%Pftkmk;ey?EDPx$T1iAfRCwC$*k_^YKoCITCjkj7Ar6RybpQ@t91sV@0g(^~tb@G{5(lh%v-WU--2tODRTQX1AZr17To6@UV_ zn&Ru8j3U6(RTzIApxoO7DxOs-ds!_nJNLT2B%oc`U;bDu7MlVW%Yx)7685kF0000< KMNUMnLSTZQX|1dP literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/64/fullscreen-line.png b/pcsx2-qt/resources/icons/white/64/fullscreen-line.png new file mode 100644 index 0000000000000000000000000000000000000000..448280ae9ce47d9b7bf911f3ea84613b8601f610 GIT binary patch literal 171 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I0wfs{c7_5;#^NA%Cx&(BWL^R}sh%#5Ar-gY z-q~a2L1zla&lIDNkzOzMLF&PrRRrg+t=zI-gpr z1#y4tZR7=>vKysIDmXMSFfuU2xm>pw0J0nA?biN$Xws`!|7)jZTQM>*eEYEffb-KW R4ZR>oc)I$ztaD0e0sx$hJevRj literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/64/function-line.png b/pcsx2-qt/resources/icons/white/64/function-line.png new file mode 100644 index 0000000000000000000000000000000000000000..a2b78a543ba0ab8f8bd933d6aff22acd02f35f8d GIT binary patch literal 211 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0J3?w7mbKU|e#^NA%Cx&(BWL^R}q5(c3u0Z-f z2Jkqa%L0^OFA4GsX87+=AYh*_J=5n1P$<&V#WAGfRPx$dr3q=RCwC$*ax}WFcih{3n35$K@6Um)C?j94TFY(#h_&nGst7$Ft8c?yM5mF z_;fqxFPwikgT7fQHrBy2r-M`_*a5VVYV7-?-89iFCn7DG+seB@%VQ@igv4X*%WwXO+!L)JA~$3t*i&TW#=#2Xz&(=fF#;gb z91sBsuRo<^PB^`K7JA)&bA;Z6-X21-IpWOBgow2E3VNmAwq8g`(BcM(t^pdPd2z9s zM*!Izpa$SF*Qj5qmfDO+^P^(P01IrvKM=;xaFY0^@A0SAo?oIk_m|J~*QH2{rg z03y=7yg0cANJub2&gO{B?6s>K%n`#Ycaemg%n1oee@3@l$jH@YU@{=to+26 z6`2TY2B$|Wg!eqwE_@`jfsS`X+lf1ld|*J9MjrHd`?UP4@RV&CEfXFlT3d%ernJ0$ rt}xM4LQf*tUId!`wS3~Ls;cTU`r^e>f7;I=00000NkvXXu0mjf5~s(J literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/64/hard-drive-2-line.png b/pcsx2-qt/resources/icons/white/64/hard-drive-2-line.png new file mode 100644 index 0000000000000000000000000000000000000000..3c890757f8db22d15188f0204e9a3c9ae438bd2d GIT binary patch literal 212 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I0wfs{c7_5;#^NA%Cx&(BWL^R}eV#6kAr-gY z-t=T_HV|QZ=;6R6^oT>{D5H=N`w`FA302k0-&LPJlmEav%`+`toLQLrD{J%&2|Z@6 z^=o3-_H4Vmobivyg3I%^)X6I>S9kpv*ucQZ#KIw<-~eW;cJ- z(-~YU7c(&GnpMetuqjYs{H_0QQ|AoLE#hko)HlC>&7fv{$HF|8_czG3p00i_>zopr E0HW4MLI3~& literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/64/keyboard-line.png b/pcsx2-qt/resources/icons/white/64/keyboard-line.png new file mode 100644 index 0000000000000000000000000000000000000000..a63fff7a0ae1712e203f18eebd2e2a9eee82fd98 GIT binary patch literal 181 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I0wfs{c7_5;#^NA%Cx&(BWL^R}`JOJ0Ar-gY zUfam&pvdDI=w2|T=Rrr_lG#to7p*s#I)VTDl06FRw=ghF={14ur6A{cy85}Sb4q9e0PW{QPyhe` literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/64/layout-grid-line.png b/pcsx2-qt/resources/icons/white/64/layout-grid-line.png new file mode 100644 index 0000000000000000000000000000000000000000..0ccbf3007447a85d66fa1ef5c66ab4780cfabe52 GIT binary patch literal 218 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I0wfs{c7_5;#^NA%Cx&(BWL^R}Q#@T9Ln>~) zz2VNttSI32(BnX$5~sU@BA*f80nWz{&KvH{%YJ{hv{8TWn~n}WO^wW+OWF=sB91<9 zSbR*&o_)r#Q)&DwtZEFu?tg91ET{kC=dbf1n?S(f^2_eb|8f5%4}=>r=>1OmbN#jW z7bCDkfU+WQ_s{tB{p+#tO(*}an)`D8RmaG63qRk!?e9{;*1O8~iJJ1uB_LBgUHx3v IIVCg!06dFSd;kCd literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/64/list-check.png b/pcsx2-qt/resources/icons/white/64/list-check.png new file mode 100644 index 0000000000000000000000000000000000000000..6aa880395ffb796c783404c12ff4874381285d8e GIT binary patch literal 175 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I0wfs{c7_5;#^NA%Cx&(BWL^R}nVv3=Ar-gY zUTb7!H56!hsH-WfqPnkd(z$1h$JzJ}M?L?)tWmyv6C=ahjOSO`V;(JB`0_lb#ia8b z3JncPQI(7gE0%dLX8611Zy~?HgW8tk@&E4&Ff5pP-1FK?OSOiEg-_qxK6t{)P;*z_ XUP;GvtHzSGAcuIm`njxgN@xNAJWfE- literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/64/pause-line.png b/pcsx2-qt/resources/icons/white/64/pause-line.png new file mode 100644 index 0000000000000000000000000000000000000000..40872537bf32be981f391aa5c8c299dfb0dad427 GIT binary patch literal 172 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0J3?w7mbKU|e#^NA%Cx&(BWL^R}LIFM@u0Z-f zDzFP$YXuZ#D+%%oX4r4=-{HW``8U@BMa(>1978H@B}=e63vh68DOz+jHA!&w^!f_O zq$fW-&dmZcS1$y#1sZ%|VCe49U#}MO4rC*Pr>mdKI;Vst0MG6` AbpQYW literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/64/play-line.png b/pcsx2-qt/resources/icons/white/64/play-line.png new file mode 100644 index 0000000000000000000000000000000000000000..2a650f3ff9faf8a3455b3fc780f08a0ef50a7234 GIT binary patch literal 289 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I0wfs{c7_5;#^NA%Cx&(BWL^R}w>@1PLn>~) zy>XE7po4@7_7VqnPj+U%J;D_a=12SeOe^!ZUKye_^;Ed(+r9hW zPs%Z_mS1q*^0d!=t^<5#4AwUoD%qJfH1j>+vum*4(Qxr@V}qTpbRj#F-+k_UAUUyI z!QoybNYN8njtR(|9f|FVXIa-2&kSTqnfLn57G;jz>Z`?LoOW6>7L_wSdB<|%F6V^Z z!V2Z;4)6RLZqIFSeCyY6Z!SZbIOCgjOgCa#HtgncD3@+{mv`ys-M{Upyp{&7{Be_6 We3@cwNPM_4$m^c2elF{r5}E*4Olr3P literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/64/refresh-line.png b/pcsx2-qt/resources/icons/white/64/refresh-line.png new file mode 100644 index 0000000000000000000000000000000000000000..efc2e11724f0e84b3d2c40f27615a188487c046a GIT binary patch literal 661 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I3?%1nZ+ru!7>k44ofy`glX(f`ga`P9xB}__ zGyr{Wd@q1T%a#QB1v3cP$JftapReHGfBwJ2`}+YG4ty}!Q1GB3VZws_r42u&fbKf% z>EaktaqI21@TxZoJgfLI9NrXQ~$1Mk;_G^<4=w5g~^`+^w7S7MV{w$iL*?vAXrr&?- ziEH(Wj=nQI9tc$%&A4nq5+I!{u=^O&3059Drp>i)ZN%C5{SeqPz;ytfi_->RK^*ZVh*^VYpH%Yu?pPH$Zq zt##@JFX!|6l)uM1WmfjR{utZ6W%4cIbnR$YwHN+D;Y-3csvPN99=So=w>;eZ(W5Q9 z(@J*h>b>dR#fM^mg*f)>UgyG&BocWK)yoTm0#LS>3wA<@0Q7C4Vcn)Nv+$ d^3ktk44ofy`glX(f`ga`P9xB}__ zGyr{Wd@q1T%a#QB1v4nv=hv_I??3J2(Vu8u0Cxr;phy$|NrOuM-*M-N}geIRi^Y*ss4}8t0$S9{=DeK zXC2=Ung8WfSx>pMz9@{l>^XPyukURxy?T$F@ zxJRzu|L<4dx3h4lCcmGZDDppg&PN566aTlXefn29_kgJ-^CGpPthOZz%bD|pxj3wf z)M7SW4BGMWf3}cR-=2y`VczW0D;=7m-PSctWpgm=2rY2FVJuQn=FHh5HhrsZ)R4&-y`bXb(vXnd*hW6;He4oB*`G9cN+v5$(%@qFB z{dID1Z+xlkJLBZU-wOiy7NnmycD`PfZ*!jKtCdCVyg5pX)jpkL-tDOP^6$={SD)s1 zTG@&H?K@`cX>0a-Zjkltw2ev13+DPRjxk((k1yig>`7CzPagY~*5Kr1>>eJ)-kRB% zpsIenm6h+fpG>+t&-?45CbpKTLY47CSJ!whV0YKwAU3Pz^IEf;(n?2jOp6N~{qN6D zW360!P&H0-gRB=9&yK5KMXu@xB;0nd+s-4qt@ug#-mDDG3w)w>y{j+YD&4egwM2d0 a<$AyAnmkDdZZ`uXox#)9&t;ucLK6VHW>DS$ literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/64/save-3-line.png b/pcsx2-qt/resources/icons/white/64/save-3-line.png new file mode 100644 index 0000000000000000000000000000000000000000..b906669e748cb13922664c841c33a237ac5d36bd GIT binary patch literal 246 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I0wfs{c7_5;#^NA%Cx&(BWL^R}8$4YcLn>~) zy&TTi>>$9FsPa%jol$*`@{%ViM;vvI9Grj8tn_7S+WZ&ty2?tMG_ObHhNbR1^Ypj+ ziwg@_G83A1t@^~g=uqmSnp{73(OZ&D!UB1(n`)NcmQUrVWx6Mvw7((z(6;gh)i3-O zOOi4iGB_9*4&0vua!kPCTR=w%i2j>%gIB?#e)mgekSK%0%BzWIKE(&Wnig5c5OtQB nVZxQvm*xuZCrp@Px$Nl8RORCwC$*hyjQFc8M^fglKiUtUePszl+r(oX7) zLD1*@i3e)d&_PUs2}-2wK%@ zfH0}Etl~Cl)uGDX%eDXS55PaJJafT+K>$20gKPN!uS_*NzmUWSpkc|E3=mkVO9oi; zR5C!vQd2U(OXO`R_y0g-fY_ral)enllL#HYQ~-tNMh66%g#Pw0ivJ&&55OUzF8P;X trrZD)z_XW-_XLn>~) zJ?F`I*g(MfVq*eFn*c{+kQ%RXd*B&|y`{nBzU7&I#lQ4z-k)w`e7i7u@v|r5HoI6= zmDlIgxX)v(ierEA^JkxO!@T&TvcU{h^B8|tZh6c5MOeYozcG9Nyz2}}^O~6+O#H*m zs4=HrH(|*iX@)8F!U~V>GB6)G$-vBGZ~%pHxa`OBqM9ec!O{JjmDe4+5T6;s`A)p& cf1k-^FyGWadEae&Z;)R+UHx3vIVCg!00n$jPXGV_ literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/64/settings-3-line.png b/pcsx2-qt/resources/icons/white/64/settings-3-line.png new file mode 100644 index 0000000000000000000000000000000000000000..3b06b150cfb45096cee480da47bc337b61155c81 GIT binary patch literal 755 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I3?%1nZ+ru!7>k44ofy`glX(f`ga`P9xB}__ zGyr{Wd@q1T%a#QB1v3cP=hyGw@c+Plg9QNz?;RA@&+qsDaQ;HWgMtb1jAmy(GB7YH zdb&7tdJlw7A=|zmowKVE2M;2X55|0arVM3zl-h@H=i|_tr#VA!{Uj*`ME_k4G&%_ z%0J+!XmC+iv0yrL&uw|mp_-^Q`jdXMelF2p>vrJVEz7&nOiKgbOO!savJE|Mef{FW zplNgFxbn``ESo1*9C7@f)27udHz$4XK7L}yr8!3@|MLI7Drd+qE-yS@^$&Rws++*GO3v3Pgz zf&BDB+q)hXCK5M2-j~~K5{!;qd&ppEz`6M?iBVHc&M7lW`*NsNF)8;L+uVG6$7!eI zwgTU|O|RG2u5`2P{=La=^R@5d4U-Rz`*GUrT>93(UG)}j3%^R~YPuyS`EdXJ c?p({b?54I?`={flfvJkY)78&qol`;+0Q_`}g8%>k literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/64/shut-down-line.png b/pcsx2-qt/resources/icons/white/64/shut-down-line.png new file mode 100644 index 0000000000000000000000000000000000000000..beffa8c0dda75c1a4f0eac379378731eaecd6aaf GIT binary patch literal 640 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I3?%1nZ+ru!7>k44ofy`glX(f`_yzccxB}__ z)B_WyOX~tnlqw1G3uc(G|G9Q7 zkcwMxuZAC5qrk)Z;K7_5+{)Mf=3n_#&UEqiymvMITKCE!Za z(KU!yvIDIRU%zZq3i7i(NIgDdD`$NNdL z`=n>CvyokNXL4SGbZ5b|mvY;LTRcO;O_v=v-<$bjdg0|r>%MDcyq8b!-@+KiAhy`_ T-pqr*xMuKl^>bP0l+XkKDX2y9 literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/64/tv-2-line.png b/pcsx2-qt/resources/icons/white/64/tv-2-line.png new file mode 100644 index 0000000000000000000000000000000000000000..5299e8374b2a5bee71e334312cf69de6929c266f GIT binary patch literal 211 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I0wfs{c7_5;#^NA%Cx&(BWL^R}y`Cij%-mxx&u^A4vyf3U<-0q7Hx5JvI%w&{|Cghamo_wwuqlv2}j5+z?lPpBLmk44ofy`glX(f`cm()_xB}__ z)Bw*bDmDPkk|+uC3uX|gH@N@*{rQCb>l@}PESTVM;lqOs1px;>o4s5Bbi*D`7srr_ zTW_bbPI_a&*&cF)7mD79aYy65xXpL6jZLsV}~rR%c+kERfOe z_KoGrX>QY*3oiU-QrG{nV{f%X5L4cadzlyaZ?2vv88e;R!lOWbQ()W=gJaP@I~(S4 zHt9?&-*tkO@!Kbdq+cQl`&Ru?IPb$^Rhgi6sj1~#RCuE~%VDRP3};>xWhrt+U$72h zno#o8J>>-3y7xY77QH-Gea?8zF^2`W)}DGCa!;SL;)Jozi#Okl+3ALHf(fT4J-v83Vqfq^-5sZmo2M=pK6^K^N$Py# zsmVEw6^|_*&y`;EYxhxshS__gQ_b{6<_P&7j%wI^<8{*-7lmuD)tQ{muo`^HkbP0l+XkKD?CPP literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/icons/white/index.theme b/pcsx2-qt/resources/icons/white/index.theme new file mode 100644 index 0000000000..912dc85720 --- /dev/null +++ b/pcsx2-qt/resources/icons/white/index.theme @@ -0,0 +1,15 @@ +[Icon Theme] +Name=White Icon Theme +Comment=White Icon Theme + +[16] +Size=16 +Type=Fixed + +[32] +Size=32 +Type=Fixed + +[64] +Size=64 +Type=Fixed diff --git a/pcsx2-qt/resources/images/dualshock-2.png b/pcsx2-qt/resources/images/dualshock-2.png new file mode 100644 index 0000000000000000000000000000000000000000..9ae167ba1137d1981828c7011d6aa8c1826296b9 GIT binary patch literal 16319 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%0AK>Ug2)s4k#ZaB`9P9XaRU9KD_D1WAkb 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 literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/images/dualshock-2@2x.png b/pcsx2-qt/resources/images/dualshock-2@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..5e01800c4812df2e5eca163c491e29430689db0f GIT binary patch literal 37601 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 V4}cMAP;vkO002ovPDHLkV1mo1P6z-1 literal 0 HcmV?d00001 diff --git a/pcsx2-qt/resources/resources.qrc b/pcsx2-qt/resources/resources.qrc new file mode 100644 index 0000000000..6e9f2b4b9e --- /dev/null +++ b/pcsx2-qt/resources/resources.qrc @@ -0,0 +1,430 @@ + + + icons/address-book-new-22.png + icons/address-book-new-22@2x.png + icons/AppIcon.png + icons/AppIcon@2x.png + icons/AppIconLarge.png + icons/AppIconLarge@2x.png + icons/applications-system-24.png + icons/applications-system-24@2x.png + icons/black/16/artboard-2-line.png + icons/black/16/book-open-line.png + icons/black/16/brush-line.png + icons/black/16/close-line.png + icons/black/16/dashboard-line.png + icons/black/16/disc-line.png + icons/black/16/door-open-line.png + icons/black/16/download-2-line.png + icons/black/16/dvd-line.png + icons/black/16/file-add-line.png + icons/black/16/file-line.png + icons/black/16/file-list-line.png + icons/black/16/file-reduce-line.png + icons/black/16/file-search-line.png + icons/black/16/file-settings-line.png + icons/black/16/flask-line.png + icons/black/16/folder-add-line.png + icons/black/16/folder-open-line.png + icons/black/16/folder-reduce-line.png + icons/black/16/folder-settings-line.png + icons/black/16/fullscreen-line.png + icons/black/16/function-line.png + icons/black/16/gamepad-line.png + icons/black/16/hard-drive-2-line.png + icons/black/16/keyboard-line.png + icons/black/16/layout-grid-line.png + icons/black/16/list-check.png + icons/black/16/pause-line.png + icons/black/16/play-line.png + icons/black/16/refresh-line.png + icons/black/16/restart-line.png + icons/black/16/save-3-line.png + icons/black/16/screenshot-2-line.png + icons/black/16/sd-card-line.png + icons/black/16/settings-3-line.png + icons/black/16/shut-down-line.png + icons/black/16/tv-2-line.png + icons/black/16/volume-up-line.png + icons/black/16/window-2-line.png + icons/black/24/artboard-2-line.png + icons/black/24/book-open-line.png + icons/black/24/brush-line.png + icons/black/24/close-line.png + icons/black/24/dashboard-line.png + icons/black/24/disc-line.png + icons/black/24/door-open-line.png + icons/black/24/download-2-line.png + icons/black/24/dvd-line.png + icons/black/24/file-add-line.png + icons/black/24/file-line.png + icons/black/24/file-list-line.png + icons/black/24/file-reduce-line.png + icons/black/24/file-search-line.png + icons/black/24/file-settings-line.png + icons/black/24/flask-line.png + icons/black/24/folder-add-line.png + icons/black/24/folder-open-line.png + icons/black/24/folder-reduce-line.png + icons/black/24/folder-settings-line.png + icons/black/24/fullscreen-line.png + icons/black/24/function-line.png + icons/black/24/gamepad-line.png + icons/black/24/hard-drive-2-line.png + icons/black/24/keyboard-line.png + icons/black/24/layout-grid-line.png + icons/black/24/list-check.png + icons/black/24/pause-line.png + icons/black/24/play-line.png + icons/black/24/refresh-line.png + icons/black/24/restart-line.png + icons/black/24/save-3-line.png + icons/black/24/screenshot-2-line.png + icons/black/24/sd-card-line.png + icons/black/24/settings-3-line.png + icons/black/24/shut-down-line.png + icons/black/24/volume-up-line.png + icons/black/24/window-2-line.png + icons/black/32/artboard-2-line.png + icons/black/32/book-open-line.png + icons/black/32/brush-line.png + icons/black/32/close-line.png + icons/black/32/dashboard-line.png + icons/black/32/disc-line.png + icons/black/32/door-open-line.png + icons/black/32/download-2-line.png + icons/black/32/dvd-line.png + icons/black/32/file-add-line.png + icons/black/32/file-line.png + icons/black/32/file-list-line.png + icons/black/32/file-reduce-line.png + icons/black/32/file-search-line.png + icons/black/32/file-settings-line.png + icons/black/32/flask-line.png + icons/black/32/folder-add-line.png + icons/black/32/folder-open-line.png + icons/black/32/folder-reduce-line.png + icons/black/32/folder-settings-line.png + icons/black/32/fullscreen-line.png + icons/black/32/function-line.png + icons/black/32/gamepad-line.png + icons/black/32/hard-drive-2-line.png + icons/black/32/keyboard-line.png + icons/black/32/layout-grid-line.png + icons/black/32/list-check.png + icons/black/32/pause-line.png + icons/black/32/play-line.png + icons/black/32/refresh-line.png + icons/black/32/restart-line.png + icons/black/32/save-3-line.png + icons/black/32/screenshot-2-line.png + icons/black/32/sd-card-line.png + icons/black/32/settings-3-line.png + icons/black/32/shut-down-line.png + icons/black/32/tv-2-line.png + icons/black/32/volume-up-line.png + icons/black/32/window-2-line.png + icons/black/48/artboard-2-line.png + icons/black/48/book-open-line.png + icons/black/48/brush-line.png + icons/black/48/close-line.png + icons/black/48/dashboard-line.png + icons/black/48/disc-line.png + icons/black/48/door-open-line.png + icons/black/48/download-2-line.png + icons/black/48/dvd-line.png + icons/black/48/file-add-line.png + icons/black/48/file-line.png + icons/black/48/file-list-line.png + icons/black/48/file-reduce-line.png + icons/black/48/file-search-line.png + icons/black/48/file-settings-line.png + icons/black/48/flask-line.png + icons/black/48/folder-add-line.png + icons/black/48/folder-open-line.png + icons/black/48/folder-reduce-line.png + icons/black/48/folder-settings-line.png + icons/black/48/fullscreen-line.png + icons/black/48/function-line.png + icons/black/48/gamepad-line.png + icons/black/48/hard-drive-2-line.png + icons/black/48/keyboard-line.png + icons/black/48/layout-grid-line.png + icons/black/48/list-check.png + icons/black/48/pause-line.png + icons/black/48/play-line.png + icons/black/48/refresh-line.png + icons/black/48/restart-line.png + icons/black/48/save-3-line.png + icons/black/48/screenshot-2-line.png + icons/black/48/sd-card-line.png + icons/black/48/settings-3-line.png + icons/black/48/shut-down-line.png + icons/black/48/volume-up-line.png + icons/black/48/window-2-line.png + icons/black/64/artboard-2-line.png + icons/black/64/book-open-line.png + icons/black/64/brush-line.png + icons/black/64/close-line.png + icons/black/64/dashboard-line.png + icons/black/64/disc-line.png + icons/black/64/door-open-line.png + icons/black/64/download-2-line.png + icons/black/64/dvd-line.png + icons/black/64/file-add-line.png + icons/black/64/file-line.png + icons/black/64/file-list-line.png + icons/black/64/file-reduce-line.png + icons/black/64/file-search-line.png + icons/black/64/file-settings-line.png + icons/black/64/flask-line.png + icons/black/64/folder-add-line.png + icons/black/64/folder-open-line.png + icons/black/64/folder-reduce-line.png + icons/black/64/folder-settings-line.png + icons/black/64/fullscreen-line.png + icons/black/64/function-line.png + icons/black/64/gamepad-line.png + icons/black/64/hard-drive-2-line.png + icons/black/64/keyboard-line.png + icons/black/64/layout-grid-line.png + icons/black/64/list-check.png + icons/black/64/pause-line.png + icons/black/64/play-line.png + icons/black/64/refresh-line.png + icons/black/64/restart-line.png + icons/black/64/save-3-line.png + icons/black/64/screenshot-2-line.png + icons/black/64/sd-card-line.png + icons/black/64/settings-3-line.png + icons/black/64/shut-down-line.png + icons/black/64/tv-2-line.png + icons/black/64/volume-up-line.png + icons/black/64/window-2-line.png + icons/black/index.theme + icons/discord.png + icons/flag-eu.png + icons/flag-eu.svg + icons/flag-eu@2x.png + icons/flag-jp.png + icons/flag-jp.svg + icons/flag-jp@2x.png + icons/flag-other.png + icons/flag-other@2x.png + icons/flag-us.png + icons/flag-us.svg + icons/flag-us@2x.png + icons/logo.png + icons/media-optical-24.png + icons/media-optical-24@2x.png + icons/media-optical-gear-24.png + icons/media-optical-gear-24@2x.png + icons/media-optical.png + icons/media-optical@2x.png + icons/QT.png + icons/star-0.png + icons/star-1.png + icons/star-2.png + icons/star-3.png + icons/star-4.png + icons/star-5.png + icons/white/16/artboard-2-line.png + icons/white/16/book-open-line.png + icons/white/16/brush-line.png + icons/white/16/close-line.png + icons/white/16/dashboard-line.png + icons/white/16/disc-line.png + icons/white/16/door-open-line.png + icons/white/16/download-2-line.png + icons/white/16/dvd-line.png + icons/white/16/file-add-line.png + icons/white/16/file-line.png + icons/white/16/file-list-line.png + icons/white/16/file-reduce-line.png + icons/white/16/file-search-line.png + icons/white/16/file-settings-line.png + icons/white/16/flask-line.png + icons/white/16/folder-add-line.png + icons/white/16/folder-open-line.png + icons/white/16/folder-reduce-line.png + icons/white/16/folder-settings-line.png + icons/white/16/fullscreen-line.png + icons/white/16/function-line.png + icons/white/16/gamepad-line.png + icons/white/16/hard-drive-2-line.png + icons/white/16/keyboard-line.png + icons/white/16/layout-grid-line.png + icons/white/16/list-check.png + icons/white/16/pause-line.png + icons/white/16/play-line.png + icons/white/16/refresh-line.png + icons/white/16/restart-line.png + icons/white/16/save-3-line.png + icons/white/16/screenshot-2-line.png + icons/white/16/sd-card-line.png + icons/white/16/settings-3-line.png + icons/white/16/shut-down-line.png + icons/white/16/tv-2-line.png + icons/white/16/volume-up-line.png + icons/white/16/window-2-line.png + icons/white/24/artboard-2-line.png + icons/white/24/book-open-line.png + icons/white/24/brush-line.png + icons/white/24/close-line.png + icons/white/24/dashboard-line.png + icons/white/24/disc-line.png + icons/white/24/door-open-line.png + icons/white/24/download-2-line.png + icons/white/24/dvd-line.png + icons/white/24/file-add-line.png + icons/white/24/file-line.png + icons/white/24/file-list-line.png + icons/white/24/file-reduce-line.png + icons/white/24/file-search-line.png + icons/white/24/file-settings-line.png + icons/white/24/flask-line.png + icons/white/24/folder-add-line.png + icons/white/24/folder-open-line.png + icons/white/24/folder-reduce-line.png + icons/white/24/folder-settings-line.png + icons/white/24/fullscreen-line.png + icons/white/24/function-line.png + icons/white/24/gamepad-line.png + icons/white/24/hard-drive-2-line.png + icons/white/24/keyboard-line.png + icons/white/24/layout-grid-line.png + icons/white/24/list-check.png + icons/white/24/pause-line.png + icons/white/24/play-line.png + icons/white/24/refresh-line.png + icons/white/24/restart-line.png + icons/white/24/save-3-line.png + icons/white/24/screenshot-2-line.png + icons/white/24/sd-card-line.png + icons/white/24/settings-3-line.png + icons/white/24/shut-down-line.png + icons/white/24/tv-2-line.png + icons/white/24/volume-up-line.png + icons/white/24/window-2-line.png + icons/white/32/artboard-2-line.png + icons/white/32/book-open-line.png + icons/white/32/brush-line.png + icons/white/32/close-line.png + icons/white/32/dashboard-line.png + icons/white/32/disc-line.png + icons/white/32/door-open-line.png + icons/white/32/download-2-line.png + icons/white/32/dvd-line.png + icons/white/32/file-add-line.png + icons/white/32/file-line.png + icons/white/32/file-list-line.png + icons/white/32/file-reduce-line.png + icons/white/32/file-search-line.png + icons/white/32/file-settings-line.png + icons/white/32/flask-line.png + icons/white/32/folder-add-line.png + icons/white/32/folder-open-line.png + icons/white/32/folder-reduce-line.png + icons/white/32/folder-settings-line.png + icons/white/32/fullscreen-line.png + icons/white/32/function-line.png + icons/white/32/gamepad-line.png + icons/white/32/hard-drive-2-line.png + icons/white/32/keyboard-line.png + icons/white/32/layout-grid-line.png + icons/white/32/list-check.png + icons/white/32/pause-line.png + icons/white/32/play-line.png + icons/white/32/refresh-line.png + icons/white/32/restart-line.png + icons/white/32/save-3-line.png + icons/white/32/screenshot-2-line.png + icons/white/32/sd-card-line.png + icons/white/32/settings-3-line.png + icons/white/32/shut-down-line.png + icons/white/32/tv-2-line.png + icons/white/32/volume-up-line.png + icons/white/32/window-2-line.png + icons/white/48/artboard-2-line.png + icons/white/48/book-open-line.png + icons/white/48/brush-line.png + icons/white/48/close-line.png + icons/white/48/dashboard-line.png + icons/white/48/disc-line.png + icons/white/48/door-open-line.png + icons/white/48/download-2-line.png + icons/white/48/dvd-line.png + icons/white/48/file-add-line.png + icons/white/48/file-line.png + icons/white/48/file-list-line.png + icons/white/48/file-reduce-line.png + icons/white/48/file-search-line.png + icons/white/48/file-settings-line.png + icons/white/48/flask-line.png + icons/white/48/folder-add-line.png + icons/white/48/folder-open-line.png + icons/white/48/folder-reduce-line.png + icons/white/48/folder-settings-line.png + icons/white/48/fullscreen-line.png + icons/white/48/function-line.png + icons/white/48/gamepad-line.png + icons/white/48/hard-drive-2-line.png + icons/white/48/keyboard-line.png + icons/white/48/layout-grid-line.png + icons/white/48/list-check.png + icons/white/48/pause-line.png + icons/white/48/play-line.png + icons/white/48/refresh-line.png + icons/white/48/restart-line.png + icons/white/48/save-3-line.png + icons/white/48/screenshot-2-line.png + icons/white/48/sd-card-line.png + icons/white/48/settings-3-line.png + icons/white/48/shut-down-line.png + icons/white/48/tv-2-line.png + icons/white/48/volume-up-line.png + icons/white/48/window-2-line.png + icons/white/64/artboard-2-line.png + icons/white/64/book-open-line.png + icons/white/64/brush-line.png + icons/white/64/close-line.png + icons/white/64/dashboard-line.png + icons/white/64/disc-line.png + icons/white/64/door-open-line.png + icons/white/64/download-2-line.png + icons/white/64/dvd-line.png + icons/white/64/file-add-line.png + icons/white/64/file-line.png + icons/white/64/file-list-line.png + icons/white/64/file-reduce-line.png + icons/white/64/file-search-line.png + icons/white/64/file-settings-line.png + icons/white/64/flask-line.png + icons/white/64/folder-add-line.png + icons/white/64/folder-open-line.png + icons/white/64/folder-reduce-line.png + icons/white/64/folder-settings-line.png + icons/white/64/fullscreen-line.png + icons/white/64/function-line.png + icons/white/64/gamepad-line.png + icons/white/64/hard-drive-2-line.png + icons/white/64/keyboard-line.png + icons/white/64/layout-grid-line.png + icons/white/64/list-check.png + icons/white/64/pause-line.png + icons/white/64/play-line.png + icons/white/64/refresh-line.png + icons/white/64/restart-line.png + icons/white/64/save-3-line.png + icons/white/64/screenshot-2-line.png + icons/white/64/sd-card-line.png + icons/white/64/settings-3-line.png + icons/white/64/shut-down-line.png + icons/white/64/tv-2-line.png + icons/white/64/volume-up-line.png + icons/white/64/window-2-line.png + icons/white/index.theme + images/dualshock-2.png + images/dualshock-2@2x.png + +