diff options
-rw-r--r-- | .github/workflows/build.yml | 232 | ||||
-rw-r--r-- | CMakeLists.txt | 54 | ||||
-rw-r--r-- | launcher/Application.cpp | 53 | ||||
-rw-r--r-- | launcher/CMakeLists.txt | 6 | ||||
-rwxr-xr-x | launcher/Launcher.in | 2 | ||||
-rw-r--r-- | launcher/minecraft/MinecraftInstance.cpp | 4 | ||||
-rw-r--r-- | launcher/modplatform/ModAPI.h | 17 | ||||
-rw-r--r-- | launcher/modplatform/flame/FlameAPI.h | 6 | ||||
-rw-r--r-- | launcher/modplatform/modrinth/ModrinthAPI.h | 15 | ||||
-rw-r--r-- | launcher/ui/dialogs/ModDownloadDialog.cpp | 6 | ||||
-rw-r--r-- | launcher/ui/pages/modplatform/ModModel.cpp | 27 | ||||
-rw-r--r-- | launcher/ui/pages/modplatform/ModModel.h | 6 | ||||
-rw-r--r-- | launcher/ui/pages/modplatform/ModPage.cpp | 58 | ||||
-rw-r--r-- | launcher/ui/pages/modplatform/ModPage.h | 9 | ||||
-rw-r--r-- | launcher/ui/pages/modplatform/ModPage.ui | 24 | ||||
-rw-r--r-- | launcher/ui/widgets/ModFilterWidget.cpp | 109 | ||||
-rw-r--r-- | launcher/ui/widgets/ModFilterWidget.h | 69 | ||||
-rw-r--r-- | launcher/ui/widgets/ModFilterWidget.ui | 54 | ||||
-rw-r--r-- | program_info/CMakeLists.txt | 2 | ||||
-rw-r--r-- | program_info/portable.txt | 4 |
20 files changed, 532 insertions, 225 deletions
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0123c99a..4b1195ee 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,14 +17,6 @@ jobs: - os: ubuntu-20.04 - - os: ubuntu-20.04 - portable: true - - - os: ubuntu-20.04 - qt_version: 5.15.2 - qt_host: linux - app_image: true - - os: windows-2022 name: "Windows-i686" msystem: mingw32 @@ -33,16 +25,6 @@ jobs: name: "Windows-x86_64" msystem: mingw64 - - os: windows-2022 - name: "Windows-i686-portable" - msystem: mingw32 - portable: true - - - os: windows-2022 - name: "Windows-x86_64-portable" - msystem: mingw64 - portable: true - - os: macos-11 qt_version: 5.12.12 qt_host: mac @@ -53,9 +35,14 @@ jobs: env: MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx_deployment_target }} INSTALL_DIR: "install" + INSTALL_PORTABLE_DIR: "install-portable" + INSTALL_APPIMAGE_DIR: "install-appdir" BUILD_DIR: "build" steps: + ## + # PREPARE + ## - name: Checkout uses: actions/checkout@v3 with: @@ -87,16 +74,16 @@ jobs: distribution: 'temurin' java-version: '17' - - name: Cache Qt - if: runner.os != 'Windows' + - name: Cache Qt (macOS) + if: runner.os == 'macOS' id: cache-qt uses: actions/cache@v3 with: path: "${{ github.workspace }}/Qt/" key: ${{ runner.os }}-${{ matrix.qt_version }}-${{ matrix.qt_arch }}-qt_cache - - name: Install Qt - if: runner.os != 'Linux' && runner.os != 'Windows' || matrix.app_image == true + - name: Install Qt (macOS) + if: runner.os == 'macOS' uses: jurplel/install-qt-action@v2 with: version: ${{ matrix.qt_version }} @@ -105,8 +92,8 @@ jobs: cached: ${{ steps.cache-qt.outputs.cache-hit }} dir: "${{ github.workspace }}/Qt/" - - name: Install System Qt on Linux - if: runner.os == 'Linux' && matrix.app_image != true + - name: Install Qt (Linux) + if: runner.os == 'Linux' run: | sudo apt-get -y update sudo apt-get -y install qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5 @@ -115,174 +102,169 @@ jobs: if: runner.os != 'Windows' uses: urkle/action-get-ninja@v1 - - name: Download linuxdeploy family for AppImage on Linux - if: matrix.app_image == true + - name: Prepare AppImage (Linux) + if: runner.os == 'Linux' run: | wget "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage" wget "https://github.com/linuxdeploy/linuxdeploy-plugin-appimage/releases/download/continuous/linuxdeploy-plugin-appimage-x86_64.AppImage" wget "https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage" - - name: Download JREs for AppImage on Linux - if: matrix.app_image == true - shell: bash - run: | ${{ github.workspace }}/.github/scripts/prepare_JREs.sh - - name: Configure CMake - if: runner.os != 'Linux' && runner.os != 'Windows' - run: | - cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -G Ninja + ## + # CONFIGURE + ## - - name: Configure CMake on Windows - if: runner.os == 'Windows' && matrix.portable != true - shell: msys2 {0} + - name: Configure CMake (macOS) + if: runner.os == 'macOS' run: | - cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_PORTABLE=OFF -G Ninja + cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -G Ninja - - name: Configure CMake on Windows portable - if: runner.os == 'Windows' && matrix.portable == true + - name: Configure CMake (Windows) + if: runner.os == 'Windows' shell: msys2 {0} run: | - cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -G Ninja + cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -G Ninja - - name: Configure CMake on Linux - if: runner.os == 'Linux' && matrix.portable != true + - name: Configure CMake (Linux) + if: runner.os == 'Linux' run: | - cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DLauncher_PORTABLE=OFF -DENABLE_LTO=ON -G Ninja + cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -G Ninja - - name: Configure CMake on Linux Portable - if: runner.os == 'Linux' && matrix.portable == true - run: | - cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -G Ninja + ## + # BUILD + ## - name: Build if: runner.os != 'Windows' run: | cmake --build ${{ env.BUILD_DIR }} - - name: Build on Windows + - name: Build (Windows) if: runner.os == 'Windows' shell: msys2 {0} run: | cmake --build ${{ env.BUILD_DIR }} - - name: Install - if: runner.os != 'Linux' && runner.os != 'Windows' + ## + # PACKAGE BUILDS + ## + + - name: Package (macOS) + if: runner.os == 'macOS' run: | cmake --install ${{ env.BUILD_DIR }} - - name: Install on Windows + cd ${{ env.INSTALL_DIR }} + chmod +x "PolyMC.app/Contents/MacOS/polymc" + tar -czf ../PolyMC.tar.gz * + + - name: Package (Windows) if: runner.os == 'Windows' shell: msys2 {0} run: | cmake --install ${{ env.BUILD_DIR }} - - name: Install on Linux - if: runner.os == 'Linux' && matrix.portable != true + cd ${{ env.INSTALL_DIR }} + if [ "${{ matrix.msystem }}" == "mingw32" ]; then + cp /mingw32/bin/libcrypto-1_1.dll /mingw32/bin/libssl-1_1.dll ./ + elif [ "${{ matrix.msystem }}" == "mingw64" ]; then + cp /mingw64/bin/libcrypto-1_1-x64.dll /mingw64/bin/libssl-1_1-x64.dll ./ + fi + + - name: Package (Windows, portable) + if: runner.os == 'Windows' + shell: msys2 {0} run: | - DESTDIR=${{ env.INSTALL_DIR }} cmake --install ${{ env.BUILD_DIR }} + cp -r ${{ env.INSTALL_DIR }} ${{ env.INSTALL_PORTABLE_DIR }} # cmake install on Windows is slow, let's just copy instead + cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }} --component portable - - name: Install on Linux portable - if: runner.os == 'Linux' && matrix.portable == true + - name: Package (Linux) + if: runner.os == 'Linux' run: | - cmake --install ${{ env.BUILD_DIR }} + cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_DIR }} + + cd ${{ env.INSTALL_DIR }} + tar -czf ../PolyMC.tar.gz * + + - name: Package (Linux, portable) + if: runner.os == 'Linux' + run: | + cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }} + cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }} --component portable + + cd ${{ env.INSTALL_PORTABLE_DIR }} + tar -czf ../PolyMC-portable.tar.gz * - - name: Bundle AppImage - if: matrix.app_image == true + - name: Package AppImage (Linux) + if: runner.os == 'Linux' shell: bash run: | + cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_APPIMAGE_DIR }}/usr + export OUTPUT="PolyMC-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage" chmod +x linuxdeploy-*.AppImage - mkdir -p ${{ env.INSTALL_DIR }}/usr/lib/jvm/java-{8,17}-openjdk - - cp -r ${{ github.workspace }}/JREs/jre8/* ${{ env.INSTALL_DIR }}/usr/lib/jvm/java-8-openjdk + mkdir -p ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-{8,17}-openjdk - cp -r ${{ github.workspace }}/JREs/jre17/* ${{ env.INSTALL_DIR }}/usr/lib/jvm/java-17-openjdk + cp -r ${{ github.workspace }}/JREs/jre8/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-8-openjdk - export LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_DIR }}/usr/lib" - LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_DIR }}/usr/lib/jvm/java-8-openjdk/lib/amd64/server" - LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_DIR }}/usr/lib/jvm/java-8-openjdk/lib/amd64" - LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_DIR }}/usr/lib/jvm/java-17-openjdk/lib/server" - LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_DIR }}/usr/lib/jvm/java-17-openjdk/lib" + cp -r ${{ github.workspace }}/JREs/jre17/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-17-openjdk - ./linuxdeploy-x86_64.AppImage --appdir ${{ env.INSTALL_DIR }} --output appimage --plugin qt -i ${{ env.INSTALL_DIR }}/usr/share/icons/hicolor/scalable/apps/org.polymc.PolyMC.svg + LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib" + LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-8-openjdk/lib/amd64/server" + LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-8-openjdk/lib/amd64" + LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-17-openjdk/lib/server" + LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-17-openjdk/lib" + export LD_LIBRARY_PATH - - name: Run macdeployqt - if: runner.os == 'macOS' - run: | - cd ${{ env.INSTALL_DIR }} - macdeployqt "PolyMC.app" -executable="PolyMC.app/Contents/MacOS/polymc" -always-overwrite + ./linuxdeploy-x86_64.AppImage --appdir ${{ env.INSTALL_APPIMAGE_DIR }} --output appimage --plugin qt -i ${{ env.INSTALL_APPIMAGE_DIR }}/usr/share/icons/hicolor/scalable/apps/org.polymc.PolyMC.svg - - name: chmod binary on macOS - if: runner.os == 'macOS' - run: | - chmod +x "${{ github.workspace }}/${{ env.INSTALL_DIR }}/PolyMC.app/Contents/MacOS/polymc" + ## + # UPLOAD BUILDS + ## - - name: tar bundle on macOS + - name: Upload binary tarball (macOS) if: runner.os == 'macOS' - run: | - cd ${{ env.INSTALL_DIR }} - tar -czf ../PolyMC.tar.gz * + uses: actions/upload-artifact@v3 + with: + name: PolyMC-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }} + path: PolyMC.tar.gz - - name: tar on Linux - if: runner.os == 'Linux' && matrix.app_image != true && matrix.portable != true - run: | - cd ${{ env.INSTALL_DIR }} - tar -czf ../PolyMC.tar.gz * + - name: Upload binary zip (Windows) + if: runner.os == 'Windows' + uses: actions/upload-artifact@v3 + with: + name: PolyMC-${{ matrix.name }}-${{ env.VERSION }}-${{ inputs.build_type }} + path: ${{ env.INSTALL_DIR }}/** - - name: tar on Linux portable - if: runner.os == 'Linux' && matrix.app_image != true && matrix.portable == true - run: | - cd ${{ env.INSTALL_DIR }} - tar -czf ../PolyMC-portable.tar.gz * + - name: Upload binary zip (Windows, portable) + if: runner.os == 'Windows' + uses: actions/upload-artifact@v3 + with: + name: PolyMC-${{ matrix.name }}-Portable-${{ env.VERSION }}-${{ inputs.build_type }} + path: ${{ env.INSTALL_PORTABLE_DIR }}/** - - name: Upload Linux tar.gz - if: runner.os == 'Linux' && matrix.app_image != true && matrix.portable != true + - name: Upload binary tarball (Linux) + if: runner.os == 'Linux' uses: actions/upload-artifact@v3 with: name: PolyMC-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }} path: PolyMC.tar.gz - - name: Upload Linux Portable tar.gz - if: runner.os == 'Linux' && matrix.app_image != true && matrix.portable == true + - name: Upload binary tarball (Linux, portable) + if: runner.os == 'Linux' uses: actions/upload-artifact@v3 with: name: PolyMC-${{ runner.os }}-Portable-${{ env.VERSION }}-${{ inputs.build_type }} path: PolyMC-portable.tar.gz - - name: Upload AppImage for Linux - if: matrix.app_image == true + - name: Upload AppImage (Linux) + if: runner.os == 'Linux' uses: actions/upload-artifact@v3 with: name: PolyMC-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage path: PolyMC-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage - - name: Copy OpenSSL libs on Windows x86 - if: runner.os == 'Windows' && matrix.msystem == 'mingw32' - shell: msys2 {0} - run: | - cp /mingw32/bin/libcrypto-1_1.dll ${{ env.INSTALL_DIR }}/ - cp /mingw32/bin/libssl-1_1.dll ${{ env.INSTALL_DIR }}/ - - name: Copy OpenSSL libs on Windows x86_64 - if: runner.os == 'Windows' && matrix.msystem == 'mingw64' - shell: msys2 {0} - run: | - cp /mingw64/bin/libcrypto-1_1-x64.dll ${{ env.INSTALL_DIR }}/ - cp /mingw64/bin/libssl-1_1-x64.dll ${{ env.INSTALL_DIR }}/ - - - name: Upload package for Windows - if: runner.os == 'Windows' - uses: actions/upload-artifact@v3 - with: - name: PolyMC-${{ matrix.name }}-${{ env.VERSION }}-${{ inputs.build_type }} - path: ${{ env.INSTALL_DIR }}/** - - - name: Upload package for macOS - if: runner.os == 'macOS' - uses: actions/upload-artifact@v3 - with: - name: PolyMC-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }} - path: PolyMC.tar.gz diff --git a/CMakeLists.txt b/CMakeLists.txt index 9530ff10..722b68da 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -164,12 +164,9 @@ add_subdirectory(program_info) ####################################### Install layout ####################################### -# Install the build results according to platform -set(Launcher_PORTABLE 1 CACHE BOOL "The type of installation (Portable or System)") - -if (Launcher_PORTABLE) - # launcher/Application.cpp will use this value - set(Launcher_APP_BINARY_DEFS "-DLAUNCHER_PORTABLE") +if(NOT (UNIX AND APPLE)) + # Install "portable.txt" if selected component is "portable" + install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_Portable_File}" DESTINATION "." COMPONENT portable EXCLUDE_FROM_ALL) endif() if(UNIX AND APPLE) @@ -179,8 +176,6 @@ if(UNIX AND APPLE) set(RESOURCES_DEST_DIR "${Launcher_Name}.app/Contents/Resources") set(JARS_DEST_DIR "${Launcher_Name}.app/Contents/MacOS/jars") - set(BUNDLE_DEST_DIR ".") - # Apps to bundle set(APPS "\${CMAKE_INSTALL_PREFIX}/${Launcher_Name}.app") @@ -205,30 +200,12 @@ if(UNIX AND APPLE) elseif(UNIX) set(BINARY_DEST_DIR "bin") - if(Launcher_PORTABLE) - set(LIBRARY_DEST_DIR "bin") - set(BUNDLE_DEST_DIR ".") - set(JARS_DEST_DIR "bin/jars") - - # Install basic runner script - configure_file(launcher/Launcher.in "${CMAKE_CURRENT_BINARY_DIR}/LauncherScript" @ONLY) - install(PROGRAMS "${CMAKE_CURRENT_BINARY_DIR}/LauncherScript" DESTINATION ${BUNDLE_DEST_DIR} RENAME ${Launcher_Name}) - else() - set(LIBRARY_DEST_DIR "lib${LIB_SUFFIX}") - set(JARS_DEST_DIR "share/jars") - set(LAUNCHER_DESKTOP_DEST_DIR "share/applications" CACHE STRING "Path to the desktop file directory") - set(LAUNCHER_METAINFO_DEST_DIR "share/metainfo" CACHE STRING "Path to the metainfo directory") - set(LAUNCHER_ICON_DEST_DIR "share/icons/hicolor/scalable/apps" CACHE STRING "Path to the scalable icon directory") - set(LAUNCHER_MAN_DEST_DIR "share/man/man6" CACHE STRING "Path to the man page directory") - - # jars path is determined on runtime, relative to "Application root path", generally /usr for Launcher_PORTABLE=0 - set(Launcher_APP_BINARY_DEFS "-DLAUNCHER_JARS_LOCATION=${JARS_DEST_DIR}") - - install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_Desktop} DESTINATION ${LAUNCHER_DESKTOP_DEST_DIR}) - install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_MetaInfo} DESTINATION ${LAUNCHER_METAINFO_DEST_DIR}) - install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_SVG} DESTINATION ${LAUNCHER_ICON_DEST_DIR}) - install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_ManPage} DESTINATION ${LAUNCHER_MAN_DEST_DIR} RENAME "${Launcher_APP_BINARY_NAME}.6") - endif() + set(LIBRARY_DEST_DIR "lib${LIB_SUFFIX}") + set(JARS_DEST_DIR "share/jars") + set(LAUNCHER_DESKTOP_DEST_DIR "share/applications" CACHE STRING "Path to the desktop file directory") + set(LAUNCHER_METAINFO_DEST_DIR "share/metainfo" CACHE STRING "Path to the metainfo directory") + set(LAUNCHER_ICON_DEST_DIR "share/icons/hicolor/scalable/apps" CACHE STRING "Path to the scalable icon directory") + set(LAUNCHER_MAN_DEST_DIR "share/man/man6" CACHE STRING "Path to the man page directory") # install as bundle with no dependencies included set(INSTALL_BUNDLE "nodeps") @@ -236,11 +213,22 @@ elseif(UNIX) # Set RPATH SET(Launcher_BINARY_RPATH "$ORIGIN/") + # jars path is determined on runtime, relative to "Application root path", generally /usr or the root of the portable bundle + set(Launcher_APP_BINARY_DEFS "-DLAUNCHER_JARS_LOCATION=${JARS_DEST_DIR}") + + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_Desktop} DESTINATION ${LAUNCHER_DESKTOP_DEST_DIR}) + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_MetaInfo} DESTINATION ${LAUNCHER_METAINFO_DEST_DIR}) + install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_SVG} DESTINATION ${LAUNCHER_ICON_DEST_DIR}) + install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_ManPage} DESTINATION ${LAUNCHER_MAN_DEST_DIR} RENAME "${Launcher_APP_BINARY_NAME}.6") + + # Install basic runner script if component "portable" is selected + configure_file(launcher/Launcher.in "${CMAKE_CURRENT_BINARY_DIR}/LauncherScript" @ONLY) + install(PROGRAMS "${CMAKE_CURRENT_BINARY_DIR}/LauncherScript" DESTINATION "." RENAME ${Launcher_Name} COMPONENT portable EXCLUDE_FROM_ALL) + elseif(WIN32) set(BINARY_DEST_DIR ".") set(LIBRARY_DEST_DIR ".") set(PLUGIN_DEST_DIR ".") - set(BUNDLE_DEST_DIR ".") set(RESOURCES_DEST_DIR ".") set(JARS_DEST_DIR "jars") diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 9cca534c..6e934fa4 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -316,6 +316,26 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) QString origcwdPath = QDir::currentPath(); QString binPath = applicationDirPath(); + + { + // Root path is used for updates and portable data +#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) + QDir foo(FS::PathCombine(binPath, "..")); // typically portable-root or /usr + m_rootPath = foo.absolutePath(); +#elif defined(Q_OS_WIN32) + m_rootPath = binPath; +#elif defined(Q_OS_MAC) + QDir foo(FS::PathCombine(binPath, "../..")); + m_rootPath = foo.absolutePath(); + // on macOS, touch the root to force Finder to reload the .app metadata (and fix any icon change issues) + FS::updateTimestamp(m_rootPath); +#endif + +#ifdef LAUNCHER_JARS_LOCATION + m_jarsPath = TOSTRING(LAUNCHER_JARS_LOCATION); +#endif + } + QString adjustedBy; QString dataPath; // change folder @@ -324,15 +344,14 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) { // the dir param. it makes multimc data path point to whatever the user specified // on command line - adjustedBy += "Command line " + dirParam; + adjustedBy = "Command line"; dataPath = dirParam; } else { -#if !defined(LAUNCHER_PORTABLE) || defined(Q_OS_MAC) QDir foo(FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), "..")); dataPath = foo.absolutePath(); - adjustedBy += dataPath; + adjustedBy = "Persistent data path"; #ifdef Q_OS_LINUX // TODO: this should be removed in a future version @@ -340,12 +359,15 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) QDir bar(FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation), "polymc")); if (bar.exists()) { dataPath = bar.absolutePath(); - adjustedBy += "Legacy data path " + dataPath; + adjustedBy = "Legacy data path"; } #endif -#else - dataPath = applicationDirPath(); - adjustedBy += "Fallback to binary path " + dataPath; + +#ifndef Q_OS_MACOS + if (QFile::exists(FS::PathCombine(m_rootPath, "portable.txt"))) { + dataPath = m_rootPath; + adjustedBy = "Portable data path"; + } #endif } @@ -535,24 +557,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) qDebug() << "<> Log initialized."; } - // Set up paths { - // Root path is used for updates. -#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) - QDir foo(FS::PathCombine(binPath, "..")); - m_rootPath = foo.absolutePath(); -#elif defined(Q_OS_WIN32) - m_rootPath = binPath; -#elif defined(Q_OS_MAC) - QDir foo(FS::PathCombine(binPath, "../..")); - m_rootPath = foo.absolutePath(); - // on macOS, touch the root to force Finder to reload the .app metadata (and fix any icon change issues) - FS::updateTimestamp(m_rootPath); -#endif - -#ifdef LAUNCHER_JARS_LOCATION - m_jarsPath = TOSTRING(LAUNCHER_JARS_LOCATION); -#endif qDebug() << BuildConfig.LAUNCHER_DISPLAYNAME << ", (c) 2013-2021 " << BuildConfig.LAUNCHER_COPYRIGHT; qDebug() << "Version : " << BuildConfig.printableVersionString(); diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 42348792..6ed86726 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -825,7 +825,6 @@ SET(LAUNCHER_SOURCES ui/dialogs/ModDownloadDialog.cpp ui/dialogs/ModDownloadDialog.h - # GUI - widgets ui/widgets/Common.cpp ui/widgets/Common.h @@ -849,6 +848,8 @@ SET(LAUNCHER_SOURCES ui/widgets/LogView.h ui/widgets/MCModInfoFrame.cpp ui/widgets/MCModInfoFrame.h + ui/widgets/ModFilterWidget.cpp + ui/widgets/ModFilterWidget.h ui/widgets/ModListView.cpp ui/widgets/ModListView.h ui/widgets/PageContainer.cpp @@ -907,6 +908,7 @@ qt5_wrap_ui(LAUNCHER_UI ui/widgets/InstanceCardWidget.ui ui/widgets/CustomCommands.ui ui/widgets/MCModInfoFrame.ui + ui/widgets/ModFilterWidget.ui ui/dialogs/CopyInstanceDialog.ui ui/dialogs/ProfileSetupDialog.ui ui/dialogs/ProgressDialog.ui @@ -989,7 +991,7 @@ if(DEFINED Launcher_APP_BINARY_DEFS) endif() install(TARGETS ${Launcher_Name} - BUNDLE DESTINATION ${BUNDLE_DEST_DIR} COMPONENT Runtime + BUNDLE DESTINATION "." COMPONENT Runtime LIBRARY DESTINATION ${LIBRARY_DEST_DIR} COMPONENT Runtime RUNTIME DESTINATION ${BINARY_DEST_DIR} COMPONENT Runtime ) diff --git a/launcher/Launcher.in b/launcher/Launcher.in index 5e5e2c2b..528e360e 100755 --- a/launcher/Launcher.in +++ b/launcher/Launcher.in @@ -21,7 +21,7 @@ echo "Launcher Dir: ${LAUNCHER_DIR}" # Set up env - filter out input LD_ variables but pass them in under different names export GAME_LIBRARY_PATH=${GAME_LIBRARY_PATH-${LD_LIBRARY_PATH}} export GAME_PRELOAD=${GAME_PRELOAD-${LD_PRELOAD}} -export LD_LIBRARY_PATH="${LAUNCHER_DIR}/bin":$LAUNCHER_LIBRARY_PATH +export LD_LIBRARY_PATH="${LAUNCHER_DIR}/lib@LIB_SUFFIX@":$LAUNCHER_LIBRARY_PATH export LD_PRELOAD=$LAUNCHER_PRELOAD export QT_PLUGIN_PATH="${LAUNCHER_DIR}/plugins" export QT_FONTPATH="${LAUNCHER_DIR}/fonts" diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 3ba79178..503b34d9 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -213,8 +213,12 @@ QString MinecraftInstance::binRoot() const QString MinecraftInstance::getNativePath() const { +#ifdef Q_OS_FREEBSD + QDir natives_dir("/usr/local/lib/lwjgl/"); +#else QDir natives_dir(FS::PathCombine(instanceRoot(), "natives/")); return natives_dir.absolutePath(); +#endif } QString MinecraftInstance::getLocalLibraryPath() const diff --git a/launcher/modplatform/ModAPI.h b/launcher/modplatform/ModAPI.h index 1a562172..8e6cd45c 100644 --- a/launcher/modplatform/ModAPI.h +++ b/launcher/modplatform/ModAPI.h @@ -3,6 +3,8 @@ #include <QString> #include <QList> +#include "Version.h" + namespace ModPlatform { class ListModel; } @@ -22,7 +24,7 @@ class ModAPI { QString search; QString sorting; ModLoaderType mod_loader; - QString version; + std::list<Version> versions; }; virtual void searchMods(CallerType* caller, SearchArgs&& args) const = 0; @@ -30,7 +32,7 @@ class ModAPI { struct VersionSearchArgs { QString addonId; - QList<QString> mcVersions; + std::list<Version> mcVersions; ModLoaderType loader; }; @@ -53,4 +55,15 @@ class ModAPI { } return ""; } + + protected: + inline auto getGameVersionsString(std::list<Version> mcVersions) const -> QString + { + QString s; + for(auto& ver : mcVersions){ + s += QString("%1,").arg(ver.toString()); + } + s.remove(s.length() - 1, 1); //remove last comma + return s; + } }; diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h index 8654a693..9bcc357e 100644 --- a/launcher/modplatform/flame/FlameAPI.h +++ b/launcher/modplatform/flame/FlameAPI.h @@ -6,6 +6,8 @@ class FlameAPI : public NetworkModAPI { private: inline auto getModSearchURL(SearchArgs& args) const -> QString override { + auto gameVersionStr = args.versions.size() != 0 ? QString("gameVersion=%1").arg(args.versions.front().toString()) : QString(); + return QString( "https://addons-ecs.forgesvc.net/api/v2/addon/search?" "gameId=432&" @@ -17,12 +19,12 @@ class FlameAPI : public NetworkModAPI { "searchFilter=%2&" "sort=%3&" "modLoaderType=%4&" - "gameVersion=%5") + "%5") .arg(args.offset) .arg(args.search) .arg(args.sorting) .arg(args.mod_loader) - .arg(args.version); + .arg(gameVersionStr); }; inline auto getVersionsURL(VersionSearchArgs& args) const -> QString override diff --git a/launcher/modplatform/modrinth/ModrinthAPI.h b/launcher/modplatform/modrinth/ModrinthAPI.h index eefa4a85..6604d772 100644 --- a/launcher/modplatform/modrinth/ModrinthAPI.h +++ b/launcher/modplatform/modrinth/ModrinthAPI.h @@ -22,12 +22,12 @@ class ModrinthAPI : public NetworkModAPI { "limit=25&" "query=%2&" "index=%3&" - "facets=[[\"categories:%4\"],[\"versions:%5\"],[\"project_type:mod\"]]") + "facets=[[\"categories:%4\"],%5[\"project_type:mod\"]]") .arg(args.offset) .arg(args.search) .arg(args.sorting) .arg(getModLoaderString(args.mod_loader)) - .arg(args.version); + .arg(getGameVersionsArray(args.versions)); }; inline auto getVersionsURL(VersionSearchArgs& args) const -> QString override @@ -40,15 +40,14 @@ class ModrinthAPI : public NetworkModAPI { .arg(getModLoaderString(args.loader)); }; - inline auto getGameVersionsString(QList<QString> mcVersions) const -> QString + auto getGameVersionsArray(std::list<Version> mcVersions) const -> QString { QString s; - for(int i = 0; i < mcVersions.count(); i++){ - s += mcVersions.at(i); - if(i < mcVersions.count() - 1) - s += ","; + for(auto& ver : mcVersions){ + s += QString("\"versions:%1\",").arg(ver.toString()); } - return s; + s.remove(s.length() - 1, 1); //remove last comma + return s.isEmpty() ? QString() : QString("[%1],").arg(s); } static auto getModLoaderString(ModLoaderType type) -> const QString diff --git a/launcher/ui/dialogs/ModDownloadDialog.cpp b/launcher/ui/dialogs/ModDownloadDialog.cpp index a53f93e8..d02ea476 100644 --- a/launcher/ui/dialogs/ModDownloadDialog.cpp +++ b/launcher/ui/dialogs/ModDownloadDialog.cpp @@ -22,7 +22,9 @@ ModDownloadDialog::ModDownloadDialog(const std::shared_ptr<ModFolderModel> &mods : QDialog(parent), mods(mods), m_instance(instance) { setObjectName(QStringLiteral("ModDownloadDialog")); - resize(400, 347); + + resize(std::max(0.5*parent->width(), 400.0), std::max(0.75*parent->height(), 400.0)); + m_verticalLayout = new QVBoxLayout(this); m_verticalLayout->setObjectName(QStringLiteral("verticalLayout")); @@ -80,7 +82,7 @@ void ModDownloadDialog::confirm() tr("Confirm mods to download") ); - for(auto task : keys){ + for(auto& task : keys){ confirm_dialog->appendMod(task, modTask.find(task).value()->getFilename()); } diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp index f75d2847..da0331b5 100644 --- a/launcher/ui/pages/modplatform/ModModel.cpp +++ b/launcher/ui/pages/modplatform/ModModel.cpp @@ -72,14 +72,11 @@ void ListModel::performPaginatedSearch() auto profile = (dynamic_cast<MinecraftInstance*>((dynamic_cast<ModPage*>(parent()))->m_instance))->getPackProfile(); m_parent->apiProvider()->searchMods(this, - { nextSearchOffset, currentSearchTerm, getSorts()[currentSort], profile->getModLoader(), getMineVersions().at(0) }); + { nextSearchOffset, currentSearchTerm, getSorts()[currentSort], profile->getModLoader(), getMineVersions() }); } -void ListModel::searchWithTerm(const QString& term, const int sort) +void ListModel::refresh() { - if (currentSearchTerm == term && currentSearchTerm.isNull() == term.isNull() && currentSort == sort) { return; } - currentSearchTerm = term; - currentSort = sort; if (jobPtr) { jobPtr->abort(); searchState = ResetRequested; @@ -94,6 +91,20 @@ void ListModel::searchWithTerm(const QString& term, const int sort) performPaginatedSearch(); } +void ListModel::searchWithTerm(const QString& term, const int sort, const bool filter_changed) +{ + if (currentSearchTerm == term + && currentSearchTerm.isNull() == term.isNull() + && currentSort == sort + && !filter_changed) + { return; } + + currentSearchTerm = term; + currentSort = sort; + + refresh(); +} + void ListModel::getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback) { if (m_logoMap.contains(logo)) { @@ -223,9 +234,7 @@ void ListModel::versionRequestSucceeded(QJsonDocument doc, QString addonId) /******** Helpers ********/ -auto ModPlatform::ListModel::getMineVersions() const -> QList<QString> +auto ModPlatform::ListModel::getMineVersions() const -> std::list<Version> { - return { (dynamic_cast<MinecraftInstance*>((dynamic_cast<ModPage*>(parent()))->m_instance)) - ->getPackProfile() - ->getComponentVersion("net.minecraft") }; + return m_parent->getFilter()->versions; } diff --git a/launcher/ui/pages/modplatform/ModModel.h b/launcher/ui/pages/modplatform/ModModel.h index dbadbeee..d460cff2 100644 --- a/launcher/ui/pages/modplatform/ModModel.h +++ b/launcher/ui/pages/modplatform/ModModel.h @@ -7,6 +7,7 @@ #include "net/NetJob.h" class ModPage; +class Version; namespace ModPlatform { @@ -33,7 +34,8 @@ class ListModel : public QAbstractListModel { /* Ask the API for more information */ void fetchMore(const QModelIndex& parent) override; - void searchWithTerm(const QString& term, const int sort); + void refresh(); + void searchWithTerm(const QString& term, const int sort, const bool filter_changed); void requestModVersions(const ModPlatform::IndexedPack& current); virtual void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) = 0; @@ -62,7 +64,7 @@ class ListModel : public QAbstractListModel { void requestLogo(QString file, QString url); - inline auto getMineVersions() const -> QList<QString>; + inline auto getMineVersions() const -> std::list<Version>; protected: ModPage* m_parent; diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index eabd8379..29f6b601 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -2,21 +2,39 @@ #include "ui_ModPage.h" #include <QKeyEvent> +#include <memory> #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" #include "ui/dialogs/ModDownloadDialog.h" ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance* instance, ModAPI* api) - : QWidget(dialog), m_instance(instance), ui(new Ui::ModPage), dialog(dialog), api(api) + : QWidget(dialog) + , m_instance(instance) + , ui(new Ui::ModPage) + , dialog(dialog) + , filter_widget(static_cast<MinecraftInstance*>(instance)->getPackProfile()->getComponentVersion("net.minecraft"), this) + , api(api) { ui->setupUi(this); connect(ui->searchButton, &QPushButton::clicked, this, &ModPage::triggerSearch); + connect(ui->modFilterButton, &QPushButton::clicked, this, &ModPage::filterMods); ui->searchEdit->installEventFilter(this); ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300); + ui->gridLayout_3->addWidget(&filter_widget, 0, 0, 1, ui->gridLayout_3->columnCount()); + + filter_widget.setInstance(static_cast<MinecraftInstance*>(m_instance)); + m_filter = filter_widget.getFilter(); + + connect(&filter_widget, &ModFilterWidget::filterChanged, this, [&]{ + ui->searchButton->setStyleSheet("text-decoration: underline"); + }); + connect(&filter_widget, &ModFilterWidget::filterUnchanged, this, [&]{ + ui->searchButton->setStyleSheet("text-decoration: none"); + }); } ModPage::~ModPage() @@ -49,9 +67,24 @@ auto ModPage::eventFilter(QObject* watched, QEvent* event) -> bool /******** Callbacks to events in the UI (set up in the derived classes) ********/ +void ModPage::filterMods() +{ + filter_widget.setHidden(!filter_widget.isHidden()); +} + void ModPage::triggerSearch() { - listModel->searchWithTerm(ui->searchEdit->text(), ui->sortByBox->currentIndex()); + auto changed = filter_widget.changed(); + m_filter = filter_widget.getFilter(); + + if(changed){ + ui->packView->clearSelection(); + ui->packDescription->clear(); + ui->versionSelectionBox->clear(); + updateSelectionButton(); + } + + listModel->searchWithTerm(ui->searchEdit->text(), ui->sortByBox->currentIndex(), changed); } void ModPage::onSelectionChanged(QModelIndex first, QModelIndex second) @@ -131,7 +164,7 @@ void ModPage::retranslate() ui->retranslateUi(this); } -void ModPage::updateModVersions() +void ModPage::updateModVersions(int prev_count) { auto packProfile = (dynamic_cast<MinecraftInstance*>(m_instance))->getPackProfile(); @@ -141,15 +174,22 @@ void ModPage::updateModVersions() for (int i = 0; i < current.versions.size(); i++) { auto version = current.versions[i]; - //NOTE: Flame doesn't care about loaderString, so passing it changes nothing. - if (!validateVersion(version, mcVersion, loaderString)) { - continue; + bool valid = false; + for(auto& mcVer : m_filter->versions){ + //NOTE: Flame doesn't care about loaderString, so passing it changes nothing. + if (validateVersion(version, mcVer.toString(), loaderString)) { + valid = true; + break; + } } - ui->versionSelectionBox->addItem(version.version, QVariant(i)); + if(valid || m_filter->versions.size() == 0) + ui->versionSelectionBox->addItem(version.version, QVariant(i)); + } + if (ui->versionSelectionBox->count() == 0 && prev_count != 0) { + ui->versionSelectionBox->addItem(tr("No valid version found!"), QVariant(-1)); + ui->modSelectionButton->setText(tr("Cannot select invalid version :(")); } - if (ui->versionSelectionBox->count() == 0) { ui->versionSelectionBox->addItem(tr("No valid version found!"), QVariant(-1)); } - ui->modSelectionButton->setText(tr("Cannot select invalid version :(")); updateSelectionButton(); } diff --git a/launcher/ui/pages/modplatform/ModPage.h b/launcher/ui/pages/modplatform/ModPage.h index 0cd13f37..85aaede9 100644 --- a/launcher/ui/pages/modplatform/ModPage.h +++ b/launcher/ui/pages/modplatform/ModPage.h @@ -7,6 +7,7 @@ #include "modplatform/ModIndex.h" #include "ui/pages/BasePage.h" #include "ui/pages/modplatform/ModModel.h" +#include "ui/widgets/ModFilterWidget.h" class ModDownloadDialog; @@ -39,9 +40,10 @@ class ModPage : public QWidget, public BasePage { virtual auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, QString loaderVer = "") const -> bool = 0; auto apiProvider() const -> const ModAPI* { return api.get(); }; + auto getFilter() const -> const std::shared_ptr<ModFilterWidget::Filter> { return m_filter; } auto getCurrent() -> ModPlatform::IndexedPack& { return current; } - void updateModVersions(); + void updateModVersions(int prev_count = -1); void openedImpl() override; auto eventFilter(QObject* watched, QEvent* event) -> bool override; @@ -52,6 +54,7 @@ class ModPage : public QWidget, public BasePage { void updateSelectionButton(); protected slots: + virtual void filterMods(); void triggerSearch(); void onSelectionChanged(QModelIndex first, QModelIndex second); void onVersionSelectionChanged(QString data); @@ -60,6 +63,10 @@ class ModPage : public QWidget, public BasePage { protected: Ui::ModPage* ui = nullptr; ModDownloadDialog* dialog = nullptr; + + ModFilterWidget filter_widget; + std::shared_ptr<ModFilterWidget::Filter> m_filter; + ModPlatform::ListModel* listModel = nullptr; ModPlatform::IndexedPack current; diff --git a/launcher/ui/pages/modplatform/ModPage.ui b/launcher/ui/pages/modplatform/ModPage.ui index 508f1bac..afcd9bb7 100644 --- a/launcher/ui/pages/modplatform/ModPage.ui +++ b/launcher/ui/pages/modplatform/ModPage.ui @@ -11,7 +11,7 @@ </rect> </property> <layout class="QGridLayout" name="gridLayout"> - <item row="1" column="0" colspan="2"> + <item row="1" column="0" colspan="4"> <layout class="QGridLayout" name="gridLayout_3"> <item row="1" column="2"> <widget class="QTextBrowser" name="packDescription"> @@ -41,7 +41,7 @@ </item> </layout> </item> - <item row="0" column="1"> + <item row="0" column="3"> <widget class="QPushButton" name="searchButton"> <property name="text"> <string>Search</string> @@ -51,12 +51,12 @@ <item row="0" column="0"> <widget class="QLineEdit" name="searchEdit"> <property name="placeholderText"> - <string>Search and filter...</string> + <string>Search for mods...</string> </property> </widget> </item> - <item row="2" column="0" colspan="2"> - <layout class="QGridLayout" name="gridLayout_4" columnstretch="0,0,0,0"> + <item row="2" column="0" colspan="4"> + <layout class="QGridLayout" name="gridLayout_4" columnstretch="0,0,0"> <item row="0" column="2"> <widget class="QComboBox" name="versionSelectionBox"/> </item> @@ -82,6 +82,20 @@ </item> </layout> </item> + <item row="0" column="1"> + <widget class="QPushButton" name="modFilterButton"> + <property name="text"> + <string>Filter options</string> + </property> + </widget> + </item> + <item row="0" column="2"> + <widget class="Line" name="line"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + </widget> + </item> </layout> </widget> <tabstops> diff --git a/launcher/ui/widgets/ModFilterWidget.cpp b/launcher/ui/widgets/ModFilterWidget.cpp new file mode 100644 index 00000000..ffc8d05d --- /dev/null +++ b/launcher/ui/widgets/ModFilterWidget.cpp @@ -0,0 +1,109 @@ +#include "ModFilterWidget.h" +#include "ui_ModFilterWidget.h" + +ModFilterWidget::ModFilterWidget(Version def, QWidget* parent) + : QTabWidget(parent), m_filter(new Filter()), ui(new Ui::ModFilterWidget) +{ + ui->setupUi(this); + + m_mcVersion_buttons.addButton(ui->strictVersionButton, VersionButtonID::Strict); + ui->strictVersionButton->click(); + m_mcVersion_buttons.addButton(ui->majorVersionButton, VersionButtonID::Major); + m_mcVersion_buttons.addButton(ui->allVersionsButton, VersionButtonID::All); + //m_mcVersion_buttons.addButton(ui->betweenVersionsButton, VersionButtonID::Between); + + connect(&m_mcVersion_buttons, SIGNAL(idClicked(int)), this, SLOT(onVersionFilterChanged(int))); + + m_filter->versions.push_front(def); + + setHidden(true); +} + +void ModFilterWidget::setInstance(MinecraftInstance* instance) +{ + m_instance = instance; + + auto mcVersionSplit = mcVersionStr().split("."); + + ui->strictVersionButton->setText( + tr("Strict match (= %1)").arg(mcVersionStr())); + ui->majorVersionButton->setText( + tr("Major version match (= %1.%2.x)").arg(mcVersionSplit[0], mcVersionSplit[1])); + ui->allVersionsButton->setText( + tr("Any version")); + //ui->betweenVersionsButton->setText( + // tr("Between two versions")); +} + +auto ModFilterWidget::getFilter() -> std::shared_ptr<Filter> +{ + m_last_version_id = m_version_id; + emit filterUnchanged(); + return m_filter; +} + +void ModFilterWidget::disableVersionButton(VersionButtonID id) +{ + switch(id){ + case(VersionButtonID::Strict): + ui->strictVersionButton->setEnabled(false); + break; + case(VersionButtonID::Major): + ui->majorVersionButton->setEnabled(false); + break; + case(VersionButtonID::All): + ui->allVersionsButton->setEnabled(false); + break; + case(VersionButtonID::Between): + // ui->betweenVersionsButton->setEnabled(false); + break; + default: + break; + } +} + +void ModFilterWidget::onVersionFilterChanged(int id) +{ + //ui->lowerVersionComboBox->setEnabled(id == VersionButtonID::Between); + //ui->upperVersionComboBox->setEnabled(id == VersionButtonID::Between); + + auto versionSplit = mcVersionStr().split("."); + int index = 0; + + auto cast_id = (VersionButtonID) id; + if (cast_id != m_version_id) { + m_version_id = cast_id; + } else { + return; + } + + m_filter->versions.clear(); + + switch(cast_id){ + case(VersionButtonID::Strict): + m_filter->versions.push_front(mcVersion()); + break; + case(VersionButtonID::Major): + for(auto i = Version(QString("%1.%2").arg(versionSplit[0], versionSplit[1])); i <= mcVersion(); index++){ + m_filter->versions.push_front(i); + i = Version(QString("%1.%2.%3").arg(versionSplit[0], versionSplit[1], QString("%1").arg(index))); + } + break; + case(VersionButtonID::All): + // Empty list to avoid enumerating all versions :P + break; + case(VersionButtonID::Between): + // TODO + break; + } + + if(changed()) + emit filterChanged(); + else + emit filterUnchanged(); +} + +ModFilterWidget::~ModFilterWidget() +{ + delete ui; +} diff --git a/launcher/ui/widgets/ModFilterWidget.h b/launcher/ui/widgets/ModFilterWidget.h new file mode 100644 index 00000000..334fc672 --- /dev/null +++ b/launcher/ui/widgets/ModFilterWidget.h @@ -0,0 +1,69 @@ +#pragma once + +#include <QTabWidget> +#include <QButtonGroup> + +#include "Version.h" +#include "minecraft/MinecraftInstance.h" +#include "minecraft/PackProfile.h" + +class MinecraftInstance; + +namespace Ui { +class ModFilterWidget; +} + +class ModFilterWidget : public QTabWidget +{ + Q_OBJECT +public: + enum VersionButtonID { + Strict = 0, + Major = 1, + All = 2, + Between = 3 + }; + + struct Filter { + std::list<Version> versions; + + bool operator==(const Filter& other) const { return versions == other.versions; } + bool operator!=(const Filter& other) const { return !(*this == other); } + }; + + std::shared_ptr<Filter> m_filter; + +public: + explicit ModFilterWidget(Version def, QWidget* parent = nullptr); + ~ModFilterWidget(); + + void setInstance(MinecraftInstance* instance); + + /// By default all buttons are enabled + void disableVersionButton(VersionButtonID); + + auto getFilter() -> std::shared_ptr<Filter>; + auto changed() const -> bool { return m_last_version_id != m_version_id; } + +private: + inline auto mcVersionStr() const -> QString { return m_instance ? m_instance->getPackProfile()->getComponentVersion("net.minecraft") : ""; } + inline auto mcVersion() const -> Version { return { mcVersionStr() }; } + +private slots: + void onVersionFilterChanged(int id); + +public: signals: + void filterChanged(); + void filterUnchanged(); + +private: + Ui::ModFilterWidget* ui; + + MinecraftInstance* m_instance = nullptr; + + QButtonGroup m_mcVersion_buttons; + + /* Used to tell if the filter was changed since the last getFilter() call */ + VersionButtonID m_last_version_id = VersionButtonID::Strict; + VersionButtonID m_version_id = VersionButtonID::Strict; +}; diff --git a/launcher/ui/widgets/ModFilterWidget.ui b/launcher/ui/widgets/ModFilterWidget.ui new file mode 100644 index 00000000..ad1090e2 --- /dev/null +++ b/launcher/ui/widgets/ModFilterWidget.ui @@ -0,0 +1,54 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ModFilterWidget</class> + <widget class="QTabWidget" name="ModFilterWidget"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>400</width> + <height>300</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Minimum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <widget class="QWidget" name="VersionPage"> + <attribute name="title"> + <string>Minecraft versions</string> + </attribute> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <layout class="QFormLayout" name="formLayout"> + <item row="2" column="0"> + <widget class="QRadioButton" name="allVersionsButton"> + <property name="text"> + <string>allVersions</string> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QRadioButton" name="strictVersionButton"> + <property name="text"> + <string>strictVersion</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QRadioButton" name="majorVersionButton"> + <property name="text"> + <string>majorVersion</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </widget> + <resources/> + <connections/> +</ui> diff --git a/program_info/CMakeLists.txt b/program_info/CMakeLists.txt index 9c243826..60549d8d 100644 --- a/program_info/CMakeLists.txt +++ b/program_info/CMakeLists.txt @@ -17,5 +17,7 @@ set(Launcher_Branding_ICNS "program_info/polymc.icns" PARENT_SCOPE) set(Launcher_Branding_WindowsRC "program_info/polymc.rc" PARENT_SCOPE) set(Launcher_Branding_LogoQRC "program_info/polymc.qrc" PARENT_SCOPE) +set(Launcher_Portable_File "program_info/portable.txt" PARENT_SCOPE) + configure_file(org.polymc.PolyMC.desktop.in org.polymc.PolyMC.desktop) configure_file(org.polymc.PolyMC.metainfo.xml.in org.polymc.PolyMC.metainfo.xml) diff --git a/program_info/portable.txt b/program_info/portable.txt new file mode 100644 index 00000000..b7e256a2 --- /dev/null +++ b/program_info/portable.txt @@ -0,0 +1,4 @@ +This file enables the portable mode for the launcher. + +If this file is present in the root directory of the launcher, it will store all data here. Otherwise it will store your data in your appdata directory. +You can safely delete this file, if you don't want the launcher to store your data here. |