diff options
Diffstat (limited to 'src/yuzu')
26 files changed, 1255 insertions, 82 deletions
| diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index cfca8f4a8..17ecaafde 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -7,6 +7,8 @@ add_executable(yuzu      Info.plist      about_dialog.cpp      about_dialog.h +    applets/profile_select.cpp +    applets/profile_select.h      applets/software_keyboard.cpp      applets/software_keyboard.h      bootmanager.cpp @@ -31,10 +33,14 @@ add_executable(yuzu      configuration/configure_input.h      configuration/configure_input_player.cpp      configuration/configure_input_player.h +    configuration/configure_input_simple.cpp +    configuration/configure_input_simple.h      configuration/configure_mouse_advanced.cpp      configuration/configure_mouse_advanced.h      configuration/configure_system.cpp      configuration/configure_system.h +    configuration/configure_per_general.cpp +    configuration/configure_per_general.h      configuration/configure_touchscreen_advanced.cpp      configuration/configure_touchscreen_advanced.h      configuration/configure_web.cpp @@ -85,7 +91,9 @@ set(UIS      configuration/configure_graphics.ui      configuration/configure_input.ui      configuration/configure_input_player.ui +    configuration/configure_input_simple.ui      configuration/configure_mouse_advanced.ui +    configuration/configure_per_general.ui      configuration/configure_system.ui      configuration/configure_touchscreen_advanced.ui      configuration/configure_web.ui diff --git a/src/yuzu/applets/profile_select.cpp b/src/yuzu/applets/profile_select.cpp new file mode 100644 index 000000000..5c1b65a2c --- /dev/null +++ b/src/yuzu/applets/profile_select.cpp @@ -0,0 +1,168 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <mutex> +#include <QDialogButtonBox> +#include <QLabel> +#include <QLineEdit> +#include <QScrollArea> +#include <QStandardItemModel> +#include <QVBoxLayout> +#include "common/file_util.h" +#include "common/string_util.h" +#include "core/hle/lock.h" +#include "yuzu/applets/profile_select.h" +#include "yuzu/main.h" + +// Same backup JPEG used by acc IProfile::GetImage if no jpeg found +constexpr std::array<u8, 107> backup_jpeg{ +    0xff, 0xd8, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x02, 0x02, +    0x02, 0x03, 0x03, 0x03, 0x03, 0x04, 0x06, 0x04, 0x04, 0x04, 0x04, 0x04, 0x08, 0x06, 0x06, 0x05, +    0x06, 0x09, 0x08, 0x0a, 0x0a, 0x09, 0x08, 0x09, 0x09, 0x0a, 0x0c, 0x0f, 0x0c, 0x0a, 0x0b, 0x0e, +    0x0b, 0x09, 0x09, 0x0d, 0x11, 0x0d, 0x0e, 0x0f, 0x10, 0x10, 0x11, 0x10, 0x0a, 0x0c, 0x12, 0x13, +    0x12, 0x10, 0x13, 0x0f, 0x10, 0x10, 0x10, 0xff, 0xc9, 0x00, 0x0b, 0x08, 0x00, 0x01, 0x00, 0x01, +    0x01, 0x01, 0x11, 0x00, 0xff, 0xcc, 0x00, 0x06, 0x00, 0x10, 0x10, 0x05, 0xff, 0xda, 0x00, 0x08, +    0x01, 0x01, 0x00, 0x00, 0x3f, 0x00, 0xd2, 0xcf, 0x20, 0xff, 0xd9, +}; + +QString FormatUserEntryText(const QString& username, Service::Account::UUID uuid) { +    return QtProfileSelectionDialog::tr( +               "%1\n%2", "%1 is the profile username, %2 is the formatted UUID (e.g. " +                         "00112233-4455-6677-8899-AABBCCDDEEFF))") +        .arg(username, QString::fromStdString(uuid.FormatSwitch())); +} + +QString GetImagePath(Service::Account::UUID uuid) { +    const auto path = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + +                      "/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg"; +    return QString::fromStdString(path); +} + +QPixmap GetIcon(Service::Account::UUID uuid) { +    QPixmap icon{GetImagePath(uuid)}; + +    if (!icon) { +        icon.fill(Qt::black); +        icon.loadFromData(backup_jpeg.data(), static_cast<u32>(backup_jpeg.size())); +    } + +    return icon.scaled(64, 64, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); +} + +QtProfileSelectionDialog::QtProfileSelectionDialog(QWidget* parent) +    : QDialog(parent), profile_manager(std::make_unique<Service::Account::ProfileManager>()) { +    outer_layout = new QVBoxLayout; + +    instruction_label = new QLabel(tr("Select a user:")); + +    scroll_area = new QScrollArea; + +    buttons = new QDialogButtonBox; +    buttons->addButton(tr("Cancel"), QDialogButtonBox::RejectRole); +    buttons->addButton(tr("OK"), QDialogButtonBox::AcceptRole); + +    connect(buttons, &QDialogButtonBox::accepted, this, &QtProfileSelectionDialog::accept); +    connect(buttons, &QDialogButtonBox::rejected, this, &QtProfileSelectionDialog::reject); + +    outer_layout->addWidget(instruction_label); +    outer_layout->addWidget(scroll_area); +    outer_layout->addWidget(buttons); + +    layout = new QVBoxLayout; +    tree_view = new QTreeView; +    item_model = new QStandardItemModel(tree_view); +    tree_view->setModel(item_model); + +    tree_view->setAlternatingRowColors(true); +    tree_view->setSelectionMode(QHeaderView::SingleSelection); +    tree_view->setSelectionBehavior(QHeaderView::SelectRows); +    tree_view->setVerticalScrollMode(QHeaderView::ScrollPerPixel); +    tree_view->setHorizontalScrollMode(QHeaderView::ScrollPerPixel); +    tree_view->setSortingEnabled(true); +    tree_view->setEditTriggers(QHeaderView::NoEditTriggers); +    tree_view->setUniformRowHeights(true); +    tree_view->setIconSize({64, 64}); +    tree_view->setContextMenuPolicy(Qt::NoContextMenu); + +    item_model->insertColumns(0, 1); +    item_model->setHeaderData(0, Qt::Horizontal, "Users"); + +    // We must register all custom types with the Qt Automoc system so that we are able to use it +    // with signals/slots. In this case, QList falls under the umbrells of custom types. +    qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>"); + +    layout->setContentsMargins(0, 0, 0, 0); +    layout->setSpacing(0); +    layout->addWidget(tree_view); + +    scroll_area->setLayout(layout); + +    connect(tree_view, &QTreeView::clicked, this, &QtProfileSelectionDialog::SelectUser); + +    const auto& profiles = profile_manager->GetAllUsers(); +    for (const auto& user : profiles) { +        Service::Account::ProfileBase profile; +        if (!profile_manager->GetProfileBase(user, profile)) +            continue; + +        const auto username = Common::StringFromFixedZeroTerminatedBuffer( +            reinterpret_cast<const char*>(profile.username.data()), profile.username.size()); + +        list_items.push_back(QList<QStandardItem*>{new QStandardItem{ +            GetIcon(user), FormatUserEntryText(QString::fromStdString(username), user)}}); +    } + +    for (const auto& item : list_items) +        item_model->appendRow(item); + +    setLayout(outer_layout); +    setWindowTitle(tr("Profile Selector")); +    resize(550, 400); +} + +QtProfileSelectionDialog::~QtProfileSelectionDialog() = default; + +void QtProfileSelectionDialog::accept() { +    ok = true; +    QDialog::accept(); +} + +void QtProfileSelectionDialog::reject() { +    ok = false; +    user_index = 0; +    QDialog::reject(); +} + +bool QtProfileSelectionDialog::GetStatus() const { +    return ok; +} + +u32 QtProfileSelectionDialog::GetIndex() const { +    return user_index; +} + +void QtProfileSelectionDialog::SelectUser(const QModelIndex& index) { +    user_index = index.row(); +} + +QtProfileSelector::QtProfileSelector(GMainWindow& parent) { +    connect(this, &QtProfileSelector::MainWindowSelectProfile, &parent, +            &GMainWindow::ProfileSelectorSelectProfile, Qt::QueuedConnection); +    connect(&parent, &GMainWindow::ProfileSelectorFinishedSelection, this, +            &QtProfileSelector::MainWindowFinishedSelection, Qt::DirectConnection); +} + +QtProfileSelector::~QtProfileSelector() = default; + +void QtProfileSelector::SelectProfile( +    std::function<void(std::optional<Service::Account::UUID>)> callback) const { +    this->callback = std::move(callback); +    emit MainWindowSelectProfile(); +} + +void QtProfileSelector::MainWindowFinishedSelection(std::optional<Service::Account::UUID> uuid) { +    // Acquire the HLE mutex +    std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock); +    callback(uuid); +} diff --git a/src/yuzu/applets/profile_select.h b/src/yuzu/applets/profile_select.h new file mode 100644 index 000000000..868573324 --- /dev/null +++ b/src/yuzu/applets/profile_select.h @@ -0,0 +1,73 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <vector> +#include <QDialog> +#include <QList> +#include "core/frontend/applets/profile_select.h" + +class GMainWindow; +class QDialogButtonBox; +class QGraphicsScene; +class QLabel; +class QScrollArea; +class QStandardItem; +class QStandardItemModel; +class QTreeView; +class QVBoxLayout; + +class QtProfileSelectionDialog final : public QDialog { +    Q_OBJECT + +public: +    explicit QtProfileSelectionDialog(QWidget* parent); +    ~QtProfileSelectionDialog() override; + +    void accept() override; +    void reject() override; + +    bool GetStatus() const; +    u32 GetIndex() const; + +private: +    bool ok = false; +    u32 user_index = 0; + +    void SelectUser(const QModelIndex& index); + +    QVBoxLayout* layout; +    QTreeView* tree_view; +    QStandardItemModel* item_model; +    QGraphicsScene* scene; + +    std::vector<QList<QStandardItem*>> list_items; + +    QVBoxLayout* outer_layout; +    QLabel* instruction_label; +    QScrollArea* scroll_area; +    QDialogButtonBox* buttons; + +    std::unique_ptr<Service::Account::ProfileManager> profile_manager; +}; + +class QtProfileSelector final : public QObject, public Core::Frontend::ProfileSelectApplet { +    Q_OBJECT + +public: +    explicit QtProfileSelector(GMainWindow& parent); +    ~QtProfileSelector() override; + +    void SelectProfile( +        std::function<void(std::optional<Service::Account::UUID>)> callback) const override; + +signals: +    void MainWindowSelectProfile() const; + +private: +    void MainWindowFinishedSelection(std::optional<Service::Account::UUID> uuid); + +    mutable std::function<void(std::optional<Service::Account::UUID>)> callback; +}; diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 02e09fa18..c4349ccc8 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -4,6 +4,7 @@  #include <QSettings>  #include "common/file_util.h" +#include "configure_input_simple.h"  #include "core/hle/service/acc/profile_manager.h"  #include "core/hle/service/hid/controllers/npad.h"  #include "input_common/main.h" @@ -339,6 +340,13 @@ void Config::ReadTouchscreenValues() {      qt_config->endGroup();  } +void Config::ApplyDefaultProfileIfInputInvalid() { +    if (!std::any_of(Settings::values.players.begin(), Settings::values.players.end(), +                     [](const Settings::PlayerInput& in) { return in.connected; })) { +        ApplyInputProfileConfiguration(UISettings::values.profile_index); +    } +} +  void Config::ReadValues() {      qt_config->beginGroup("Controls"); @@ -441,6 +449,21 @@ void Config::ReadValues() {      Settings::values.yuzu_token = qt_config->value("yuzu_token").toString().toStdString();      qt_config->endGroup(); +    const auto size = qt_config->beginReadArray("DisabledAddOns"); +    for (int i = 0; i < size; ++i) { +        qt_config->setArrayIndex(i); +        const auto title_id = qt_config->value("title_id", 0).toULongLong(); +        std::vector<std::string> out; +        const auto d_size = qt_config->beginReadArray("disabled"); +        for (int j = 0; j < d_size; ++j) { +            qt_config->setArrayIndex(j); +            out.push_back(qt_config->value("d", "").toString().toStdString()); +        } +        qt_config->endArray(); +        Settings::values.disabled_addons.insert_or_assign(title_id, out); +    } +    qt_config->endArray(); +      qt_config->beginGroup("UI");      UISettings::values.theme = qt_config->value("theme", UISettings::themes[0].second).toString();      UISettings::values.enable_discord_presence = @@ -505,6 +528,9 @@ void Config::ReadValues() {      UISettings::values.first_start = qt_config->value("firstStart", true).toBool();      UISettings::values.callout_flags = qt_config->value("calloutFlags", 0).toUInt();      UISettings::values.show_console = qt_config->value("showConsole", false).toBool(); +    UISettings::values.profile_index = qt_config->value("profileIndex", 0).toUInt(); + +    ApplyDefaultProfileIfInputInvalid();      qt_config->endGroup();  } @@ -647,6 +673,21 @@ void Config::SaveValues() {      qt_config->setValue("yuzu_token", QString::fromStdString(Settings::values.yuzu_token));      qt_config->endGroup(); +    qt_config->beginWriteArray("DisabledAddOns"); +    int i = 0; +    for (const auto& elem : Settings::values.disabled_addons) { +        qt_config->setArrayIndex(i); +        qt_config->setValue("title_id", QVariant::fromValue<u64>(elem.first)); +        qt_config->beginWriteArray("disabled"); +        for (std::size_t j = 0; j < elem.second.size(); ++j) { +            qt_config->setArrayIndex(j); +            qt_config->setValue("d", QString::fromStdString(elem.second[j])); +        } +        qt_config->endArray(); +        ++i; +    } +    qt_config->endArray(); +      qt_config->beginGroup("UI");      qt_config->setValue("theme", UISettings::values.theme);      qt_config->setValue("enable_discord_presence", UISettings::values.enable_discord_presence); @@ -695,6 +736,7 @@ void Config::SaveValues() {      qt_config->setValue("firstStart", UISettings::values.first_start);      qt_config->setValue("calloutFlags", UISettings::values.callout_flags);      qt_config->setValue("showConsole", UISettings::values.show_console); +    qt_config->setValue("profileIndex", UISettings::values.profile_index);      qt_config->endGroup();  } diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h index a1c27bbf9..e73ad19bb 100644 --- a/src/yuzu/configuration/config.h +++ b/src/yuzu/configuration/config.h @@ -34,6 +34,7 @@ private:      void ReadKeyboardValues();      void ReadMouseValues();      void ReadTouchscreenValues(); +    void ApplyDefaultProfileIfInputInvalid();      void SaveValues();      void SavePlayerValues(); diff --git a/src/yuzu/configuration/configure.ui b/src/yuzu/configuration/configure.ui index 9b297df28..8706b80d2 100644 --- a/src/yuzu/configuration/configure.ui +++ b/src/yuzu/configuration/configure.ui @@ -7,7 +7,7 @@      <x>0</x>      <y>0</y>      <width>461</width> -    <height>500</height> +    <height>659</height>     </rect>    </property>    <property name="windowTitle"> @@ -24,17 +24,17 @@         <string>General</string>        </attribute>       </widget> -      <widget class="ConfigureGameList" name="gameListTab"> -        <attribute name="title"> -          <string>Game List</string> -        </attribute> -      </widget> +     <widget class="ConfigureGameList" name="gameListTab"> +      <attribute name="title"> +       <string>Game List</string> +      </attribute> +     </widget>       <widget class="ConfigureSystem" name="systemTab">        <attribute name="title">         <string>System</string>        </attribute>       </widget> -     <widget class="ConfigureInput" name="inputTab"> +     <widget class="ConfigureInputSimple" name="inputTab">        <attribute name="title">         <string>Input</string>        </attribute> @@ -54,11 +54,11 @@         <string>Debug</string>        </attribute>       </widget> -      <widget class="ConfigureWeb" name="webTab"> -        <attribute name="title"> -          <string>Web</string> -        </attribute> -      </widget> +     <widget class="ConfigureWeb" name="webTab"> +      <attribute name="title"> +       <string>Web</string> +      </attribute> +     </widget>      </widget>     </item>     <item> @@ -77,12 +77,12 @@     <header>configuration/configure_general.h</header>     <container>1</container>    </customwidget> -   <customwidget> -     <class>ConfigureGameList</class> -     <extends>QWidget</extends> -     <header>configuration/configure_gamelist.h</header> -     <container>1</container> -   </customwidget> +  <customwidget> +   <class>ConfigureGameList</class> +   <extends>QWidget</extends> +   <header>configuration/configure_gamelist.h</header> +   <container>1</container> +  </customwidget>    <customwidget>     <class>ConfigureSystem</class>     <extends>QWidget</extends> @@ -102,9 +102,9 @@     <container>1</container>    </customwidget>    <customwidget> -   <class>ConfigureInput</class> +   <class>ConfigureInputSimple</class>     <extends>QWidget</extends> -   <header>configuration/configure_input.h</header> +   <header>configuration/configure_input_simple.h</header>     <container>1</container>    </customwidget>    <customwidget> @@ -113,12 +113,12 @@     <header>configuration/configure_graphics.h</header>     <container>1</container>    </customwidget> -   <customwidget> -     <class>ConfigureWeb</class> -     <extends>QWidget</extends> -     <header>configuration/configure_web.h</header> -     <container>1</container> -   </customwidget> +  <customwidget> +   <class>ConfigureWeb</class> +   <extends>QWidget</extends> +   <header>configuration/configure_web.h</header> +   <container>1</container> +  </customwidget>   </customwidgets>   <resources/>   <connections> diff --git a/src/yuzu/configuration/configure_audio.cpp b/src/yuzu/configuration/configure_audio.cpp index eb1da0f9e..5d9ccc6e8 100644 --- a/src/yuzu/configuration/configure_audio.cpp +++ b/src/yuzu/configuration/configure_audio.cpp @@ -17,8 +17,8 @@ ConfigureAudio::ConfigureAudio(QWidget* parent)      ui->output_sink_combo_box->clear();      ui->output_sink_combo_box->addItem("auto"); -    for (const auto& sink_detail : AudioCore::g_sink_details) { -        ui->output_sink_combo_box->addItem(sink_detail.id); +    for (const char* id : AudioCore::GetSinkIDs()) { +        ui->output_sink_combo_box->addItem(id);      }      connect(ui->volume_slider, &QSlider::valueChanged, this, @@ -97,8 +97,7 @@ void ConfigureAudio::updateAudioDevices(int sink_index) {      ui->audio_device_combo_box->addItem(AudioCore::auto_device_name);      const std::string sink_id = ui->output_sink_combo_box->itemText(sink_index).toStdString(); -    const std::vector<std::string> device_list = AudioCore::GetSinkDetails(sink_id).list_devices(); -    for (const auto& device : device_list) { +    for (const auto& device : AudioCore::GetDeviceListForSink(sink_id)) {          ui->audio_device_combo_box->addItem(QString::fromStdString(device));      }  } diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp index 830d26115..f39d57998 100644 --- a/src/yuzu/configuration/configure_input.cpp +++ b/src/yuzu/configuration/configure_input.cpp @@ -20,6 +20,33 @@  #include "yuzu/configuration/configure_input_player.h"  #include "yuzu/configuration/configure_mouse_advanced.h" +void OnDockedModeChanged(bool last_state, bool new_state) { +    if (last_state == new_state) { +        return; +    } + +    Core::System& system{Core::System::GetInstance()}; +    if (!system.IsPoweredOn()) { +        return; +    } +    Service::SM::ServiceManager& sm = system.ServiceManager(); + +    // Message queue is shared between these services, we just need to signal an operation +    // change to one and it will handle both automatically +    auto applet_oe = sm.GetService<Service::AM::AppletOE>("appletOE"); +    auto applet_ae = sm.GetService<Service::AM::AppletAE>("appletAE"); +    bool has_signalled = false; + +    if (applet_oe != nullptr) { +        applet_oe->GetMessageQueue()->OperationModeChanged(); +        has_signalled = true; +    } + +    if (applet_ae != nullptr && !has_signalled) { +        applet_ae->GetMessageQueue()->OperationModeChanged(); +    } +} +  namespace {  template <typename Dialog, typename... Args>  void CallConfigureDialog(ConfigureInput& parent, Args&&... args) { @@ -34,7 +61,7 @@ void CallConfigureDialog(ConfigureInput& parent, Args&&... args) {  } // Anonymous namespace  ConfigureInput::ConfigureInput(QWidget* parent) -    : QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()) { +    : QDialog(parent), ui(std::make_unique<Ui::ConfigureInput>()) {      ui->setupUi(this);      players_controller = { @@ -90,37 +117,6 @@ ConfigureInput::ConfigureInput(QWidget* parent)  ConfigureInput::~ConfigureInput() = default; -void ConfigureInput::OnDockedModeChanged(bool last_state, bool new_state) { -    if (ui->use_docked_mode->isChecked() && ui->handheld_connected->isChecked()) { -        ui->handheld_connected->setChecked(false); -    } - -    if (last_state == new_state) { -        return; -    } - -    Core::System& system{Core::System::GetInstance()}; -    if (!system.IsPoweredOn()) { -        return; -    } -    Service::SM::ServiceManager& sm = system.ServiceManager(); - -    // Message queue is shared between these services, we just need to signal an operation -    // change to one and it will handle both automatically -    auto applet_oe = sm.GetService<Service::AM::AppletOE>("appletOE"); -    auto applet_ae = sm.GetService<Service::AM::AppletAE>("appletAE"); -    bool has_signalled = false; - -    if (applet_oe != nullptr) { -        applet_oe->GetMessageQueue()->OperationModeChanged(); -        has_signalled = true; -    } - -    if (applet_ae != nullptr && !has_signalled) { -        applet_ae->GetMessageQueue()->OperationModeChanged(); -    } -} -  void ConfigureInput::applyConfiguration() {      for (std::size_t i = 0; i < players_controller.size(); ++i) {          const auto controller_type_index = players_controller[i]->currentIndex(); diff --git a/src/yuzu/configuration/configure_input.h b/src/yuzu/configuration/configure_input.h index 1649e4c0b..b8e62cc2b 100644 --- a/src/yuzu/configuration/configure_input.h +++ b/src/yuzu/configuration/configure_input.h @@ -7,8 +7,8 @@  #include <array>  #include <memory> +#include <QDialog>  #include <QKeyEvent> -#include <QWidget>  #include "ui_configure_input.h" @@ -20,7 +20,9 @@ namespace Ui {  class ConfigureInput;  } -class ConfigureInput : public QWidget { +void OnDockedModeChanged(bool last_state, bool new_state); + +class ConfigureInput : public QDialog {      Q_OBJECT  public: @@ -33,8 +35,6 @@ public:  private:      void updateUIEnabled(); -    void OnDockedModeChanged(bool last_state, bool new_state); -      /// Load configuration settings.      void loadConfiguration();      /// Restore all buttons to their default values. diff --git a/src/yuzu/configuration/configure_input.ui b/src/yuzu/configuration/configure_input.ui index dae8277bc..0a2d9f024 100644 --- a/src/yuzu/configuration/configure_input.ui +++ b/src/yuzu/configuration/configure_input.ui @@ -1,13 +1,13 @@  <?xml version="1.0" encoding="UTF-8"?>  <ui version="4.0">   <class>ConfigureInput</class> - <widget class="QWidget" name="ConfigureInput"> + <widget class="QDialog" name="ConfigureInput">    <property name="geometry">     <rect>      <x>0</x>      <y>0</y> -    <width>473</width> -    <height>685</height> +    <width>384</width> +    <height>576</height>     </rect>    </property>    <property name="windowTitle"> @@ -478,6 +478,13 @@           </property>          </spacer>         </item> +       <item> +        <widget class="QDialogButtonBox" name="buttonBox"> +         <property name="standardButtons"> +          <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> +         </property> +        </widget> +       </item>        </layout>       </item>      </layout> @@ -485,5 +492,38 @@    </layout>   </widget>   <resources/> - <connections/> + <connections> +  <connection> +   <sender>buttonBox</sender> +   <signal>accepted()</signal> +   <receiver>ConfigureInput</receiver> +   <slot>accept()</slot> +   <hints> +    <hint type="sourcelabel"> +     <x>294</x> +     <y>553</y> +    </hint> +    <hint type="destinationlabel"> +     <x>191</x> +     <y>287</y> +    </hint> +   </hints> +  </connection> +  <connection> +   <sender>buttonBox</sender> +   <signal>rejected()</signal> +   <receiver>ConfigureInput</receiver> +   <slot>reject()</slot> +   <hints> +    <hint type="sourcelabel"> +     <x>294</x> +     <y>553</y> +    </hint> +    <hint type="destinationlabel"> +     <x>191</x> +     <y>287</y> +    </hint> +   </hints> +  </connection> + </connections>  </ui> diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp index 7dadd83c1..ba2b32c4f 100644 --- a/src/yuzu/configuration/configure_input_player.cpp +++ b/src/yuzu/configuration/configure_input_player.cpp @@ -6,6 +6,7 @@  #include <memory>  #include <utility>  #include <QColorDialog> +#include <QGridLayout>  #include <QMenu>  #include <QMessageBox>  #include <QTimer> diff --git a/src/yuzu/configuration/configure_input_simple.cpp b/src/yuzu/configuration/configure_input_simple.cpp new file mode 100644 index 000000000..b4f3724bd --- /dev/null +++ b/src/yuzu/configuration/configure_input_simple.cpp @@ -0,0 +1,142 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <array> +#include <cstring> +#include <functional> +#include <tuple> + +#include <QDialog> + +#include "ui_configure_input_simple.h" +#include "yuzu/configuration/configure_input.h" +#include "yuzu/configuration/configure_input_player.h" +#include "yuzu/configuration/configure_input_simple.h" +#include "yuzu/ui_settings.h" + +namespace { + +template <typename Dialog, typename... Args> +void CallConfigureDialog(ConfigureInputSimple* caller, Args&&... args) { +    caller->applyConfiguration(); +    Dialog dialog(caller, std::forward<Args>(args)...); + +    const auto res = dialog.exec(); +    if (res == QDialog::Accepted) { +        dialog.applyConfiguration(); +    } +} + +// OnProfileSelect functions should (when applicable): +// - Set controller types +// - Set controller enabled +// - Set docked mode +// - Set advanced controller config/enabled (i.e. debug, kbd, mouse, touch) +// +// OnProfileSelect function should NOT however: +// - Reset any button mappings +// - Open any dialogs +// - Block in any way + +constexpr std::size_t HANDHELD_INDEX = 8; + +void HandheldOnProfileSelect() { +    Settings::values.players[HANDHELD_INDEX].connected = true; +    Settings::values.players[HANDHELD_INDEX].type = Settings::ControllerType::DualJoycon; + +    for (std::size_t player = 0; player < HANDHELD_INDEX; ++player) { +        Settings::values.players[player].connected = false; +    } + +    Settings::values.use_docked_mode = false; +    Settings::values.keyboard_enabled = false; +    Settings::values.mouse_enabled = false; +    Settings::values.debug_pad_enabled = false; +    Settings::values.touchscreen.enabled = true; +} + +void DualJoyconsDockedOnProfileSelect() { +    Settings::values.players[0].connected = true; +    Settings::values.players[0].type = Settings::ControllerType::DualJoycon; + +    for (std::size_t player = 1; player <= HANDHELD_INDEX; ++player) { +        Settings::values.players[player].connected = false; +    } + +    Settings::values.use_docked_mode = true; +    Settings::values.keyboard_enabled = false; +    Settings::values.mouse_enabled = false; +    Settings::values.debug_pad_enabled = false; +    Settings::values.touchscreen.enabled = false; +} + +// Name, OnProfileSelect (called when selected in drop down), OnConfigure (called when configure +// is clicked) +using InputProfile = +    std::tuple<QString, std::function<void()>, std::function<void(ConfigureInputSimple*)>>; + +const std::array<InputProfile, 3> INPUT_PROFILES{{ +    {ConfigureInputSimple::tr("Single Player - Handheld - Undocked"), HandheldOnProfileSelect, +     [](ConfigureInputSimple* caller) { +         CallConfigureDialog<ConfigureInputPlayer>(caller, HANDHELD_INDEX, false); +     }}, +    {ConfigureInputSimple::tr("Single Player - Dual Joycons - Docked"), +     DualJoyconsDockedOnProfileSelect, +     [](ConfigureInputSimple* caller) { +         CallConfigureDialog<ConfigureInputPlayer>(caller, 1, false); +     }}, +    {ConfigureInputSimple::tr("Custom"), [] {}, CallConfigureDialog<ConfigureInput>}, +}}; + +} // namespace + +void ApplyInputProfileConfiguration(int profile_index) { +    std::get<1>( +        INPUT_PROFILES.at(std::min(profile_index, static_cast<int>(INPUT_PROFILES.size() - 1))))(); +} + +ConfigureInputSimple::ConfigureInputSimple(QWidget* parent) +    : QWidget(parent), ui(std::make_unique<Ui::ConfigureInputSimple>()) { +    ui->setupUi(this); + +    for (const auto& profile : INPUT_PROFILES) { +        ui->profile_combobox->addItem(std::get<0>(profile), std::get<0>(profile)); +    } + +    connect(ui->profile_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, +            &ConfigureInputSimple::OnSelectProfile); +    connect(ui->profile_configure, &QPushButton::pressed, this, &ConfigureInputSimple::OnConfigure); + +    this->loadConfiguration(); +} + +ConfigureInputSimple::~ConfigureInputSimple() = default; + +void ConfigureInputSimple::applyConfiguration() { +    auto index = ui->profile_combobox->currentIndex(); +    // Make the stored index for "Custom" very large so that if new profiles are added it +    // doesn't change. +    if (index >= static_cast<int>(INPUT_PROFILES.size() - 1)) +        index = std::numeric_limits<int>::max(); + +    UISettings::values.profile_index = index; +} + +void ConfigureInputSimple::loadConfiguration() { +    const auto index = UISettings::values.profile_index; +    if (index >= static_cast<int>(INPUT_PROFILES.size()) || index < 0) +        ui->profile_combobox->setCurrentIndex(static_cast<int>(INPUT_PROFILES.size() - 1)); +    else +        ui->profile_combobox->setCurrentIndex(index); +} + +void ConfigureInputSimple::OnSelectProfile(int index) { +    const auto old_docked = Settings::values.use_docked_mode; +    ApplyInputProfileConfiguration(index); +    OnDockedModeChanged(old_docked, Settings::values.use_docked_mode); +} + +void ConfigureInputSimple::OnConfigure() { +    std::get<2>(INPUT_PROFILES.at(ui->profile_combobox->currentIndex()))(this); +} diff --git a/src/yuzu/configuration/configure_input_simple.h b/src/yuzu/configuration/configure_input_simple.h new file mode 100644 index 000000000..5b6b69994 --- /dev/null +++ b/src/yuzu/configuration/configure_input_simple.h @@ -0,0 +1,40 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> + +#include <QWidget> + +class QPushButton; +class QString; +class QTimer; + +namespace Ui { +class ConfigureInputSimple; +} + +// Used by configuration loader to apply a profile if the input is invalid. +void ApplyInputProfileConfiguration(int profile_index); + +class ConfigureInputSimple : public QWidget { +    Q_OBJECT + +public: +    explicit ConfigureInputSimple(QWidget* parent = nullptr); +    ~ConfigureInputSimple() override; + +    /// Save all button configurations to settings file +    void applyConfiguration(); + +private: +    /// Load configuration settings. +    void loadConfiguration(); + +    void OnSelectProfile(int index); +    void OnConfigure(); + +    std::unique_ptr<Ui::ConfigureInputSimple> ui; +}; diff --git a/src/yuzu/configuration/configure_input_simple.ui b/src/yuzu/configuration/configure_input_simple.ui new file mode 100644 index 000000000..c4889caa9 --- /dev/null +++ b/src/yuzu/configuration/configure_input_simple.ui @@ -0,0 +1,97 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigureInputSimple</class> + <widget class="QWidget" name="ConfigureInputSimple"> +  <property name="geometry"> +   <rect> +    <x>0</x> +    <y>0</y> +    <width>473</width> +    <height>685</height> +   </rect> +  </property> +  <property name="windowTitle"> +   <string>ConfigureInputSimple</string> +  </property> +  <layout class="QVBoxLayout" name="verticalLayout_5"> +   <item> +    <layout class="QVBoxLayout" name="verticalLayout"> +     <item> +      <widget class="QGroupBox" name="gridGroupBox"> +       <property name="title"> +        <string>Profile</string> +       </property> +       <layout class="QGridLayout" name="gridLayout"> +        <item row="1" column="2"> +         <widget class="QPushButton" name="profile_configure"> +          <property name="text"> +           <string>Configure</string> +          </property> +         </widget> +        </item> +        <item row="1" column="0"> +         <spacer name="horizontalSpacer"> +          <property name="orientation"> +           <enum>Qt::Horizontal</enum> +          </property> +          <property name="sizeHint" stdset="0"> +           <size> +            <width>40</width> +            <height>20</height> +           </size> +          </property> +         </spacer> +        </item> +        <item row="1" column="3"> +         <spacer name="horizontalSpacer_2"> +          <property name="orientation"> +           <enum>Qt::Horizontal</enum> +          </property> +          <property name="sizeHint" stdset="0"> +           <size> +            <width>40</width> +            <height>20</height> +           </size> +          </property> +         </spacer> +        </item> +        <item row="1" column="1"> +         <widget class="QComboBox" name="profile_combobox"> +          <property name="minimumSize"> +           <size> +            <width>250</width> +            <height>0</height> +           </size> +          </property> +         </widget> +        </item> +        <item row="0" column="1" colspan="2"> +         <widget class="QLabel" name="label"> +          <property name="text"> +           <string>Choose a controller configuration:</string> +          </property> +         </widget> +        </item> +       </layout> +      </widget> +     </item> +    </layout> +   </item> +   <item> +    <spacer name="verticalSpacer"> +     <property name="orientation"> +      <enum>Qt::Vertical</enum> +     </property> +     <property name="sizeHint" stdset="0"> +      <size> +       <width>20</width> +       <height>40</height> +      </size> +     </property> +    </spacer> +   </item> +  </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/src/yuzu/configuration/configure_per_general.cpp b/src/yuzu/configuration/configure_per_general.cpp new file mode 100644 index 000000000..80109b434 --- /dev/null +++ b/src/yuzu/configuration/configure_per_general.cpp @@ -0,0 +1,170 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <memory> +#include <utility> + +#include <QHeaderView> +#include <QMenu> +#include <QMessageBox> +#include <QStandardItemModel> +#include <QString> +#include <QTimer> +#include <QTreeView> + +#include "core/file_sys/control_metadata.h" +#include "core/file_sys/patch_manager.h" +#include "core/file_sys/xts_archive.h" +#include "core/loader/loader.h" +#include "ui_configure_per_general.h" +#include "yuzu/configuration/config.h" +#include "yuzu/configuration/configure_input.h" +#include "yuzu/configuration/configure_per_general.h" +#include "yuzu/ui_settings.h" +#include "yuzu/util/util.h" + +ConfigurePerGameGeneral::ConfigurePerGameGeneral(QWidget* parent, u64 title_id) +    : QDialog(parent), ui(std::make_unique<Ui::ConfigurePerGameGeneral>()), title_id(title_id) { + +    ui->setupUi(this); +    setFocusPolicy(Qt::ClickFocus); +    setWindowTitle(tr("Properties")); + +    layout = new QVBoxLayout; +    tree_view = new QTreeView; +    item_model = new QStandardItemModel(tree_view); +    tree_view->setModel(item_model); +    tree_view->setAlternatingRowColors(true); +    tree_view->setSelectionMode(QHeaderView::SingleSelection); +    tree_view->setSelectionBehavior(QHeaderView::SelectRows); +    tree_view->setVerticalScrollMode(QHeaderView::ScrollPerPixel); +    tree_view->setHorizontalScrollMode(QHeaderView::ScrollPerPixel); +    tree_view->setSortingEnabled(true); +    tree_view->setEditTriggers(QHeaderView::NoEditTriggers); +    tree_view->setUniformRowHeights(true); +    tree_view->setContextMenuPolicy(Qt::NoContextMenu); + +    item_model->insertColumns(0, 2); +    item_model->setHeaderData(0, Qt::Horizontal, "Patch Name"); +    item_model->setHeaderData(1, Qt::Horizontal, "Version"); + +    // We must register all custom types with the Qt Automoc system so that we are able to use it +    // with signals/slots. In this case, QList falls under the umbrells of custom types. +    qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>"); + +    layout->setContentsMargins(0, 0, 0, 0); +    layout->setSpacing(0); +    layout->addWidget(tree_view); + +    ui->scrollArea->setLayout(layout); + +    scene = new QGraphicsScene; +    ui->icon_view->setScene(scene); + +    connect(item_model, &QStandardItemModel::itemChanged, +            [] { UISettings::values.is_game_list_reload_pending.exchange(true); }); + +    this->loadConfiguration(); +} + +ConfigurePerGameGeneral::~ConfigurePerGameGeneral() = default; + +void ConfigurePerGameGeneral::applyConfiguration() { +    std::vector<std::string> disabled_addons; + +    for (const auto& item : list_items) { +        const auto disabled = item.front()->checkState() == Qt::Unchecked; +        if (disabled) +            disabled_addons.push_back(item.front()->text().toStdString()); +    } + +    Settings::values.disabled_addons[title_id] = disabled_addons; +} + +void ConfigurePerGameGeneral::loadFromFile(FileSys::VirtualFile file) { +    this->file = std::move(file); +    this->loadConfiguration(); +} + +void ConfigurePerGameGeneral::loadConfiguration() { +    if (file == nullptr) +        return; + +    const auto loader = Loader::GetLoader(file); + +    ui->display_title_id->setText(fmt::format("{:016X}", title_id).c_str()); + +    FileSys::PatchManager pm{title_id}; +    const auto control = pm.GetControlMetadata(); + +    if (control.first != nullptr) { +        ui->display_version->setText(QString::fromStdString(control.first->GetVersionString())); +        ui->display_name->setText(QString::fromStdString(control.first->GetApplicationName())); +        ui->display_developer->setText(QString::fromStdString(control.first->GetDeveloperName())); +    } else { +        std::string title; +        if (loader->ReadTitle(title) == Loader::ResultStatus::Success) +            ui->display_name->setText(QString::fromStdString(title)); + +        std::string developer; +        if (loader->ReadDeveloper(developer) == Loader::ResultStatus::Success) +            ui->display_developer->setText(QString::fromStdString(developer)); + +        ui->display_version->setText(QStringLiteral("1.0.0")); +    } + +    if (control.second != nullptr) { +        scene->clear(); + +        QPixmap map; +        const auto bytes = control.second->ReadAllBytes(); +        map.loadFromData(bytes.data(), bytes.size()); + +        scene->addPixmap(map.scaled(ui->icon_view->width(), ui->icon_view->height(), +                                    Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); +    } else { +        std::vector<u8> bytes; +        if (loader->ReadIcon(bytes) == Loader::ResultStatus::Success) { +            scene->clear(); + +            QPixmap map; +            map.loadFromData(bytes.data(), bytes.size()); + +            scene->addPixmap(map.scaled(ui->icon_view->width(), ui->icon_view->height(), +                                        Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); +        } +    } + +    FileSys::VirtualFile update_raw; +    loader->ReadUpdateRaw(update_raw); + +    const auto& disabled = Settings::values.disabled_addons[title_id]; + +    for (const auto& patch : pm.GetPatchVersionNames(update_raw)) { +        QStandardItem* first_item = new QStandardItem; +        const auto name = QString::fromStdString(patch.first).replace("[D] ", ""); +        first_item->setText(name); +        first_item->setCheckable(true); + +        const auto patch_disabled = +            std::find(disabled.begin(), disabled.end(), name.toStdString()) != disabled.end(); + +        first_item->setCheckState(patch_disabled ? Qt::Unchecked : Qt::Checked); + +        list_items.push_back(QList<QStandardItem*>{ +            first_item, new QStandardItem{QString::fromStdString(patch.second)}}); +        item_model->appendRow(list_items.back()); +    } + +    tree_view->setColumnWidth(0, 5 * tree_view->width() / 16); + +    ui->display_filename->setText(QString::fromStdString(file->GetName())); + +    ui->display_format->setText( +        QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))); + +    const auto valueText = ReadableByteSize(file->GetSize()); +    ui->display_size->setText(valueText); +} diff --git a/src/yuzu/configuration/configure_per_general.h b/src/yuzu/configuration/configure_per_general.h new file mode 100644 index 000000000..a4494446c --- /dev/null +++ b/src/yuzu/configuration/configure_per_general.h @@ -0,0 +1,50 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <vector> + +#include <QKeyEvent> +#include <QList> +#include <QWidget> + +#include "core/file_sys/vfs_types.h" + +class QTreeView; +class QGraphicsScene; +class QStandardItem; +class QStandardItemModel; + +namespace Ui { +class ConfigurePerGameGeneral; +} + +class ConfigurePerGameGeneral : public QDialog { +    Q_OBJECT + +public: +    explicit ConfigurePerGameGeneral(QWidget* parent, u64 title_id); +    ~ConfigurePerGameGeneral() override; + +    /// Save all button configurations to settings file +    void applyConfiguration(); + +    void loadFromFile(FileSys::VirtualFile file); + +private: +    std::unique_ptr<Ui::ConfigurePerGameGeneral> ui; +    FileSys::VirtualFile file; +    u64 title_id; + +    QVBoxLayout* layout; +    QTreeView* tree_view; +    QStandardItemModel* item_model; +    QGraphicsScene* scene; + +    std::vector<QList<QStandardItem*>> list_items; + +    void loadConfiguration(); +}; diff --git a/src/yuzu/configuration/configure_per_general.ui b/src/yuzu/configuration/configure_per_general.ui new file mode 100644 index 000000000..8fdd96fa4 --- /dev/null +++ b/src/yuzu/configuration/configure_per_general.ui @@ -0,0 +1,276 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigurePerGameGeneral</class> + <widget class="QDialog" name="ConfigurePerGameGeneral"> +  <property name="geometry"> +   <rect> +    <x>0</x> +    <y>0</y> +    <width>400</width> +    <height>520</height> +   </rect> +  </property> +  <property name="windowTitle"> +   <string>ConfigurePerGameGeneral</string> +  </property> +  <layout class="QHBoxLayout" name="HorizontalLayout"> +   <item> +    <layout class="QVBoxLayout" name="VerticalLayout"> +     <item> +      <widget class="QGroupBox" name="GeneralGroupBox"> +       <property name="title"> +        <string>Info</string> +       </property> +       <layout class="QHBoxLayout" name="GeneralHorizontalLayout"> +        <item> +         <layout class="QGridLayout" name="gridLayout_2"> +          <item row="6" column="1" colspan="2"> +           <widget class="QLineEdit" name="display_filename"> +            <property name="enabled"> +             <bool>true</bool> +            </property> +            <property name="readOnly"> +             <bool>true</bool> +            </property> +           </widget> +          </item> +          <item row="0" column="1"> +           <widget class="QLineEdit" name="display_name"> +            <property name="enabled"> +             <bool>true</bool> +            </property> +            <property name="readOnly"> +             <bool>true</bool> +            </property> +           </widget> +          </item> +          <item row="1" column="0"> +           <widget class="QLabel" name="label_2"> +            <property name="text"> +             <string>Developer</string> +            </property> +           </widget> +          </item> +          <item row="5" column="1" colspan="2"> +           <widget class="QLineEdit" name="display_size"> +            <property name="enabled"> +             <bool>true</bool> +            </property> +            <property name="readOnly"> +             <bool>true</bool> +            </property> +           </widget> +          </item> +          <item row="0" column="0"> +           <widget class="QLabel" name="label"> +            <property name="text"> +             <string>Name</string> +            </property> +           </widget> +          </item> +          <item row="6" column="0"> +           <widget class="QLabel" name="label_7"> +            <property name="text"> +             <string>Filename</string> +            </property> +           </widget> +          </item> +          <item row="2" column="0"> +           <widget class="QLabel" name="label_3"> +            <property name="text"> +             <string>Version</string> +            </property> +           </widget> +          </item> +          <item row="4" column="0"> +           <widget class="QLabel" name="label_5"> +            <property name="text"> +             <string>Format</string> +            </property> +           </widget> +          </item> +          <item row="2" column="1"> +           <widget class="QLineEdit" name="display_version"> +            <property name="enabled"> +             <bool>true</bool> +            </property> +            <property name="readOnly"> +             <bool>true</bool> +            </property> +           </widget> +          </item> +          <item row="4" column="1"> +           <widget class="QLineEdit" name="display_format"> +            <property name="enabled"> +             <bool>true</bool> +            </property> +            <property name="readOnly"> +             <bool>true</bool> +            </property> +           </widget> +          </item> +          <item row="5" column="0"> +           <widget class="QLabel" name="label_6"> +            <property name="text"> +             <string>Size</string> +            </property> +           </widget> +          </item> +          <item row="1" column="1"> +           <widget class="QLineEdit" name="display_developer"> +            <property name="enabled"> +             <bool>true</bool> +            </property> +            <property name="readOnly"> +             <bool>true</bool> +            </property> +           </widget> +          </item> +          <item row="3" column="0"> +           <widget class="QLabel" name="label_4"> +            <property name="text"> +             <string>Title ID</string> +            </property> +           </widget> +          </item> +          <item row="3" column="1"> +           <widget class="QLineEdit" name="display_title_id"> +            <property name="enabled"> +             <bool>true</bool> +            </property> +            <property name="readOnly"> +             <bool>true</bool> +            </property> +           </widget> +          </item> +          <item row="0" column="2" rowspan="5"> +           <widget class="QGraphicsView" name="icon_view"> +            <property name="sizePolicy"> +             <sizepolicy hsizetype="Maximum" vsizetype="Maximum"> +              <horstretch>0</horstretch> +              <verstretch>0</verstretch> +             </sizepolicy> +            </property> +            <property name="minimumSize"> +             <size> +              <width>128</width> +              <height>128</height> +             </size> +            </property> +            <property name="maximumSize"> +             <size> +              <width>128</width> +              <height>128</height> +             </size> +            </property> +            <property name="verticalScrollBarPolicy"> +             <enum>Qt::ScrollBarAlwaysOff</enum> +            </property> +            <property name="horizontalScrollBarPolicy"> +             <enum>Qt::ScrollBarAlwaysOff</enum> +            </property> +            <property name="sizeAdjustPolicy"> +             <enum>QAbstractScrollArea::AdjustToContents</enum> +            </property> +            <property name="interactive"> +             <bool>false</bool> +            </property> +           </widget> +          </item> +         </layout> +        </item> +       </layout> +      </widget> +     </item> +     <item> +      <widget class="QGroupBox" name="PerformanceGroupBox"> +       <property name="title"> +        <string>Add-Ons</string> +       </property> +       <layout class="QHBoxLayout" name="PerformanceHorizontalLayout"> +        <item> +         <widget class="QScrollArea" name="scrollArea"> +          <property name="widgetResizable"> +           <bool>true</bool> +          </property> +          <widget class="QWidget" name="scrollAreaWidgetContents"> +           <property name="geometry"> +            <rect> +             <x>0</x> +             <y>0</y> +             <width>350</width> +             <height>169</height> +            </rect> +           </property> +          </widget> +         </widget> +        </item> +        <item> +         <layout class="QVBoxLayout" name="PerformanceVerticalLayout"/> +        </item> +       </layout> +      </widget> +     </item> +     <item> +      <spacer name="verticalSpacer"> +       <property name="orientation"> +        <enum>Qt::Vertical</enum> +       </property> +       <property name="sizeType"> +        <enum>QSizePolicy::Fixed</enum> +       </property> +       <property name="sizeHint" stdset="0"> +        <size> +         <width>20</width> +         <height>40</height> +        </size> +       </property> +      </spacer> +     </item> +     <item> +      <widget class="QDialogButtonBox" name="buttonBox"> +       <property name="standardButtons"> +        <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> +       </property> +      </widget> +     </item> +    </layout> +   </item> +  </layout> + </widget> + <resources/> + <connections> +  <connection> +   <sender>buttonBox</sender> +   <signal>accepted()</signal> +   <receiver>ConfigurePerGameGeneral</receiver> +   <slot>accept()</slot> +   <hints> +    <hint type="sourcelabel"> +     <x>269</x> +     <y>567</y> +    </hint> +    <hint type="destinationlabel"> +     <x>269</x> +     <y>294</y> +    </hint> +   </hints> +  </connection> +  <connection> +   <sender>buttonBox</sender> +   <signal>rejected()</signal> +   <receiver>ConfigurePerGameGeneral</receiver> +   <slot>reject()</slot> +   <hints> +    <hint type="sourcelabel"> +     <x>269</x> +     <y>567</y> +    </hint> +    <hint type="destinationlabel"> +     <x>269</x> +     <y>294</y> +    </hint> +   </hints> +  </connection> + </connections> +</ui> diff --git a/src/yuzu/debugger/graphics/graphics_surface.cpp b/src/yuzu/debugger/graphics/graphics_surface.cpp index 707747422..209798521 100644 --- a/src/yuzu/debugger/graphics/graphics_surface.cpp +++ b/src/yuzu/debugger/graphics/graphics_surface.cpp @@ -30,6 +30,7 @@ static Tegra::Texture::TextureFormat ConvertToTextureFormat(          return Tegra::Texture::TextureFormat::A2B10G10R10;      default:          UNIMPLEMENTED_MSG("Unimplemented RT format"); +        return Tegra::Texture::TextureFormat::A8R8G8B8;      }  } diff --git a/src/yuzu/debugger/wait_tree.cpp b/src/yuzu/debugger/wait_tree.cpp index f9c18ede4..6b3a757e0 100644 --- a/src/yuzu/debugger/wait_tree.cpp +++ b/src/yuzu/debugger/wait_tree.cpp @@ -75,7 +75,7 @@ std::vector<std::unique_ptr<WaitTreeThread>> WaitTreeItem::MakeThreadItemList()      return item_list;  } -WaitTreeText::WaitTreeText(const QString& t) : text(t) {} +WaitTreeText::WaitTreeText(QString t) : text(std::move(t)) {}  WaitTreeText::~WaitTreeText() = default;  QString WaitTreeText::GetText() const { diff --git a/src/yuzu/debugger/wait_tree.h b/src/yuzu/debugger/wait_tree.h index 492fb6ac9..e639ef412 100644 --- a/src/yuzu/debugger/wait_tree.h +++ b/src/yuzu/debugger/wait_tree.h @@ -52,7 +52,7 @@ private:  class WaitTreeText : public WaitTreeItem {      Q_OBJECT  public: -    explicit WaitTreeText(const QString& text); +    explicit WaitTreeText(QString text);      ~WaitTreeText() override;      QString GetText() const override; diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index b52a50915..8e9524fd6 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -333,6 +333,8 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {      QAction* dump_romfs = context_menu.addAction(tr("Dump RomFS"));      QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard"));      QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry")); +    context_menu.addSeparator(); +    QAction* properties = context_menu.addAction(tr("Properties"));      open_save_location->setEnabled(program_id != 0);      auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); @@ -346,6 +348,7 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {      connect(copy_tid, &QAction::triggered, [&]() { emit CopyTIDRequested(program_id); });      connect(navigate_to_gamedb_entry, &QAction::triggered,              [&]() { emit NavigateToGamedbEntryRequested(program_id, compatibility_list); }); +    connect(properties, &QAction::triggered, [&]() { emit OpenPerGameGeneralRequested(path); });      context_menu.exec(tree_view->viewport()->mapToGlobal(menu_location));  } diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h index 05e115e19..b317eb2fc 100644 --- a/src/yuzu/game_list.h +++ b/src/yuzu/game_list.h @@ -70,6 +70,7 @@ signals:      void CopyTIDRequested(u64 program_id);      void NavigateToGamedbEntryRequested(u64 program_id,                                          const CompatibilityList& compatibility_list); +    void OpenPerGameGeneralRequested(const std::string& file);  private slots:      void onTextChanged(const QString& newText); diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp index 9fd074223..b37710f59 100644 --- a/src/yuzu/game_list_worker.cpp +++ b/src/yuzu/game_list_worker.cpp @@ -62,7 +62,7 @@ QString FormatPatchNameVersions(const FileSys::PatchManager& patch_manager,      FileSys::VirtualFile update_raw;      loader.ReadUpdateRaw(update_raw);      for (const auto& kv : patch_manager.GetPatchVersionNames(update_raw)) { -        const bool is_update = kv.first == "Update"; +        const bool is_update = kv.first == "Update" || kv.first == "[D] Update";          if (!updatable && is_update) {              continue;          } @@ -99,12 +99,14 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri          compatibility = it->second.first;      } +    const auto file_type = loader.GetFileType(); +    const auto file_type_string = QString::fromStdString(Loader::GetFileTypeString(file_type)); +      QList<QStandardItem*> list{ -        new GameListItemPath( -            FormatGameName(path), icon, QString::fromStdString(name), -            QString::fromStdString(Loader::GetFileTypeString(loader.GetFileType())), program_id), +        new GameListItemPath(FormatGameName(path), icon, QString::fromStdString(name), +                             file_type_string, program_id),          new GameListItemCompat(compatibility), -        new GameListItem(QString::fromStdString(Loader::GetFileTypeString(loader.GetFileType()))), +        new GameListItem(file_type_string),          new GameListItemSize(FileUtil::GetSize(path)),      }; @@ -196,12 +198,16 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign          const bool is_dir = FileUtil::IsDirectory(physical_name);          if (!is_dir &&              (HasSupportedFileExtension(physical_name) || IsExtractedNCAMain(physical_name))) { -            std::unique_ptr<Loader::AppLoader> loader = -                Loader::GetLoader(vfs->OpenFile(physical_name, FileSys::Mode::Read)); -            if (!loader || ((loader->GetFileType() == Loader::FileType::Unknown || -                             loader->GetFileType() == Loader::FileType::Error) && -                            !UISettings::values.show_unknown)) +            auto loader = Loader::GetLoader(vfs->OpenFile(physical_name, FileSys::Mode::Read)); +            if (!loader) {                  return true; +            } + +            const auto file_type = loader->GetFileType(); +            if ((file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) && +                !UISettings::values.show_unknown) { +                return true; +            }              std::vector<u8> icon;              const auto res1 = loader->ReadIcon(icon); diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 808f14fb3..01a0f94ab 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -8,7 +8,9 @@  #include <thread>  // VFS includes must be before glad as they will conflict with Windows file api, which uses defines. +#include "applets/profile_select.h"  #include "applets/software_keyboard.h" +#include "configuration/configure_per_general.h"  #include "core/file_sys/vfs.h"  #include "core/file_sys/vfs_real.h"  #include "core/hle/service/acc/profile_manager.h" @@ -207,6 +209,28 @@ GMainWindow::~GMainWindow() {          delete render_window;  } +void GMainWindow::ProfileSelectorSelectProfile() { +    QtProfileSelectionDialog dialog(this); +    dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | +                          Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint); +    dialog.setWindowModality(Qt::WindowModal); +    dialog.exec(); + +    if (!dialog.GetStatus()) { +        emit ProfileSelectorFinishedSelection(std::nullopt); +        return; +    } + +    Service::Account::ProfileManager manager; +    const auto uuid = manager.GetUser(dialog.GetIndex()); +    if (!uuid.has_value()) { +        emit ProfileSelectorFinishedSelection(std::nullopt); +        return; +    } + +    emit ProfileSelectorFinishedSelection(uuid); +} +  void GMainWindow::SoftwareKeyboardGetText(      const Core::Frontend::SoftwareKeyboardParameters& parameters) {      QtSoftwareKeyboardDialog dialog(this, parameters); @@ -450,6 +474,8 @@ void GMainWindow::ConnectWidgetEvents() {      connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID);      connect(game_list, &GameList::NavigateToGamedbEntryRequested, this,              &GMainWindow::OnGameListNavigateToGamedbEntry); +    connect(game_list, &GameList::OpenPerGameGeneralRequested, this, +            &GMainWindow::OnGameListOpenPerGameProperties);      connect(this, &GMainWindow::EmulationStarting, render_window,              &GRenderWindow::OnEmulationStarting); @@ -584,6 +610,7 @@ bool GMainWindow::LoadROM(const QString& filename) {      system.SetGPUDebugContext(debug_context); +    system.SetProfileSelector(std::make_unique<QtProfileSelector>(*this));      system.SetSoftwareKeyboard(std::make_unique<QtSoftwareKeyboard>(*this));      const Core::System::ResultStatus result{system.Load(*render_window, filename.toStdString())}; @@ -1002,6 +1029,32 @@ void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id,      QDesktopServices::openUrl(QUrl("https://yuzu-emu.org/game/" + directory));  } +void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) { +    u64 title_id{}; +    const auto v_file = Core::GetGameFileFromPath(vfs, file); +    const auto loader = Loader::GetLoader(v_file); +    if (loader == nullptr || loader->ReadProgramId(title_id) != Loader::ResultStatus::Success) { +        QMessageBox::information(this, tr("Properties"), +                                 tr("The game properties could not be loaded.")); +        return; +    } + +    ConfigurePerGameGeneral dialog(this, title_id); +    dialog.loadFromFile(v_file); +    auto result = dialog.exec(); +    if (result == QDialog::Accepted) { +        dialog.applyConfiguration(); + +        const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false); +        if (reload) { +            game_list->PopulateAsync(UISettings::values.gamedir, +                                     UISettings::values.gamedir_deepscan); +        } + +        config->Save(); +    } +} +  void GMainWindow::OnMenuLoadFile() {      const QString extensions =          QString("*.").append(GameList::supported_file_extensions.join(" *.")).append(" main"); diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 9a1df5168..4e37f6a2d 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -99,10 +99,12 @@ signals:      // Signal that tells widgets to update icons to use the current theme      void UpdateThemedIcons(); +    void ProfileSelectorFinishedSelection(std::optional<Service::Account::UUID> uuid);      void SoftwareKeyboardFinishedText(std::optional<std::u16string> text);      void SoftwareKeyboardFinishedCheckDialog();  public slots: +    void ProfileSelectorSelectProfile();      void SoftwareKeyboardGetText(const Core::Frontend::SoftwareKeyboardParameters& parameters);      void SoftwareKeyboardInvokeCheckDialog(std::u16string error_message); @@ -168,6 +170,7 @@ private slots:      void OnGameListCopyTID(u64 program_id);      void OnGameListNavigateToGamedbEntry(u64 program_id,                                           const CompatibilityList& compatibility_list); +    void OnGameListOpenPerGameProperties(const std::string& file);      void OnMenuLoadFile();      void OnMenuLoadFolder();      void OnMenuInstallToNAND(); diff --git a/src/yuzu/ui_settings.h b/src/yuzu/ui_settings.h index b03bc2de6..58ba240fd 100644 --- a/src/yuzu/ui_settings.h +++ b/src/yuzu/ui_settings.h @@ -62,6 +62,9 @@ struct Values {      // logging      bool show_console; +    // Controllers +    int profile_index; +      // Game List      bool show_unknown;      bool show_add_ons; | 
