diff --git a/CMakeLists.txt b/CMakeLists.txt index 88c92776d8..fceeb6171c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,6 +17,7 @@ include(DownloadExternals) include(CMakeDependentOption) include(CTest) include(CPMUtil) +include(UseCcache) DetectArchitecture() @@ -200,30 +201,6 @@ if(YUZU_ENABLE_LTO) include(UseLTO) endif() -option(USE_CCACHE "Use ccache for compilation" OFF) -set(CCACHE_PATH "ccache" CACHE STRING "Path to ccache binary") -if(USE_CCACHE) - find_program(CCACHE_BINARY ${CCACHE_PATH}) - if(CCACHE_BINARY) - message(STATUS "Found ccache at: ${CCACHE_BINARY}") - set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE_BINARY}) - set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE_BINARY}) - else() - message(FATAL_ERROR "USE_CCACHE enabled, but no executable found at: ${CCACHE_PATH}") - endif() - # Follow SCCache recommendations: - # - if(WIN32) - if(CMAKE_BUILD_TYPE STREQUAL "Debug") - string(REPLACE "/Zi" "/Z7" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}") - string(REPLACE "/Zi" "/Z7" CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}") - elseif(CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") - string(REPLACE "/Zi" "/Z7" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}") - string(REPLACE "/Zi" "/Z7" CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO}") - endif() - endif() -endif() - option(YUZU_DOWNLOAD_ANDROID_VVL "Download validation layer binary for android" ON) option(YUZU_LEGACY "Apply patches that improve compatibility with older GPUs (e.g. Snapdragon 865) at the cost of performance" OFF) @@ -268,7 +245,6 @@ if (ENABLE_OPENSSL) option(YUZU_USE_BUNDLED_OPENSSL "Download bundled OpenSSL build" ${DEFAULT_YUZU_USE_BUNDLED_OPENSSL}) endif() -# TODO(crueter): CPM this if (ANDROID AND YUZU_DOWNLOAD_ANDROID_VVL) AddJsonPackage(vulkan-validation-layers) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 255ac1ac84..39f2c6facb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -240,21 +240,174 @@ if (ENABLE_SDL2 AND YUZU_CMD) set_target_properties(yuzu-cmd PROPERTIES OUTPUT_NAME "eden-cli") endif() +if (ENABLE_WEB_SERVICE) + add_subdirectory(web_service) +endif() + if (ENABLE_QT) add_subdirectory(qt_common) -endif() -if (ENABLE_QT_QML) - set(QML_IMPORT_PATH ${CMAKE_CURRENT_SOURCE_DIR} CACHE STRING "") - add_subdirectory(Eden) -endif() + function(yuzu_qt_target target) + target_compile_definitions(${target} PRIVATE + # Use QStringBuilder for string concatenation to reduce + # the overall number of temporary strings created. + -DQT_USE_QSTRINGBUILDER -if(ENABLE_QT_WIDGETS) - add_subdirectory(yuzu) -endif() + # Disable implicit type narrowing in signal/slot connect() calls. + -DQT_NO_NARROWING_CONVERSIONS_IN_CONNECT -if (ENABLE_WEB_SERVICE) - add_subdirectory(web_service) + # Disable unsafe overloads of QProcess' start() function. + -DQT_NO_PROCESS_COMBINED_ARGUMENT_START + + # Disable implicit QString->QUrl conversions to enforce use of proper resolving functions. + -DQT_NO_URL_CAST_FROM_STRING + ) + + file(GLOB COMPAT_LIST + ${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.qrc + ${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.json) + file(GLOB_RECURSE ICONS ${PROJECT_SOURCE_DIR}/dist/icons/*) + file(GLOB_RECURSE THEMES ${PROJECT_SOURCE_DIR}/dist/qt_themes/*) + + if (ENABLE_UPDATE_CHECKER) + target_compile_definitions(${target} PUBLIC ENABLE_UPDATE_CHECKER) + endif() + + if (ENABLE_QT_TRANSLATION) + set(YUZU_QT_LANGUAGES "${PROJECT_SOURCE_DIR}/dist/languages" CACHE PATH "Path to the translation bundle for the Qt frontend") + option(GENERATE_QT_TRANSLATION "Generate en.ts as the translation source file" OFF) + option(WORKAROUND_BROKEN_LUPDATE "Run lupdate directly through CMake if Qt's convenience wrappers don't work" OFF) + + # Update source TS file if enabled + if (GENERATE_QT_TRANSLATION) + get_target_property(SRCS yuzu SOURCES) + # these calls to qt_create_translation also creates a rule to generate en.qm which conflicts with providing english plurals + # so we have to set a OUTPUT_LOCATION so that we don't have multiple rules to generate en.qm + set_source_files_properties(${YUZU_QT_LANGUAGES}/en.ts PROPERTIES OUTPUT_LOCATION "${CMAKE_CURRENT_BINARY_DIR}/translations") + if (WORKAROUND_BROKEN_LUPDATE) + add_custom_command(OUTPUT ${YUZU_QT_LANGUAGES}/en.ts + COMMAND lupdate + -source-language en_US + -target-language en_US + ${SRCS} + ${UIS} + -ts ${YUZU_QT_LANGUAGES}/en.ts + DEPENDS + ${SRCS} + ${UIS} + WORKING_DIRECTORY + ${CMAKE_CURRENT_SOURCE_DIR} + ) + else() + qt_create_translation(QM_FILES + ${SRCS} + ${UIS} + ${YUZU_QT_LANGUAGES}/en.ts + OPTIONS + -source-language en_US + -target-language en_US + ) + endif() + + # Generate plurals into dist/english_plurals/generated_en.ts so it can be used to revise dist/english_plurals/en.ts + set(GENERATED_PLURALS_FILE ${PROJECT_SOURCE_DIR}/dist/english_plurals/generated_en.ts) + set_source_files_properties(${GENERATED_PLURALS_FILE} PROPERTIES OUTPUT_LOCATION "${CMAKE_CURRENT_BINARY_DIR}/plurals") + if (WORKAROUND_BROKEN_LUPDATE) + add_custom_command(OUTPUT ${GENERATED_PLURALS_FILE} + COMMAND lupdate + -source-language en_US + -target-language en_US + ${SRCS} + ${UIS} + -ts ${GENERATED_PLURALS_FILE} + DEPENDS + ${SRCS} + ${UIS} + WORKING_DIRECTORY + ${CMAKE_CURRENT_SOURCE_DIR} + ) + else() + qt_create_translation(QM_FILES ${SRCS} ${UIS} ${GENERATED_PLURALS_FILE} OPTIONS -pluralonly -source-language en_US -target-language en_US) + endif() + + add_custom_target(translation ALL DEPENDS ${YUZU_QT_LANGUAGES}/en.ts ${GENERATED_PLURALS_FILE}) + endif() + + # Find all TS files except en.ts + file(GLOB_RECURSE LANGUAGES_TS ${YUZU_QT_LANGUAGES}/*.ts) + list(REMOVE_ITEM LANGUAGES_TS ${YUZU_QT_LANGUAGES}/en.ts) + + # Compile TS files to QM files + qt_add_translation(LANGUAGES_QM ${LANGUAGES_TS}) + + # Compile english plurals TS file to en.qm + qt_add_translation(LANGUAGES_QM ${PROJECT_SOURCE_DIR}/dist/english_plurals/en.ts) + + # Build a QRC file from the QM file list + set(LANGUAGES_QRC ${CMAKE_CURRENT_BINARY_DIR}/languages.qrc) + file(WRITE ${LANGUAGES_QRC} "\n") + foreach (QM ${LANGUAGES_QM}) + get_filename_component(QM_FILE ${QM} NAME) + file(APPEND ${LANGUAGES_QRC} "${QM_FILE}\n") + endforeach (QM) + file(APPEND ${LANGUAGES_QRC} "") + + # Add the QRC file to package in all QM files + qt_add_resources(LANGUAGES ${LANGUAGES_QRC}) + else() + set(LANGUAGES) + endif() + + target_sources(${target} + PRIVATE + ${COMPAT_LIST} + ${ICONS} + ${LANGUAGES} + ${THEMES}) + + if (APPLE) + # Normal icns + set(MACOSX_ICON "${PROJECT_SOURCE_DIR}/dist/eden.icns") + set_source_files_properties(${MACOSX_ICON} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) + target_sources(${target} PRIVATE ${MACOSX_ICON}) + + # Liquid glass + set(MACOSX_LIQUID_GLASS_ICON "${PROJECT_SOURCE_DIR}/dist/Assets.car") + set_source_files_properties(${MACOSX_LIQUID_GLASS_ICON} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) + target_sources(${target} PRIVATE ${MACOSX_LIQUID_GLASS_ICON}) + + set_target_properties(${target} PROPERTIES MACOSX_BUNDLE TRUE) + set_target_properties(${target} PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist) + + set(CMAKE_FIND_LIBRARY_SUFFIXES ".dylib") + find_library(MOLTENVK_LIBRARY MoltenVK REQUIRED) + message(STATUS "Using MoltenVK at ${MOLTENVK_LIBRARY}.") + + set_source_files_properties(${MOLTENVK_LIBRARY} PROPERTIES + MACOSX_PACKAGE_LOCATION Frameworks + XCODE_FILE_ATTRIBUTES "CodeSignOnCopy") + target_sources(${target} PRIVATE ${MOLTENVK_LIBRARY}) + elseif(WIN32) + # compile as a win32 gui application instead of a console application + target_link_libraries(${target} PRIVATE Qt6::EntryPointPrivate) + if(MSVC) + target_link_libraries(${target} PRIVATE version.lib) + set_target_properties(${target} PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS") + elseif(MINGW) + set_target_properties(${target} PROPERTIES LINK_FLAGS_RELEASE "-Wl,--subsystem,windows") + target_link_libraries(${target} PRIVATE dwmapi) + endif() + endif() + endfunction() + + if (ENABLE_QT_QML) + set(QML_IMPORT_PATH ${CMAKE_CURRENT_SOURCE_DIR} CACHE STRING "") + add_subdirectory(Eden) + endif() + + if(ENABLE_QT_WIDGETS) + add_subdirectory(yuzu) + endif() endif() if (ANDROID) diff --git a/src/Eden/Interface/GraphicsDeviceInterface.cpp b/src/Eden/Interface/GraphicsDeviceInterface.cpp index be3e2df78b..962c599174 100644 --- a/src/Eden/Interface/GraphicsDeviceInterface.cpp +++ b/src/Eden/Interface/GraphicsDeviceInterface.cpp @@ -68,7 +68,7 @@ void GraphicsDeviceInterface::populateVsync() } const auto &present_modes = //< relevant vector of present modes for the selected device or API - m_isVulkan && m_device > -1 && https://gofile.io/d/920ShN ? device_present_modes[m_device] : default_present_modes; + m_isVulkan && m_device > -1 && !device_present_modes.empty() ? device_present_modes[m_device] : default_present_modes; m_vsyncModes.clear(); m_vsyncModes.reserve(present_modes.size()); diff --git a/src/Eden/Interface/MainWindowInterface.cpp b/src/Eden/Interface/MainWindowInterface.cpp index 6b41d65f46..6d87443549 100644 --- a/src/Eden/Interface/MainWindowInterface.cpp +++ b/src/Eden/Interface/MainWindowInterface.cpp @@ -9,6 +9,9 @@ #include "qt_common/util/game.h" #include "qt_common/abstract/frontend.h" +#include "qt_common/qt_constants.h" + +#include MainWindowInterface::MainWindowInterface(GameListModel* model, QObject* parent) : QObject{parent}, m_gameList(model) { @@ -67,6 +70,8 @@ void MainWindowInterface::setFirmwareVersion() { setFirmwareTooltip(QString::fromStdString(display_title)); } +// TODO(qml): The following are basically all identical to main_window.cpp +// Is there any way we can combine these? void MainWindowInterface::openRootDataFolder() { QtCommon::Game::OpenRootDataFolder(); } @@ -91,7 +96,35 @@ void MainWindowInterface::openLogFolder() QtCommon::Game::OpenLogFolder(); } +void MainWindowInterface::createHomeMenuDesktopShortcut() { + QtCommon::Game::CreateHomeMenuShortcut(QtCommon::Game::ShortcutTarget::Desktop); +} + +void MainWindowInterface::createHomeMenuApplicationMenuShortcut() { + QtCommon::Game::CreateHomeMenuShortcut(QtCommon::Game::ShortcutTarget::Applications); +} + +void MainWindowInterface::openURL(const QUrl& url) { + const bool open = QDesktopServices::openUrl(url); + if (!open) { + QtCommon::Frontend::Warning(tr("Error opening URL"), + tr("Unable to open the URL \"%1\".").arg(url.toString())); + } +} + +void MainWindowInterface::openModsPage() { + openURL(QUrl(QString::fromLocal8Bit(QtCommon::Constants::modPage))); +} + +void MainWindowInterface::openQuickstartGuide() { + openURL(QUrl(QString::fromLocal8Bit(QtCommon::Constants::quickstartPage))); +} + +void MainWindowInterface::openFAQ() { + openURL(QUrl(QString::fromLocal8Bit(QtCommon::Constants::helpPage))); +} +/// PROPERTIES /// QString MainWindowInterface::firmwareDisplay() const { return m_firmwareDisplay; } diff --git a/src/Eden/Interface/MainWindowInterface.h b/src/Eden/Interface/MainWindowInterface.h index 7aa9de40e3..b0c42d4464 100644 --- a/src/Eden/Interface/MainWindowInterface.h +++ b/src/Eden/Interface/MainWindowInterface.h @@ -24,6 +24,14 @@ public: Q_INVOKABLE void installDecryptionKeys(); + Q_INVOKABLE void createHomeMenuApplicationMenuShortcut(); + Q_INVOKABLE void createHomeMenuDesktopShortcut(); + + Q_INVOKABLE void openURL(const QUrl& url); + Q_INVOKABLE void openModsPage(); + Q_INVOKABLE void openQuickstartGuide(); + Q_INVOKABLE void openFAQ(); + bool firmwareGood() const; void setFirmwareGood(bool newFirmwareGood); diff --git a/src/Eden/Interface/MetaObjectHelper.h b/src/Eden/Interface/MetaObjectHelper.h index 375e5d211a..a9f5352c8c 100644 --- a/src/Eden/Interface/MetaObjectHelper.h +++ b/src/Eden/Interface/MetaObjectHelper.h @@ -18,7 +18,7 @@ public: { QQmlProperty qmlProperty(object, property); QMetaProperty metaProperty = qmlProperty.property(); - return metaProperty.typeName(); + return QString::fromLocal8Bit(metaProperty.typeName()); } }; diff --git a/src/Eden/Interface/QMLSetting.cpp b/src/Eden/Interface/QMLSetting.cpp index a1fa791c53..d665283ad6 100644 --- a/src/Eden/Interface/QMLSetting.cpp +++ b/src/Eden/Interface/QMLSetting.cpp @@ -3,7 +3,6 @@ #include "QMLSetting.h" #include "common/settings.h" -#include "qt_common/config/uisettings.h" #include @@ -56,17 +55,17 @@ QMLSetting::QMLSetting(Settings::BasicSetting *setting, QObject *parent, Request }(); if (type == typeid(bool)) { - m_type = "bool"; + m_type = QStringLiteral("bool"); m_metaType = QMetaType::Bool; } else if (setting->IsEnum()) { m_metaType = QMetaType::UInt; if (request == RequestType::RadioGroup) { - m_type = "radio"; + m_type = QStringLiteral("radio"); // TODO: Add the options and whatnot // see CreateRadioGroup } else { - m_type = "enumCombo"; + m_type = QStringLiteral("enumCombo"); } } else if (setting->IsIntegral()) { m_metaType = QMetaType::UInt; @@ -75,27 +74,27 @@ QMLSetting::QMLSetting(Settings::BasicSetting *setting, QObject *parent, Request case RequestType::Slider: case RequestType::ReverseSlider: // TODO: Reversal and multiplier - m_type = "intSlider"; + m_type = QStringLiteral("intSlider"); break; case RequestType::Default: case RequestType::LineEdit: - m_type = "intSpin"; + m_type = QStringLiteral("intSpin"); break; case RequestType::DateTimeEdit: // TODO: disabled/restrict - m_type = "time"; + m_type = QStringLiteral("time"); break; case RequestType::SpinBox: // TODO: suffix - m_type = "intSpin"; + m_type = QStringLiteral("intSpin"); break; case RequestType::HexEdit: - m_type = "hex"; + m_type = QStringLiteral("hex"); break; case RequestType::ComboBox: // TODO: Add the options and whatnot // see CreateComboBox - m_type = "intCombo"; + m_type = QStringLiteral("intCombo"); break; default: // UNIMPLEMENTED(); @@ -108,12 +107,12 @@ QMLSetting::QMLSetting(Settings::BasicSetting *setting, QObject *parent, Request case RequestType::Default: case RequestType::SpinBox: // TODO: suffix - m_type = "doubleSpin"; + m_type = QStringLiteral("doubleSpin"); break; case RequestType::Slider: case RequestType::ReverseSlider: // TODO: multiplier, suffix, reversal - m_type = "doubleSlider"; + m_type = QStringLiteral("doubleSlider"); break; default: // UNIMPLEMENTED assert @@ -126,10 +125,10 @@ QMLSetting::QMLSetting(Settings::BasicSetting *setting, QObject *parent, Request switch (request) { case RequestType::Default: case RequestType::LineEdit: - m_type = "stringLine"; + m_type = QStringLiteral("stringLine"); break; case RequestType::ComboBox: - m_type = "stringCombo"; + m_type = QStringLiteral("stringCombo"); break; default: // UNIMPLEMENTED(); diff --git a/src/Eden/Interface/SettingsInterface.cpp b/src/Eden/Interface/SettingsInterface.cpp index 50de9385f2..f1f735a74c 100644 --- a/src/Eden/Interface/SettingsInterface.cpp +++ b/src/Eden/Interface/SettingsInterface.cpp @@ -93,11 +93,11 @@ QMLSetting *SettingsInterface::getSetting(Settings::BasicSetting *setting) } // TODO: Suffix (fr) - QString suffix = ""; + QString suffix = QString(); if ((setting->Specialization() & Settings::SpecializationAttributeMask) == Settings::Specialization::Percentage) { - suffix = "%"; + suffix = QStringLiteral("%"); } // paired setting (I/A) diff --git a/src/Eden/Main/AboutDialog.qml b/src/Eden/Main/AboutDialog.qml new file mode 100644 index 0000000000..94939118a1 --- /dev/null +++ b/src/Eden/Main/AboutDialog.qml @@ -0,0 +1,84 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls + +import Carboxyl.Contour + +NativeDialog { + title: qsTr("About Eden") + + width: 700 + height: 400 + + standardButtons: Dialog.Ok + + Image { + id: img + anchors { + left: parent.left + verticalCenter: parent.verticalCenter + + margins: 10 + } + + source: "qrc:/icons/default/256x256/eden.png" + width: 200 + height: 200 + } + + ColumnLayout { + anchors { + left: img.right + top: parent.top + bottom: parent.bottom + right: parent.right + + margins: 10 + } + + Label { + font.pixelSize: 28 + text: qsTr("Eden") + } + + Label { + font.pixelSize: 14 + text: TitleManager.title + } + + Label { + text: qsTr("Eden is an experimental open-source emulator for the Nintendo Switch licensed under the \ +GPLv3.0+, based on the Yuzu emulator project, which ended development back in March 2024.\n\n\ +This software should not be used to play games you have not legally obtained.") + wrapMode: Text.WordWrap + Layout.fillWidth: true + Layout.fillHeight: true + font.pointSize: 12 + } + + Label { + text: 'Website | ' + + 'Source Code | ' + + 'Contributors | ' + + 'Discord | ' + + 'Revolt | ' + + 'Twitter | ' + + 'License' + textFormat: Text.RichText + wrapMode: Text.WordWrap + Layout.fillWidth: true + onLinkActivated: link => Qt.openUrlExternally(link) + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.NoButton + cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor + } + } + + Label { + text: '"Nintendo Switch" is a trademark of Nintendo. Eden is not affiliated with Nintendo in any way.' + font.pixelSize: 9 + } + } +} diff --git a/src/Eden/Main/CMakeLists.txt b/src/Eden/Main/CMakeLists.txt index e75db4cfa2..96b1a356e4 100644 --- a/src/Eden/Main/CMakeLists.txt +++ b/src/Eden/Main/CMakeLists.txt @@ -18,6 +18,9 @@ EdenModule( GameCarouselCard.qml GameCarousel.qml + + AboutDialog.qml + DepsDialog.qml LIBRARIES Eden::Interface ) diff --git a/src/Eden/Main/DepsDialog.qml b/src/Eden/Main/DepsDialog.qml new file mode 100644 index 0000000000..1d16c6cdec --- /dev/null +++ b/src/Eden/Main/DepsDialog.qml @@ -0,0 +1,142 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls + +import Carboxyl.Contour + +NativeDialog { + title: qsTr("Eden Dependencies") + + width: 700 + height: 600 + + standardButtons: Dialog.Ok + + Image { + id: img + anchors { + left: parent.left + verticalCenter: parent.verticalCenter + + margins: 10 + } + + source: "qrc:/icons/default/256x256/eden.png" + width: 200 + height: 200 + } + + ColumnLayout { + anchors { + left: img.right + top: parent.top + bottom: parent.bottom + right: parent.right + + margins: 10 + } + + Label { + font.pixelSize: 28 + text: qsTr("Eden Dependencies") + } + + Label { + font.pixelSize: 14 + text: qsTr("The projects that make Eden possible") + } + + HorizontalHeaderView { + syncView: tableView + clip: true + + delegate: Rectangle { + required property string display + + implicitWidth: scroll.width / 2 - 5 + implicitHeight: 30 + + color: palette.alternateBase + + border { + color: palette.mid + width: 1 + } + + Component.onCompleted: console.log(display, row) + + Label { + text: display + anchors.fill: parent + + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + } + } + } + + ScrollView { + id: scroll + + Layout.fillHeight: true + Layout.fillWidth: true + + TableView { + id: tableView + alternatingRows: true + + model: DependencyModel + + boundsBehavior: Flickable.StopAtBounds + clip: true + + Component.onCompleted: console.log(itemAtCell(Qt.point(1, 1))) + + delegate: Rectangle { + required property string display + required property int row + + implicitWidth: scroll.width / 2 - 5 + implicitHeight: 30 + + color: row % 2 == 0 ? palette.base : palette.alternateBase + border { + color: palette.mid + width: 1 + } + + Component.onCompleted: console.log(display, row) + + Label { + text: display + anchors.fill: parent + anchors.leftMargin: 10 + + verticalAlignment: Text.AlignVCenter + textFormat: Text.RichText + + onLinkActivated: link => Qt.openUrlExternally(link) + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.NoButton + cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor + } + } + } + } + + WheelHandler { + target: scroll + onWheel: event => { + const sensitivity = 1 / 1500 + scroll.ScrollBar.vertical.position -= event.angleDelta.y * sensitivity + scroll.ScrollBar.vertical.position = Math.max( + Math.min( + scroll.ScrollBar.vertical.position, + 1.0 - scroll.ScrollBar.vertical.size), 0.0) + } + } + } + } +} diff --git a/src/Eden/Main/GameCarousel.qml b/src/Eden/Main/GameCarousel.qml index 056a928827..c046b8ae1b 100644 --- a/src/Eden/Main/GameCarousel.qml +++ b/src/Eden/Main/GameCarousel.qml @@ -1,6 +1,5 @@ // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later - import QtQuick import QtQuick.Controls import Qt.labs.platform @@ -15,7 +14,6 @@ ListView { focus: true - // focusPolicy: Qt.StrongFocus model: EdenGameList orientation: ListView.Horizontal clip: false @@ -38,7 +36,6 @@ ListView { currentIndex = count - 1 } - // TODO(crueter): handle move/displace/add (requires thread worker on game list and a bunch of other shit) Rectangle { id: hg clip: false @@ -79,10 +76,12 @@ ListView { } highlightRangeMode: ListView.StrictlyEnforceRange - preferredHighlightBegin: currentItem === null ? 0 : x + width / 2 - currentItem.width / 2 - preferredHighlightEnd: currentItem === null ? 0 : x + width / 2 + currentItem.width / 2 + // TODO: Configurable Card size + preferredHighlightBegin: x + width / 2 - 150 + preferredHighlightEnd: x + width / 2 + 150 highlightMoveDuration: 300 + delegate: GameCarouselCard { id: game width: 300 diff --git a/src/Eden/Main/Main.qml b/src/Eden/Main/Main.qml index aaf396d382..3892bc1d8e 100644 --- a/src/Eden/Main/Main.qml +++ b/src/Eden/Main/Main.qml @@ -40,6 +40,14 @@ ApplicationWindow { id: globalConfig } + AboutDialog { + id: aboutDialog + } + + DepsDialog { + id: depDialog + } + menuBar: MenuBar { Menu { title: qsTr("&File") @@ -206,34 +214,91 @@ ApplicationWindow { MenuSeparator {} Menu { - title: qsTr("&Amiibo") + title: qsTr("Am&iibo") + } + + Menu { + title: qsTr("&Applets") + + Action { + text: qsTr("Open &Album") + } + + Action { + text: qsTr("Open &Mii Editor") + } + + Action { + text: qsTr("Open &Controller Menu") + } + + Action { + text: qsTr("Open &Home Menu") + } + + Action { + text: qsTr("Open &Setup") + } + } + + Menu { + title: qsTr("&Create Home Menu Shortcut") + + Action { + text: qsTr("&Desktop") + onTriggered: MainWindowInterface.createHomeMenuDesktopShortcut() + } + + Action { + text: qsTr("&Application Menu") + onTriggered: MainWindowInterface.createHomeMenuApplicationMenuShortcut() + } } + MenuSeparator {} + Action { - text: qsTr("Open A&lbum") + text: qsTr("&Capture Screenshot") + shortcut: "Ctrl+P" } + Menu { + title: "&TAS" + } + } + + Menu { + title: qsTr("&Multiplayer") + } + + Menu { + title: qsTr("&Help") + Action { - text: qsTr("Open &Mii Editor") + text: qsTr("Open &Mods Page") + onTriggered: MainWindowInterface.openModsPage() } Action { - text: qsTr("Open Co&ntroller Menu") + text: qsTr("Open &Quickstart Guide") + onTriggered: MainWindowInterface.openQuickstartGuide() } Action { - text: qsTr("Open &Home Menu") + text: qsTr("&FAQ") + onTriggered: MainWindowInterface.openFAQ() } MenuSeparator {} Action { - text: qsTr("&Capture Screenshot") - shortcut: "Ctrl+P" + text: qsTr("&About Eden") + onTriggered: aboutDialog.show() } - Menu { - title: "&TAS" + Action { + text: qsTr("&Eden Dependencies") + onTriggered: depDialog.show() } } } diff --git a/src/Eden/Models/CMakeLists.txt b/src/Eden/Models/CMakeLists.txt index dc3f4b00ac..6d24b8575b 100644 --- a/src/Eden/Models/CMakeLists.txt +++ b/src/Eden/Models/CMakeLists.txt @@ -11,6 +11,7 @@ qt_add_library(EdenModels STATIC SettingsModel.h SettingsModel.cpp GameListWorker.h GameListWorker.cpp GameIconProvider.h GameIconProvider.cpp + DependencyModel.h DependencyModel.cpp ) target_link_libraries(EdenModels PRIVATE Qt6::Core Qt6::Quick Qt6::Gui) diff --git a/src/Eden/Models/DependencyModel.cpp b/src/Eden/Models/DependencyModel.cpp new file mode 100644 index 0000000000..2f183dc869 --- /dev/null +++ b/src/Eden/Models/DependencyModel.cpp @@ -0,0 +1,50 @@ +#include +#include "DependencyModel.h" + +#include "dep_hashes.h" + +DependencyModel::DependencyModel(QObject* parent) : QAbstractTableModel(parent) { + for (size_t i = 0; i < Common::dep_hashes.size(); ++i) { + QString name = QString::fromLocal8Bit(Common::dep_names.at(i)); + QString version = QString::fromLocal8Bit(Common::dep_hashes.at(i)); + QString url = QString::fromLocal8Bit(Common::dep_urls.at(i)); + + m_data.append(Dependency{name, version, url}); + } +} + +QVariant DependencyModel::headerData(int section, Qt::Orientation orientation, int role) const { + if (orientation == Qt::Vertical) + return QString(); + + switch (section) { + case 0: + return tr("Dependency"); + case 1: + default: + return tr("Version"); + } +} + +int DependencyModel::rowCount(const QModelIndex& parent) const { + return m_data.size(); +} + +int DependencyModel::columnCount(const QModelIndex& parent) const { + return 2; +} + +QVariant DependencyModel::data(const QModelIndex& index, int role) const { + if (!index.isValid() || role != Qt::DisplayRole) + return QVariant(); + + Dependency d = m_data[index.row()]; + + switch (index.column()) { + case 0: + return QStringLiteral("%2").arg(d.url, d.name); + case 1: + default: + return d.version; + } +} diff --git a/src/Eden/Models/DependencyModel.h b/src/Eden/Models/DependencyModel.h new file mode 100644 index 0000000000..2c0ae977e2 --- /dev/null +++ b/src/Eden/Models/DependencyModel.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +struct Dependency { + QString name; + QString version; + QString url; +}; + +class DependencyModel : public QAbstractTableModel { + Q_OBJECT + +public: + explicit DependencyModel(QObject* parent = nullptr); + + // Header: + QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const override; + + // Basic functionality: + 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; + +private: + QList m_data; +}; diff --git a/src/Eden/Native/CMakeLists.txt b/src/Eden/Native/CMakeLists.txt index 04df283a0e..3eac05e972 100644 --- a/src/Eden/Native/CMakeLists.txt +++ b/src/Eden/Native/CMakeLists.txt @@ -10,8 +10,7 @@ qt_add_executable(eden EdenApplication.h EdenApplication.cpp LibQtCommon.cpp - LibQtCommon.h -) + LibQtCommon.h) set(MODULES Eden::Util @@ -20,8 +19,7 @@ set(MODULES Eden::Interface Eden::Constants Eden::Main - Eden::Models -) + Eden::Models) # if (ENABLE_SDL2) # add_subdirectory(Gamepad) @@ -38,60 +36,13 @@ target_link_libraries(eden Carboxyl::Carboxyl - ${MODULES} -) + ${MODULES}) target_link_libraries(eden PRIVATE common core input_common frontend_common qt_common network video_core) target_link_libraries(eden PRIVATE Boost::headers glad fmt) target_link_libraries(eden PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads) -target_compile_definitions(eden PRIVATE - # Use QStringBuilder for string concatenation to reduce - # the overall number of temporary strings created. - -DQT_USE_QSTRINGBUILDER - - # Disable implicit type narrowing in signal/slot connect() calls. - -DQT_NO_NARROWING_CONVERSIONS_IN_CONNECT - - # Disable unsafe overloads of QProcess' start() function. - -DQT_NO_PROCESS_COMBINED_ARGUMENT_START - - # Disable implicit QString->QUrl conversions to enforce use of proper resolving functions. - -DQT_NO_URL_CAST_FROM_STRING -) - -if (APPLE) - set(MACOSX_ICON "../../dist/eden.icns") - set_source_files_properties(${MACOSX_ICON} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) - target_sources(eden PRIVATE ${MACOSX_ICON}) - set_target_properties(eden PROPERTIES MACOSX_BUNDLE TRUE) - set_target_properties(eden PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist) - - if (YUZU_USE_BUNDLED_MOLTENVK) - set(MOLTENVK_PLATFORM "macOS") - set(MOLTENVK_VERSION "v1.4.0") - download_moltenvk(${MOLTENVK_PLATFORM} ${MOLTENVK_VERSION}) - endif() - - set(CMAKE_FIND_LIBRARY_SUFFIXES ".dylib") - find_library(MOLTENVK_LIBRARY MoltenVK REQUIRED) - message(STATUS "Using MoltenVK at ${MOLTENVK_LIBRARY}.") - - set_source_files_properties(${MOLTENVK_LIBRARY} PROPERTIES - MACOSX_PACKAGE_LOCATION Frameworks - XCODE_FILE_ATTRIBUTES "CodeSignOnCopy") - target_sources(eden PRIVATE ${MOLTENVK_LIBRARY}) -elseif(WIN32) - # compile as a win32 gui application instead of a console application - target_link_libraries(eden PRIVATE Qt6::EntryPointPrivate) - if(MSVC) - target_link_libraries(eden PRIVATE version.lib) - set_target_properties(eden PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS") - elseif(MINGW) - set_target_properties(eden PROPERTIES LINK_FLAGS_RELEASE "-Wl,--subsystem,windows") - target_link_libraries(eden PRIVATE dwmapi) - endif() -endif() +yuzu_qt_target(eden) if (ENABLE_QT_WIDGETS) set_target_properties(eden PROPERTIES OUTPUT_NAME "eden-qml") diff --git a/src/Eden/Native/EdenApplication.cpp b/src/Eden/Native/EdenApplication.cpp index e46d1db412..83e353be15 100644 --- a/src/Eden/Native/EdenApplication.cpp +++ b/src/Eden/Native/EdenApplication.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include "Eden/Interface/MainWindowInterface.h" +#include "Eden/Models/DependencyModel.h" #include "EdenApplication.h" #include @@ -94,6 +95,10 @@ int EdenApplication::run() { GameListModel *gameListModel = new GameListModel(this, &engine); ctx->setContextProperty(QStringLiteral("EdenGameList"), gameListModel); + // Dependency Model + DependencyModel* depModel = new DependencyModel(this); + ctx->setContextProperty(QStringLiteral("DependencyModel"), depModel); + // Settings Interface SettingsInterface *interface = new SettingsInterface(&engine); ctx->setContextProperty(QStringLiteral("SettingsInterface"), interface); diff --git a/src/Eden/Native/main.cpp b/src/Eden/Native/main.cpp index 3ad8fabbae..78ee09afc0 100644 --- a/src/Eden/Native/main.cpp +++ b/src/Eden/Native/main.cpp @@ -6,12 +6,22 @@ #undef VMA_IMPLEMENTATION #endif +#include #include "EdenApplication.h" int main(int argc, char *argv[]) { EdenApplication app(argc, argv); + QDirIterator iter(QDir(QStringLiteral(":/")), QDirIterator::Subdirectories); + + while (iter.hasNext()) { + QString next = iter.next(); + if (!next.contains(QStringLiteral("k")) && !next.contains(QStringLiteral("breeze"))) { + qDebug() << next; + } + } + return app.run(); } diff --git a/src/qt_common/CMakeLists.txt b/src/qt_common/CMakeLists.txt index 3d77b756b6..ffd7e3845e 100644 --- a/src/qt_common/CMakeLists.txt +++ b/src/qt_common/CMakeLists.txt @@ -38,6 +38,7 @@ add_library(qt_common STATIC util/fs.h util/fs.cpp util/vk_device_info.cpp util/vk_device_info.h + qt_constants.h ) if (UNIX) diff --git a/src/qt_common/externals/cpmfile.json b/src/qt_common/externals/cpmfile.json index 11fb5b9620..85e8858a63 100644 --- a/src/qt_common/externals/cpmfile.json +++ b/src/qt_common/externals/cpmfile.json @@ -19,8 +19,8 @@ "package": "Carboxyl", "repo": "crueter/Carboxyl", "git_host": "git.crueter.xyz", - "sha": "6c37572331", - "hash": "e2a7f14d6b6bc9285e1b525fee653a47bc0f971343addb1223873c11f683f0c9e360dedfa496c5074895f0788092ea401d42e7e61e98a244c38373de49a0f638", + "sha": "ff49f168b0", + "hash": "a4a46be3aea6bb666c274c1ae323abb4b26746a4976e0595b9893cfba5297b78c267526ba99e02e6ce8d48da605f9ea448d8cf6bbd01e81953754a2131c65d2b", "bundled": "true", "options": [ "CARBOXYL_DEMO OFF" diff --git a/src/qt_common/qt_constants.h b/src/qt_common/qt_constants.h new file mode 100644 index 0000000000..46ed8c2f15 --- /dev/null +++ b/src/qt_common/qt_constants.h @@ -0,0 +1,7 @@ +#pragma once + +namespace QtCommon::Constants { +static constexpr const char* modPage = "https://github.com/eden-emulator/yuzu-mod-archive"; +static constexpr const char* quickstartPage = "https://yuzu-mirror.github.io/help/quickstart/"; +static constexpr const char* helpPage = "https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user"; +} diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 3a4df096be..f69e1c48dc 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -255,142 +255,7 @@ if (CXX_CLANG) ) endif() -file(GLOB COMPAT_LIST - ${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.qrc - ${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.json) -file(GLOB_RECURSE ICONS ${PROJECT_SOURCE_DIR}/dist/icons/*) -file(GLOB_RECURSE THEMES ${PROJECT_SOURCE_DIR}/dist/qt_themes/*) - -if (ENABLE_UPDATE_CHECKER) - target_compile_definitions(yuzu PUBLIC ENABLE_UPDATE_CHECKER) -endif() - -if (ENABLE_QT_TRANSLATION) - set(YUZU_QT_LANGUAGES "${PROJECT_SOURCE_DIR}/dist/languages" CACHE PATH "Path to the translation bundle for the Qt frontend") - option(GENERATE_QT_TRANSLATION "Generate en.ts as the translation source file" OFF) - option(WORKAROUND_BROKEN_LUPDATE "Run lupdate directly through CMake if Qt's convenience wrappers don't work" OFF) - - # Update source TS file if enabled - if (GENERATE_QT_TRANSLATION) - get_target_property(SRCS yuzu SOURCES) - # these calls to qt_create_translation also creates a rule to generate en.qm which conflicts with providing english plurals - # so we have to set a OUTPUT_LOCATION so that we don't have multiple rules to generate en.qm - set_source_files_properties(${YUZU_QT_LANGUAGES}/en.ts PROPERTIES OUTPUT_LOCATION "${CMAKE_CURRENT_BINARY_DIR}/translations") - if (WORKAROUND_BROKEN_LUPDATE) - add_custom_command(OUTPUT ${YUZU_QT_LANGUAGES}/en.ts - COMMAND lupdate - -source-language en_US - -target-language en_US - ${SRCS} - ${UIS} - -ts ${YUZU_QT_LANGUAGES}/en.ts - DEPENDS - ${SRCS} - ${UIS} - WORKING_DIRECTORY - ${CMAKE_CURRENT_SOURCE_DIR} - ) - else() - qt_create_translation(QM_FILES - ${SRCS} - ${UIS} - ${YUZU_QT_LANGUAGES}/en.ts - OPTIONS - -source-language en_US - -target-language en_US - ) - endif() - - # Generate plurals into dist/english_plurals/generated_en.ts so it can be used to revise dist/english_plurals/en.ts - set(GENERATED_PLURALS_FILE ${PROJECT_SOURCE_DIR}/dist/english_plurals/generated_en.ts) - set_source_files_properties(${GENERATED_PLURALS_FILE} PROPERTIES OUTPUT_LOCATION "${CMAKE_CURRENT_BINARY_DIR}/plurals") - if (WORKAROUND_BROKEN_LUPDATE) - add_custom_command(OUTPUT ${GENERATED_PLURALS_FILE} - COMMAND lupdate - -source-language en_US - -target-language en_US - ${SRCS} - ${UIS} - -ts ${GENERATED_PLURALS_FILE} - DEPENDS - ${SRCS} - ${UIS} - WORKING_DIRECTORY - ${CMAKE_CURRENT_SOURCE_DIR} - ) - else() - qt_create_translation(QM_FILES ${SRCS} ${UIS} ${GENERATED_PLURALS_FILE} OPTIONS -pluralonly -source-language en_US -target-language en_US) - endif() - - add_custom_target(translation ALL DEPENDS ${YUZU_QT_LANGUAGES}/en.ts ${GENERATED_PLURALS_FILE}) - endif() - - # Find all TS files except en.ts - file(GLOB_RECURSE LANGUAGES_TS ${YUZU_QT_LANGUAGES}/*.ts) - list(REMOVE_ITEM LANGUAGES_TS ${YUZU_QT_LANGUAGES}/en.ts) - - # Compile TS files to QM files - qt_add_translation(LANGUAGES_QM ${LANGUAGES_TS}) - - # Compile english plurals TS file to en.qm - qt_add_translation(LANGUAGES_QM ${PROJECT_SOURCE_DIR}/dist/english_plurals/en.ts) - - # Build a QRC file from the QM file list - set(LANGUAGES_QRC ${CMAKE_CURRENT_BINARY_DIR}/languages.qrc) - file(WRITE ${LANGUAGES_QRC} "\n") - foreach (QM ${LANGUAGES_QM}) - get_filename_component(QM_FILE ${QM} NAME) - file(APPEND ${LANGUAGES_QRC} "${QM_FILE}\n") - endforeach (QM) - file(APPEND ${LANGUAGES_QRC} "") - - # Add the QRC file to package in all QM files - qt_add_resources(LANGUAGES ${LANGUAGES_QRC}) -else() - set(LANGUAGES) -endif() - -target_sources(yuzu - PRIVATE - ${COMPAT_LIST} - ${ICONS} - ${LANGUAGES} - ${THEMES} -) - -if (APPLE) - # Normal icns - set(MACOSX_ICON "../../dist/eden.icns") - set_source_files_properties(${MACOSX_ICON} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) - target_sources(yuzu PRIVATE ${MACOSX_ICON}) - - # Liquid glass - set(MACOSX_LIQUID_GLASS_ICON "../../dist/Assets.car") - set_source_files_properties(${MACOSX_LIQUID_GLASS_ICON} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) - target_sources(yuzu PRIVATE ${MACOSX_LIQUID_GLASS_ICON}) - - set_target_properties(yuzu PROPERTIES MACOSX_BUNDLE TRUE) - set_target_properties(yuzu PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist) - - set(CMAKE_FIND_LIBRARY_SUFFIXES ".dylib") - find_library(MOLTENVK_LIBRARY MoltenVK REQUIRED) - message(STATUS "Using MoltenVK at ${MOLTENVK_LIBRARY}.") - - set_source_files_properties(${MOLTENVK_LIBRARY} PROPERTIES - MACOSX_PACKAGE_LOCATION Frameworks - XCODE_FILE_ATTRIBUTES "CodeSignOnCopy") - target_sources(yuzu PRIVATE ${MOLTENVK_LIBRARY}) -elseif(WIN32) - # compile as a win32 gui application instead of a console application - target_link_libraries(yuzu PRIVATE Qt6::EntryPointPrivate) - if(MSVC) - target_link_libraries(yuzu PRIVATE version.lib) - set_target_properties(yuzu PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS") - elseif(MINGW) - set_target_properties(yuzu PROPERTIES LINK_FLAGS_RELEASE "-Wl,--subsystem,windows") - target_link_libraries(yuzu PRIVATE dwmapi) - endif() -endif() +yuzu_qt_target(yuzu) target_link_libraries(yuzu PRIVATE nlohmann_json::nlohmann_json) target_link_libraries(yuzu PRIVATE common core input_common frontend_common network video_core qt_common) diff --git a/src/yuzu/main_window.cpp b/src/yuzu/main_window.cpp index 9d5b6e8ce0..865a71efa1 100644 --- a/src/yuzu/main_window.cpp +++ b/src/yuzu/main_window.cpp @@ -78,6 +78,7 @@ #include "qt_common/abstract/frontend.h" #include "qt_common/qt_common.h" +#include "qt_common/qt_constants.h" #include "qt_common/util/path.h" #include "qt_common/util/content.h" @@ -2732,6 +2733,7 @@ void MainWindow::IncrementInstallProgress() { install_progress->setValue(install_progress->value() + 1); } +// TODO(qml): Implement InstallDialog + Interface in QML void MainWindow::OnMenuInstallToNAND() { const QString file_filter = tr("Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive " @@ -3095,15 +3097,15 @@ void MainWindow::OpenURL(const QUrl& url) { } void MainWindow::OnOpenModsPage() { - OpenURL(QUrl(QStringLiteral("https://github.com/eden-emulator/yuzu-mod-archive"))); + OpenURL(QUrl(QString::fromLocal8Bit(QtCommon::Constants::modPage))); } void MainWindow::OnOpenQuickstartGuide() { - OpenURL(QUrl(QStringLiteral("https://yuzu-mirror.github.io/help/quickstart/"))); + OpenURL(QUrl(QString::fromLocal8Bit(QtCommon::Constants::quickstartPage))); } void MainWindow::OnOpenFAQ() { - OpenURL(QUrl(QStringLiteral("https://yuzu-mirror.github.io/help"))); + OpenURL(QUrl(QString::fromLocal8Bit(QtCommon::Constants::helpPage))); } void MainWindow::ToggleFullscreen() {