diff options
Diffstat (limited to 'src/yuzu')
60 files changed, 5965 insertions, 1586 deletions
| diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 9379d9110..17ecaafde 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -7,6 +7,10 @@ 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      bootmanager.h      compatibility_list.cpp @@ -27,8 +31,18 @@ add_executable(yuzu      configuration/configure_graphics.h      configuration/configure_input.cpp      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      configuration/configure_web.h      debugger/graphics/graphics_breakpoint_observer.cpp @@ -56,6 +70,8 @@ add_executable(yuzu      main.h      ui_settings.cpp      ui_settings.h +    util/limitable_input_dialog.cpp +    util/limitable_input_dialog.h      util/spinbox.cpp      util/spinbox.h      util/util.cpp @@ -74,7 +90,12 @@ set(UIS      configuration/configure_general.ui      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      hotkeys.ui      main.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/applets/software_keyboard.cpp b/src/yuzu/applets/software_keyboard.cpp new file mode 100644 index 000000000..8a26fdff1 --- /dev/null +++ b/src/yuzu/applets/software_keyboard.cpp @@ -0,0 +1,152 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <mutex> +#include <QDialogButtonBox> +#include <QFont> +#include <QLabel> +#include <QLineEdit> +#include <QVBoxLayout> +#include "core/hle/lock.h" +#include "yuzu/applets/software_keyboard.h" +#include "yuzu/main.h" + +QtSoftwareKeyboardValidator::QtSoftwareKeyboardValidator( +    Core::Frontend::SoftwareKeyboardParameters parameters) +    : parameters(std::move(parameters)) {} + +QValidator::State QtSoftwareKeyboardValidator::validate(QString& input, int& pos) const { +    if (input.size() > parameters.max_length) +        return Invalid; +    if (parameters.disable_space && input.contains(' ')) +        return Invalid; +    if (parameters.disable_address && input.contains('@')) +        return Invalid; +    if (parameters.disable_percent && input.contains('%')) +        return Invalid; +    if (parameters.disable_slash && (input.contains('/') || input.contains('\\'))) +        return Invalid; +    if (parameters.disable_number && +        std::any_of(input.begin(), input.end(), [](QChar c) { return c.isDigit(); })) { +        return Invalid; +    } + +    if (parameters.disable_download_code && +        std::any_of(input.begin(), input.end(), [](QChar c) { return c == 'O' || c == 'I'; })) { +        return Invalid; +    } + +    return Acceptable; +} + +QtSoftwareKeyboardDialog::QtSoftwareKeyboardDialog( +    QWidget* parent, Core::Frontend::SoftwareKeyboardParameters parameters_) +    : QDialog(parent), parameters(std::move(parameters_)) { +    layout = new QVBoxLayout; + +    header_label = new QLabel(QString::fromStdU16String(parameters.header_text)); +    header_label->setFont({header_label->font().family(), 11, QFont::Bold}); +    if (header_label->text().isEmpty()) +        header_label->setText(tr("Enter text:")); + +    sub_label = new QLabel(QString::fromStdU16String(parameters.sub_text)); +    sub_label->setFont({sub_label->font().family(), sub_label->font().pointSize(), +                        sub_label->font().weight(), true}); +    sub_label->setHidden(parameters.sub_text.empty()); + +    guide_label = new QLabel(QString::fromStdU16String(parameters.guide_text)); +    guide_label->setHidden(parameters.guide_text.empty()); + +    length_label = new QLabel(QStringLiteral("0/%1").arg(parameters.max_length)); +    length_label->setAlignment(Qt::AlignRight); +    length_label->setFont({length_label->font().family(), 8}); + +    line_edit = new QLineEdit; +    line_edit->setValidator(new QtSoftwareKeyboardValidator(parameters)); +    line_edit->setMaxLength(static_cast<int>(parameters.max_length)); +    line_edit->setText(QString::fromStdU16String(parameters.initial_text)); +    line_edit->setCursorPosition( +        parameters.cursor_at_beginning ? 0 : static_cast<int>(parameters.initial_text.size())); +    line_edit->setEchoMode(parameters.password ? QLineEdit::Password : QLineEdit::Normal); + +    connect(line_edit, &QLineEdit::textChanged, this, [this](const QString& text) { +        length_label->setText(QStringLiteral("%1/%2").arg(text.size()).arg(parameters.max_length)); +    }); + +    buttons = new QDialogButtonBox; +    buttons->addButton(tr("Cancel"), QDialogButtonBox::RejectRole); +    buttons->addButton(parameters.submit_text.empty() +                           ? tr("OK") +                           : QString::fromStdU16String(parameters.submit_text), +                       QDialogButtonBox::AcceptRole); + +    connect(buttons, &QDialogButtonBox::accepted, this, &QtSoftwareKeyboardDialog::accept); +    connect(buttons, &QDialogButtonBox::rejected, this, &QtSoftwareKeyboardDialog::reject); +    layout->addWidget(header_label); +    layout->addWidget(sub_label); +    layout->addWidget(guide_label); +    layout->addWidget(length_label); +    layout->addWidget(line_edit); +    layout->addWidget(buttons); +    setLayout(layout); +    setWindowTitle(tr("Software Keyboard")); +} + +QtSoftwareKeyboardDialog::~QtSoftwareKeyboardDialog() = default; + +void QtSoftwareKeyboardDialog::accept() { +    ok = true; +    text = line_edit->text().toStdU16String(); +    QDialog::accept(); +} + +void QtSoftwareKeyboardDialog::reject() { +    ok = false; +    text.clear(); +    QDialog::reject(); +} + +std::u16string QtSoftwareKeyboardDialog::GetText() const { +    return text; +} + +bool QtSoftwareKeyboardDialog::GetStatus() const { +    return ok; +} + +QtSoftwareKeyboard::QtSoftwareKeyboard(GMainWindow& main_window) { +    connect(this, &QtSoftwareKeyboard::MainWindowGetText, &main_window, +            &GMainWindow::SoftwareKeyboardGetText, Qt::QueuedConnection); +    connect(this, &QtSoftwareKeyboard::MainWindowTextCheckDialog, &main_window, +            &GMainWindow::SoftwareKeyboardInvokeCheckDialog, Qt::BlockingQueuedConnection); +    connect(&main_window, &GMainWindow::SoftwareKeyboardFinishedText, this, +            &QtSoftwareKeyboard::MainWindowFinishedText, Qt::QueuedConnection); +} + +QtSoftwareKeyboard::~QtSoftwareKeyboard() = default; + +void QtSoftwareKeyboard::RequestText(std::function<void(std::optional<std::u16string>)> out, +                                     Core::Frontend::SoftwareKeyboardParameters parameters) const { +    text_output = std::move(out); +    emit MainWindowGetText(parameters); +} + +void QtSoftwareKeyboard::SendTextCheckDialog(std::u16string error_message, +                                             std::function<void()> finished_check) const { +    this->finished_check = std::move(finished_check); +    emit MainWindowTextCheckDialog(error_message); +} + +void QtSoftwareKeyboard::MainWindowFinishedText(std::optional<std::u16string> text) { +    // Acquire the HLE mutex +    std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock); +    text_output(text); +} + +void QtSoftwareKeyboard::MainWindowFinishedCheckDialog() { +    // Acquire the HLE mutex +    std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock); +    finished_check(); +} diff --git a/src/yuzu/applets/software_keyboard.h b/src/yuzu/applets/software_keyboard.h new file mode 100644 index 000000000..c63720ba4 --- /dev/null +++ b/src/yuzu/applets/software_keyboard.h @@ -0,0 +1,79 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <QDialog> +#include <QValidator> +#include "common/assert.h" +#include "core/frontend/applets/software_keyboard.h" + +class GMainWindow; +class QDialogButtonBox; +class QLabel; +class QLineEdit; +class QVBoxLayout; +class QtSoftwareKeyboard; + +class QtSoftwareKeyboardValidator final : public QValidator { +public: +    explicit QtSoftwareKeyboardValidator(Core::Frontend::SoftwareKeyboardParameters parameters); +    State validate(QString& input, int& pos) const override; + +private: +    Core::Frontend::SoftwareKeyboardParameters parameters; +}; + +class QtSoftwareKeyboardDialog final : public QDialog { +    Q_OBJECT + +public: +    QtSoftwareKeyboardDialog(QWidget* parent, +                             Core::Frontend::SoftwareKeyboardParameters parameters); +    ~QtSoftwareKeyboardDialog() override; + +    void accept() override; +    void reject() override; + +    std::u16string GetText() const; +    bool GetStatus() const; + +private: +    bool ok = false; +    std::u16string text; + +    QDialogButtonBox* buttons; +    QLabel* header_label; +    QLabel* sub_label; +    QLabel* guide_label; +    QLabel* length_label; +    QLineEdit* line_edit; +    QVBoxLayout* layout; + +    Core::Frontend::SoftwareKeyboardParameters parameters; +}; + +class QtSoftwareKeyboard final : public QObject, public Core::Frontend::SoftwareKeyboardApplet { +    Q_OBJECT + +public: +    explicit QtSoftwareKeyboard(GMainWindow& parent); +    ~QtSoftwareKeyboard() override; + +    void RequestText(std::function<void(std::optional<std::u16string>)> out, +                     Core::Frontend::SoftwareKeyboardParameters parameters) const override; +    void SendTextCheckDialog(std::u16string error_message, +                             std::function<void()> finished_check) const override; + +signals: +    void MainWindowGetText(Core::Frontend::SoftwareKeyboardParameters parameters) const; +    void MainWindowTextCheckDialog(std::u16string error_message) const; + +private: +    void MainWindowFinishedText(std::optional<std::u16string> text); +    void MainWindowFinishedCheckDialog(); + +    mutable std::function<void(std::optional<std::u16string>)> text_output; +    mutable std::function<void()> finished_check; +}; diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index 39eef8858..40db7a5e9 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -14,6 +14,8 @@  #include "input_common/keyboard.h"  #include "input_common/main.h"  #include "input_common/motion_emu.h" +#include "video_core/renderer_base.h" +#include "video_core/video_core.h"  #include "yuzu/bootmanager.h"  EmuThread::EmuThread(GRenderWindow* render_window) : render_window(render_window) {} @@ -310,7 +312,7 @@ void GRenderWindow::InitRenderTarget() {      // TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground,      // WA_DontShowOnScreen, WA_DeleteOnClose      QGLFormat fmt; -    fmt.setVersion(3, 3); +    fmt.setVersion(4, 3);      fmt.setProfile(QGLFormat::CoreProfile);      fmt.setSwapInterval(false); @@ -333,6 +335,22 @@ void GRenderWindow::InitRenderTarget() {      BackupGeometry();  } +void GRenderWindow::CaptureScreenshot(u16 res_scale, const QString& screenshot_path) { +    auto& renderer = Core::System::GetInstance().Renderer(); + +    if (!res_scale) +        res_scale = VideoCore::GetResolutionScaleFactor(renderer); + +    const Layout::FramebufferLayout layout{Layout::FrameLayoutFromResolutionScale(res_scale)}; +    screenshot_image = QImage(QSize(layout.width, layout.height), QImage::Format_RGB32); +    renderer.RequestScreenshot(screenshot_image.bits(), +                               [=] { +                                   screenshot_image.mirrored(false, true).save(screenshot_path); +                                   LOG_INFO(Frontend, "The screenshot is saved."); +                               }, +                               layout); +} +  void GRenderWindow::OnMinimalClientAreaChangeRequest(      const std::pair<unsigned, unsigned>& minimal_size) {      setMinimumSize(minimal_size.first, minimal_size.second); diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h index 873985564..4e3028215 100644 --- a/src/yuzu/bootmanager.h +++ b/src/yuzu/bootmanager.h @@ -8,6 +8,7 @@  #include <condition_variable>  #include <mutex>  #include <QGLWidget> +#include <QImage>  #include <QThread>  #include "common/thread.h"  #include "core/core.h" @@ -139,6 +140,8 @@ public:      void InitRenderTarget(); +    void CaptureScreenshot(u16 res_scale, const QString& screenshot_path); +  public slots:      void moveContext(); // overridden @@ -165,6 +168,9 @@ private:      EmuThread* emu_thread; +    /// Temporary storage of the screenshot taken +    QImage screenshot_image; +  protected:      void showEvent(QShowEvent* event) override;  }; diff --git a/src/yuzu/compatdb.cpp b/src/yuzu/compatdb.cpp index 91e754274..5f0896f84 100644 --- a/src/yuzu/compatdb.cpp +++ b/src/yuzu/compatdb.cpp @@ -5,6 +5,7 @@  #include <QButtonGroup>  #include <QMessageBox>  #include <QPushButton> +#include <QtConcurrent/qtconcurrentrun.h>  #include "common/logging/log.h"  #include "common/telemetry.h"  #include "core/core.h" @@ -23,6 +24,8 @@ CompatDB::CompatDB(QWidget* parent)      connect(ui->radioButton_IntroMenu, &QRadioButton::clicked, this, &CompatDB::EnableNext);      connect(ui->radioButton_WontBoot, &QRadioButton::clicked, this, &CompatDB::EnableNext);      connect(button(NextButton), &QPushButton::clicked, this, &CompatDB::Submit); +    connect(&testcase_watcher, &QFutureWatcher<bool>::finished, this, +            &CompatDB::OnTestcaseSubmitted);  }  CompatDB::~CompatDB() = default; @@ -48,18 +51,38 @@ void CompatDB::Submit() {          }          break;      case CompatDBPage::Final: +        back();          LOG_DEBUG(Frontend, "Compatibility Rating: {}", compatibility->checkedId());          Core::Telemetry().AddField(Telemetry::FieldType::UserFeedback, "Compatibility",                                     compatibility->checkedId()); -        // older versions of QT don't support the "NoCancelButtonOnLastPage" option, this is a -        // workaround + +        button(NextButton)->setEnabled(false); +        button(NextButton)->setText(tr("Submitting"));          button(QWizard::CancelButton)->setVisible(false); + +        testcase_watcher.setFuture(QtConcurrent::run( +            [this]() { return Core::System::GetInstance().TelemetrySession().SubmitTestcase(); }));          break;      default:          LOG_ERROR(Frontend, "Unexpected page: {}", currentId());      }  } +void CompatDB::OnTestcaseSubmitted() { +    if (!testcase_watcher.result()) { +        QMessageBox::critical(this, tr("Communication error"), +                              tr("An error occured while sending the Testcase")); +        button(NextButton)->setEnabled(true); +        button(NextButton)->setText(tr("Next")); +        button(QWizard::CancelButton)->setVisible(true); +    } else { +        next(); +        // older versions of QT don't support the "NoCancelButtonOnLastPage" option, this is a +        // workaround +        button(QWizard::CancelButton)->setVisible(false); +    } +} +  void CompatDB::EnableNext() {      button(NextButton)->setEnabled(true);  } diff --git a/src/yuzu/compatdb.h b/src/yuzu/compatdb.h index ca0dd11d6..5381f67f7 100644 --- a/src/yuzu/compatdb.h +++ b/src/yuzu/compatdb.h @@ -5,6 +5,7 @@  #pragma once  #include <memory> +#include <QFutureWatcher>  #include <QWizard>  namespace Ui { @@ -19,8 +20,11 @@ public:      ~CompatDB();  private: +    QFutureWatcher<bool> testcase_watcher; +      std::unique_ptr<Ui::CompatDB> ui;      void Submit(); +    void OnTestcaseSubmitted();      void EnableNext();  }; diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index d4fd60a73..165d70e9c 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -4,7 +4,9 @@  #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"  #include "yuzu/configuration/config.h"  #include "yuzu/ui_settings.h" @@ -47,40 +49,317 @@ const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> Config:      },  }}; -void Config::ReadValues() { -    qt_config->beginGroup("Controls"); +const std::array<int, Settings::NativeMouseButton::NumMouseButtons> Config::default_mouse_buttons = +    { +        Qt::Key_BracketLeft, Qt::Key_BracketRight, Qt::Key_Apostrophe, Qt::Key_Minus, Qt::Key_Equal, +}; + +const std::array<int, Settings::NativeKeyboard::NumKeyboardKeys> Config::default_keyboard_keys = { +    0, +    0, +    0, +    0, +    Qt::Key_A, +    Qt::Key_B, +    Qt::Key_C, +    Qt::Key_D, +    Qt::Key_E, +    Qt::Key_F, +    Qt::Key_G, +    Qt::Key_H, +    Qt::Key_I, +    Qt::Key_J, +    Qt::Key_K, +    Qt::Key_L, +    Qt::Key_M, +    Qt::Key_N, +    Qt::Key_O, +    Qt::Key_P, +    Qt::Key_Q, +    Qt::Key_R, +    Qt::Key_S, +    Qt::Key_T, +    Qt::Key_U, +    Qt::Key_V, +    Qt::Key_W, +    Qt::Key_X, +    Qt::Key_Y, +    Qt::Key_Z, +    Qt::Key_1, +    Qt::Key_2, +    Qt::Key_3, +    Qt::Key_4, +    Qt::Key_5, +    Qt::Key_6, +    Qt::Key_7, +    Qt::Key_8, +    Qt::Key_9, +    Qt::Key_0, +    Qt::Key_Enter, +    Qt::Key_Escape, +    Qt::Key_Backspace, +    Qt::Key_Tab, +    Qt::Key_Space, +    Qt::Key_Minus, +    Qt::Key_Equal, +    Qt::Key_BracketLeft, +    Qt::Key_BracketRight, +    Qt::Key_Backslash, +    Qt::Key_Dead_Tilde, +    Qt::Key_Semicolon, +    Qt::Key_Apostrophe, +    Qt::Key_Dead_Grave, +    Qt::Key_Comma, +    Qt::Key_Period, +    Qt::Key_Slash, +    Qt::Key_CapsLock, + +    Qt::Key_F1, +    Qt::Key_F2, +    Qt::Key_F3, +    Qt::Key_F4, +    Qt::Key_F5, +    Qt::Key_F6, +    Qt::Key_F7, +    Qt::Key_F8, +    Qt::Key_F9, +    Qt::Key_F10, +    Qt::Key_F11, +    Qt::Key_F12, + +    Qt::Key_SysReq, +    Qt::Key_ScrollLock, +    Qt::Key_Pause, +    Qt::Key_Insert, +    Qt::Key_Home, +    Qt::Key_PageUp, +    Qt::Key_Delete, +    Qt::Key_End, +    Qt::Key_PageDown, +    Qt::Key_Right, +    Qt::Key_Left, +    Qt::Key_Down, +    Qt::Key_Up, + +    Qt::Key_NumLock, +    Qt::Key_Slash, +    Qt::Key_Asterisk, +    Qt::Key_Minus, +    Qt::Key_Plus, +    Qt::Key_Enter, +    Qt::Key_1, +    Qt::Key_2, +    Qt::Key_3, +    Qt::Key_4, +    Qt::Key_5, +    Qt::Key_6, +    Qt::Key_7, +    Qt::Key_8, +    Qt::Key_9, +    Qt::Key_0, +    Qt::Key_Period, + +    0, +    0, +    Qt::Key_PowerOff, +    Qt::Key_Equal, + +    Qt::Key_F13, +    Qt::Key_F14, +    Qt::Key_F15, +    Qt::Key_F16, +    Qt::Key_F17, +    Qt::Key_F18, +    Qt::Key_F19, +    Qt::Key_F20, +    Qt::Key_F21, +    Qt::Key_F22, +    Qt::Key_F23, +    Qt::Key_F24, + +    Qt::Key_Open, +    Qt::Key_Help, +    Qt::Key_Menu, +    0, +    Qt::Key_Stop, +    Qt::Key_AudioRepeat, +    Qt::Key_Undo, +    Qt::Key_Cut, +    Qt::Key_Copy, +    Qt::Key_Paste, +    Qt::Key_Find, +    Qt::Key_VolumeMute, +    Qt::Key_VolumeUp, +    Qt::Key_VolumeDown, +    Qt::Key_CapsLock, +    Qt::Key_NumLock, +    Qt::Key_ScrollLock, +    Qt::Key_Comma, + +    Qt::Key_ParenLeft, +    Qt::Key_ParenRight, +}; + +const std::array<int, Settings::NativeKeyboard::NumKeyboardMods> Config::default_keyboard_mods = { +    Qt::Key_Control, Qt::Key_Shift, Qt::Key_Alt,   Qt::Key_ApplicationLeft, +    Qt::Key_Control, Qt::Key_Shift, Qt::Key_AltGr, Qt::Key_ApplicationRight, +}; + +void Config::ReadPlayerValues() { +    for (std::size_t p = 0; p < Settings::values.players.size(); ++p) { +        auto& player = Settings::values.players[p]; + +        player.connected = qt_config->value(QString("player_%1_connected").arg(p), false).toBool(); + +        player.type = static_cast<Settings::ControllerType>( +            qt_config +                ->value(QString("player_%1_type").arg(p), +                        static_cast<u8>(Settings::ControllerType::DualJoycon)) +                .toUInt()); + +        player.body_color_left = qt_config +                                     ->value(QString("player_%1_body_color_left").arg(p), +                                             Settings::JOYCON_BODY_NEON_BLUE) +                                     .toUInt(); +        player.body_color_right = qt_config +                                      ->value(QString("player_%1_body_color_right").arg(p), +                                              Settings::JOYCON_BODY_NEON_RED) +                                      .toUInt(); +        player.button_color_left = qt_config +                                       ->value(QString("player_%1_button_color_left").arg(p), +                                               Settings::JOYCON_BUTTONS_NEON_BLUE) +                                       .toUInt(); +        player.button_color_right = qt_config +                                        ->value(QString("player_%1_button_color_right").arg(p), +                                                Settings::JOYCON_BUTTONS_NEON_RED) +                                        .toUInt(); + +        for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { +            std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); +            player.buttons[i] = +                qt_config +                    ->value(QString("player_%1_").arg(p) + Settings::NativeButton::mapping[i], +                            QString::fromStdString(default_param)) +                    .toString() +                    .toStdString(); +            if (player.buttons[i].empty()) +                player.buttons[i] = default_param; +        } + +        for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { +            std::string default_param = InputCommon::GenerateAnalogParamFromKeys( +                default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], +                default_analogs[i][3], default_analogs[i][4], 0.5f); +            player.analogs[i] = +                qt_config +                    ->value(QString("player_%1_").arg(p) + Settings::NativeAnalog::mapping[i], +                            QString::fromStdString(default_param)) +                    .toString() +                    .toStdString(); +            if (player.analogs[i].empty()) +                player.analogs[i] = default_param; +        } +    } + +    std::stable_partition( +        Settings::values.players.begin(), +        Settings::values.players.begin() + +            Service::HID::Controller_NPad::NPadIdToIndex(Service::HID::NPAD_HANDHELD), +        [](const auto& player) { return player.connected; }); +} + +void Config::ReadDebugValues() { +    Settings::values.debug_pad_enabled = qt_config->value("debug_pad_enabled", false).toBool();      for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {          std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); -        Settings::values.buttons[i] = +        Settings::values.debug_pad_buttons[i] =              qt_config -                ->value(Settings::NativeButton::mapping[i], QString::fromStdString(default_param)) +                ->value(QString("debug_pad_") + Settings::NativeButton::mapping[i], +                        QString::fromStdString(default_param))                  .toString()                  .toStdString(); -        if (Settings::values.buttons[i].empty()) -            Settings::values.buttons[i] = default_param; +        if (Settings::values.debug_pad_buttons[i].empty()) +            Settings::values.debug_pad_buttons[i] = default_param;      }      for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {          std::string default_param = InputCommon::GenerateAnalogParamFromKeys(              default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],              default_analogs[i][3], default_analogs[i][4], 0.5f); -        Settings::values.analogs[i] = +        Settings::values.debug_pad_analogs[i] = +            qt_config +                ->value(QString("debug_pad_") + Settings::NativeAnalog::mapping[i], +                        QString::fromStdString(default_param)) +                .toString() +                .toStdString(); +        if (Settings::values.debug_pad_analogs[i].empty()) +            Settings::values.debug_pad_analogs[i] = default_param; +    } +} + +void Config::ReadKeyboardValues() { +    Settings::values.keyboard_enabled = qt_config->value("keyboard_enabled", false).toBool(); + +    std::transform(default_keyboard_keys.begin(), default_keyboard_keys.end(), +                   Settings::values.keyboard_keys.begin(), InputCommon::GenerateKeyboardParam); +    std::transform(default_keyboard_mods.begin(), default_keyboard_mods.end(), +                   Settings::values.keyboard_keys.begin() + +                       Settings::NativeKeyboard::LeftControlKey, +                   InputCommon::GenerateKeyboardParam); +    std::transform(default_keyboard_mods.begin(), default_keyboard_mods.end(), +                   Settings::values.keyboard_mods.begin(), InputCommon::GenerateKeyboardParam); +} + +void Config::ReadMouseValues() { +    Settings::values.mouse_enabled = qt_config->value("mouse_enabled", false).toBool(); + +    for (int i = 0; i < Settings::NativeMouseButton::NumMouseButtons; ++i) { +        std::string default_param = InputCommon::GenerateKeyboardParam(default_mouse_buttons[i]); +        Settings::values.mouse_buttons[i] =              qt_config -                ->value(Settings::NativeAnalog::mapping[i], QString::fromStdString(default_param)) +                ->value(QString("mouse_") + Settings::NativeMouseButton::mapping[i], +                        QString::fromStdString(default_param))                  .toString()                  .toStdString(); -        if (Settings::values.analogs[i].empty()) -            Settings::values.analogs[i] = default_param; +        if (Settings::values.mouse_buttons[i].empty()) +            Settings::values.mouse_buttons[i] = default_param;      } +} + +void Config::ReadTouchscreenValues() { +    Settings::values.touchscreen.enabled = qt_config->value("touchscreen_enabled", true).toBool(); +    Settings::values.touchscreen.device = +        qt_config->value("touchscreen_device", "engine:emu_window").toString().toStdString(); + +    Settings::values.touchscreen.finger = qt_config->value("touchscreen_finger", 0).toUInt(); +    Settings::values.touchscreen.rotation_angle = qt_config->value("touchscreen_angle", 0).toUInt(); +    Settings::values.touchscreen.diameter_x = +        qt_config->value("touchscreen_diameter_x", 15).toUInt(); +    Settings::values.touchscreen.diameter_y = +        qt_config->value("touchscreen_diameter_y", 15).toUInt(); +    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"); + +    ReadPlayerValues(); +    ReadDebugValues(); +    ReadKeyboardValues(); +    ReadMouseValues(); +    ReadTouchscreenValues();      Settings::values.motion_device =          qt_config->value("motion_device", "engine:motion_emu,update_period:100,sensitivity:0.01")              .toString()              .toStdString(); -    Settings::values.touch_device = -        qt_config->value("touch_device", "engine:emu_window").toString().toStdString(); - -    qt_config->endGroup();      qt_config->beginGroup("Core");      Settings::values.use_cpu_jit = qt_config->value("use_cpu_jit", true).toBool(); @@ -126,6 +405,11 @@ void Config::ReadValues() {              .toStdString());      qt_config->endGroup(); +    qt_config->beginGroup("Core"); +    Settings::values.use_cpu_jit = qt_config->value("use_cpu_jit", true).toBool(); +    Settings::values.use_multi_core = qt_config->value("use_multi_core", false).toBool(); +    qt_config->endGroup(); +      qt_config->beginGroup("System");      Settings::values.use_docked_mode = qt_config->value("use_docked_mode", false).toBool();      Settings::values.enable_nfc = qt_config->value("enable_nfc", true).toBool(); @@ -134,6 +418,14 @@ void Config::ReadValues() {                                                      Service::Account::MAX_USERS - 1);      Settings::values.language_index = qt_config->value("language_index", 1).toInt(); + +    const auto enabled = qt_config->value("rng_seed_enabled", false).toBool(); +    if (enabled) { +        Settings::values.rng_seed = qt_config->value("rng_seed", 0).toULongLong(); +    } else { +        Settings::values.rng_seed = std::nullopt; +    } +      qt_config->endGroup();      qt_config->beginGroup("Miscellaneous"); @@ -145,6 +437,8 @@ void Config::ReadValues() {      Settings::values.use_gdbstub = qt_config->value("use_gdbstub", false).toBool();      Settings::values.gdbstub_port = qt_config->value("gdbstub_port", 24689).toInt();      Settings::values.program_args = qt_config->value("program_args", "").toString().toStdString(); +    Settings::values.dump_exefs = qt_config->value("dump_exefs", false).toBool(); +    Settings::values.dump_nso = qt_config->value("dump_nso", false).toBool();      qt_config->endGroup();      qt_config->beginGroup("WebService"); @@ -155,13 +449,33 @@ 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 =          qt_config->value("enable_discord_presence", true).toBool(); +    UISettings::values.screenshot_resolution_factor = +        static_cast<u16>(qt_config->value("screenshot_resolution_factor", 0).toUInt()); +    UISettings::values.select_user_on_boot = +        qt_config->value("select_user_on_boot", false).toBool();      qt_config->beginGroup("UIGameList");      UISettings::values.show_unknown = qt_config->value("show_unknown", true).toBool(); +    UISettings::values.show_add_ons = qt_config->value("show_add_ons", true).toBool();      UISettings::values.icon_size = qt_config->value("icon_size", 64).toUInt();      UISettings::values.row_1_text_id = qt_config->value("row_1_text_id", 3).toUInt();      UISettings::values.row_2_text_id = qt_config->value("row_2_text_id", 2).toUInt(); @@ -216,22 +530,86 @@ 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();  } -void Config::SaveValues() { -    qt_config->beginGroup("Controls"); +void Config::SavePlayerValues() { +    for (std::size_t p = 0; p < Settings::values.players.size(); ++p) { +        const auto& player = Settings::values.players[p]; + +        qt_config->setValue(QString("player_%1_connected").arg(p), player.connected); +        qt_config->setValue(QString("player_%1_type").arg(p), static_cast<u8>(player.type)); + +        qt_config->setValue(QString("player_%1_body_color_left").arg(p), player.body_color_left); +        qt_config->setValue(QString("player_%1_body_color_right").arg(p), player.body_color_right); +        qt_config->setValue(QString("player_%1_button_color_left").arg(p), +                            player.button_color_left); +        qt_config->setValue(QString("player_%1_button_color_right").arg(p), +                            player.button_color_right); + +        for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { +            qt_config->setValue(QString("player_%1_").arg(p) + +                                    QString::fromStdString(Settings::NativeButton::mapping[i]), +                                QString::fromStdString(player.buttons[i])); +        } +        for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { +            qt_config->setValue(QString("player_%1_").arg(p) + +                                    QString::fromStdString(Settings::NativeAnalog::mapping[i]), +                                QString::fromStdString(player.analogs[i])); +        } +    } +} + +void Config::SaveDebugValues() { +    qt_config->setValue("debug_pad_enabled", Settings::values.debug_pad_enabled);      for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { -        qt_config->setValue(QString::fromStdString(Settings::NativeButton::mapping[i]), -                            QString::fromStdString(Settings::values.buttons[i])); +        qt_config->setValue(QString("debug_pad_") + +                                QString::fromStdString(Settings::NativeButton::mapping[i]), +                            QString::fromStdString(Settings::values.debug_pad_buttons[i]));      }      for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { -        qt_config->setValue(QString::fromStdString(Settings::NativeAnalog::mapping[i]), -                            QString::fromStdString(Settings::values.analogs[i])); +        qt_config->setValue(QString("debug_pad_") + +                                QString::fromStdString(Settings::NativeAnalog::mapping[i]), +                            QString::fromStdString(Settings::values.debug_pad_analogs[i]));      } +} + +void Config::SaveMouseValues() { +    qt_config->setValue("mouse_enabled", Settings::values.mouse_enabled); + +    for (int i = 0; i < Settings::NativeMouseButton::NumMouseButtons; ++i) { +        qt_config->setValue(QString("mouse_") + +                                QString::fromStdString(Settings::NativeMouseButton::mapping[i]), +                            QString::fromStdString(Settings::values.mouse_buttons[i])); +    } +} + +void Config::SaveTouchscreenValues() { +    qt_config->setValue("touchscreen_enabled", Settings::values.touchscreen.enabled); +    qt_config->setValue("touchscreen_device", +                        QString::fromStdString(Settings::values.touchscreen.device)); + +    qt_config->setValue("touchscreen_finger", Settings::values.touchscreen.finger); +    qt_config->setValue("touchscreen_angle", Settings::values.touchscreen.rotation_angle); +    qt_config->setValue("touchscreen_diameter_x", Settings::values.touchscreen.diameter_x); +    qt_config->setValue("touchscreen_diameter_y", Settings::values.touchscreen.diameter_y); +} + +void Config::SaveValues() { +    qt_config->beginGroup("Controls"); + +    SavePlayerValues(); +    SaveDebugValues(); +    SaveMouseValues(); +    SaveTouchscreenValues(); +      qt_config->setValue("motion_device", QString::fromStdString(Settings::values.motion_device)); -    qt_config->setValue("touch_device", QString::fromStdString(Settings::values.touch_device)); +    qt_config->setValue("keyboard_enabled", Settings::values.keyboard_enabled); +      qt_config->endGroup();      qt_config->beginGroup("Core"); @@ -270,8 +648,11 @@ void Config::SaveValues() {      qt_config->setValue("use_docked_mode", Settings::values.use_docked_mode);      qt_config->setValue("enable_nfc", Settings::values.enable_nfc);      qt_config->setValue("current_user", Settings::values.current_user); -      qt_config->setValue("language_index", Settings::values.language_index); + +    qt_config->setValue("rng_seed_enabled", Settings::values.rng_seed.has_value()); +    qt_config->setValue("rng_seed", Settings::values.rng_seed.value_or(0)); +      qt_config->endGroup();      qt_config->beginGroup("Miscellaneous"); @@ -283,6 +664,8 @@ void Config::SaveValues() {      qt_config->setValue("use_gdbstub", Settings::values.use_gdbstub);      qt_config->setValue("gdbstub_port", Settings::values.gdbstub_port);      qt_config->setValue("program_args", QString::fromStdString(Settings::values.program_args)); +    qt_config->setValue("dump_exefs", Settings::values.dump_exefs); +    qt_config->setValue("dump_nso", Settings::values.dump_nso);      qt_config->endGroup();      qt_config->beginGroup("WebService"); @@ -292,12 +675,31 @@ 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(static_cast<int>(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); +    qt_config->setValue("screenshot_resolution_factor", +                        UISettings::values.screenshot_resolution_factor); +    qt_config->setValue("select_user_on_boot", UISettings::values.select_user_on_boot);      qt_config->beginGroup("UIGameList");      qt_config->setValue("show_unknown", UISettings::values.show_unknown); +    qt_config->setValue("show_add_ons", UISettings::values.show_add_ons);      qt_config->setValue("icon_size", UISettings::values.icon_size);      qt_config->setValue("row_1_text_id", UISettings::values.row_1_text_id);      qt_config->setValue("row_2_text_id", UISettings::values.row_2_text_id); @@ -315,6 +717,7 @@ void Config::SaveValues() {      qt_config->beginGroup("Paths");      qt_config->setValue("romsPath", UISettings::values.roms_path);      qt_config->setValue("symbolsPath", UISettings::values.symbols_path); +    qt_config->setValue("screenshotPath", UISettings::values.screenshot_path);      qt_config->setValue("gameListRootDir", UISettings::values.gamedir);      qt_config->setValue("gameListDeepScan", UISettings::values.gamedir_deepscan);      qt_config->setValue("recentFiles", UISettings::values.recent_files); @@ -336,6 +739,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 9c99c1b75..e73ad19bb 100644 --- a/src/yuzu/configuration/config.h +++ b/src/yuzu/configuration/config.h @@ -22,10 +22,25 @@ public:      static const std::array<int, Settings::NativeButton::NumButtons> default_buttons;      static const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> default_analogs; +    static const std::array<int, Settings::NativeMouseButton::NumMouseButtons> +        default_mouse_buttons; +    static const std::array<int, Settings::NativeKeyboard::NumKeyboardKeys> default_keyboard_keys; +    static const std::array<int, Settings::NativeKeyboard::NumKeyboardMods> default_keyboard_mods;  private:      void ReadValues(); +    void ReadPlayerValues(); +    void ReadDebugValues(); +    void ReadKeyboardValues(); +    void ReadMouseValues(); +    void ReadTouchscreenValues(); +    void ApplyDefaultProfileIfInputInvalid(); +      void SaveValues(); +    void SavePlayerValues(); +    void SaveDebugValues(); +    void SaveMouseValues(); +    void SaveTouchscreenValues();      std::unique_ptr<QSettings> qt_config;      std::string qt_config_loc; diff --git a/src/yuzu/configuration/configure.ui b/src/yuzu/configuration/configure.ui index 9b297df28..ce833b6c8 100644 --- a/src/yuzu/configuration/configure.ui +++ b/src/yuzu/configuration/configure.ui @@ -6,8 +6,8 @@     <rect>      <x>0</x>      <y>0</y> -    <width>461</width> -    <height>500</height> +    <width>382</width> +    <height>241</height>     </rect>    </property>    <property name="windowTitle"> @@ -15,51 +15,71 @@    </property>    <layout class="QVBoxLayout" name="verticalLayout">     <item> -    <widget class="QTabWidget" name="tabWidget"> -     <property name="currentIndex"> -      <number>0</number> -     </property> -     <widget class="ConfigureGeneral" name="generalTab"> -      <attribute name="title"> -       <string>General</string> -      </attribute> -     </widget> -      <widget class="ConfigureGameList" name="gameListTab"> +    <layout class="QHBoxLayout" name="horizontalLayout"> +     <item> +      <widget class="QListWidget" name="selectorList"> +       <property name="minimumSize"> +        <size> +         <width>150</width> +         <height>0</height> +        </size> +       </property> +       <property name="maximumSize"> +        <size> +         <width>150</width> +         <height>16777215</height> +        </size> +       </property> +      </widget> +     </item> +     <item> +      <widget class="QTabWidget" name="tabWidget"> +       <property name="currentIndex"> +        <number>0</number> +       </property> +       <widget class="ConfigureGeneral" name="generalTab">          <attribute name="title"> -          <string>Game List</string> +         <string>General</string>          </attribute> -      </widget> -     <widget class="ConfigureSystem" name="systemTab"> -      <attribute name="title"> -       <string>System</string> -      </attribute> -     </widget> -     <widget class="ConfigureInput" name="inputTab"> -      <attribute name="title"> -       <string>Input</string> -      </attribute> -     </widget> -     <widget class="ConfigureGraphics" name="graphicsTab"> -      <attribute name="title"> -       <string>Graphics</string> -      </attribute> -     </widget> -     <widget class="ConfigureAudio" name="audioTab"> -      <attribute name="title"> -       <string>Audio</string> -      </attribute> -     </widget> -     <widget class="ConfigureDebug" name="debugTab"> -      <attribute name="title"> -       <string>Debug</string> -      </attribute> -     </widget> -      <widget class="ConfigureWeb" name="webTab"> +       </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="ConfigureInputSimple" name="inputTab"> +        <attribute name="title"> +         <string>Input</string> +        </attribute> +       </widget> +       <widget class="ConfigureGraphics" name="graphicsTab"> +        <attribute name="title"> +         <string>Graphics</string> +        </attribute> +       </widget> +       <widget class="ConfigureAudio" name="audioTab"> +        <attribute name="title"> +         <string>Audio</string> +        </attribute> +       </widget> +       <widget class="ConfigureDebug" name="debugTab"> +        <attribute name="title"> +         <string>Debug</string> +        </attribute> +       </widget> +       <widget class="ConfigureWeb" name="webTab">          <attribute name="title"> -          <string>Web</string> +         <string>Web</string>          </attribute> +       </widget>        </widget> -    </widget> +     </item> +    </layout>     </item>     <item>      <widget class="QDialogButtonBox" name="buttonBox"> @@ -77,12 +97,6 @@     <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>ConfigureSystem</class>     <extends>QWidget</extends> @@ -102,23 +116,29 @@     <container>1</container>    </customwidget>    <customwidget> -   <class>ConfigureInput</class> +   <class>ConfigureGraphics</class>     <extends>QWidget</extends> -   <header>configuration/configure_input.h</header> +   <header>configuration/configure_graphics.h</header>     <container>1</container>    </customwidget>    <customwidget> -   <class>ConfigureGraphics</class> +   <class>ConfigureWeb</class>     <extends>QWidget</extends> -   <header>configuration/configure_graphics.h</header> +   <header>configuration/configure_web.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>ConfigureInputSimple</class> +   <extends>QWidget</extends> +   <header>configuration/configure_input_simple.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_audio.h b/src/yuzu/configuration/configure_audio.h index 207f9dfb3..8771421c0 100644 --- a/src/yuzu/configuration/configure_audio.h +++ b/src/yuzu/configuration/configure_audio.h @@ -16,15 +16,14 @@ class ConfigureAudio : public QWidget {  public:      explicit ConfigureAudio(QWidget* parent = nullptr); -    ~ConfigureAudio(); +    ~ConfigureAudio() override;      void applyConfiguration();      void retranslateUi(); -public slots: +private:      void updateAudioDevices(int sink_index); -private:      void setConfiguration();      void setOutputSinkFromSinkID();      void setAudioDeviceFromDeviceID(); diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp index 9e765fc93..aa7de7b54 100644 --- a/src/yuzu/configuration/configure_debug.cpp +++ b/src/yuzu/configuration/configure_debug.cpp @@ -34,6 +34,8 @@ void ConfigureDebug::setConfiguration() {      ui->toggle_console->setChecked(UISettings::values.show_console);      ui->log_filter_edit->setText(QString::fromStdString(Settings::values.log_filter));      ui->homebrew_args_edit->setText(QString::fromStdString(Settings::values.program_args)); +    ui->dump_exefs->setChecked(Settings::values.dump_exefs); +    ui->dump_decompressed_nso->setChecked(Settings::values.dump_nso);  }  void ConfigureDebug::applyConfiguration() { @@ -42,6 +44,8 @@ void ConfigureDebug::applyConfiguration() {      UISettings::values.show_console = ui->toggle_console->isChecked();      Settings::values.log_filter = ui->log_filter_edit->text().toStdString();      Settings::values.program_args = ui->homebrew_args_edit->text().toStdString(); +    Settings::values.dump_exefs = ui->dump_exefs->isChecked(); +    Settings::values.dump_nso = ui->dump_decompressed_nso->isChecked();      Debugger::ToggleConsole();      Log::Filter filter;      filter.ParseFilterString(Settings::values.log_filter); diff --git a/src/yuzu/configuration/configure_debug.h b/src/yuzu/configuration/configure_debug.h index d167eb996..c6420b18c 100644 --- a/src/yuzu/configuration/configure_debug.h +++ b/src/yuzu/configuration/configure_debug.h @@ -16,13 +16,12 @@ class ConfigureDebug : public QWidget {  public:      explicit ConfigureDebug(QWidget* parent = nullptr); -    ~ConfigureDebug(); +    ~ConfigureDebug() override;      void applyConfiguration();  private:      void setConfiguration(); -private:      std::unique_ptr<Ui::ConfigureDebug> ui;  }; diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui index ff4987604..758a92335 100644 --- a/src/yuzu/configuration/configure_debug.ui +++ b/src/yuzu/configuration/configure_debug.ui @@ -7,7 +7,7 @@      <x>0</x>      <y>0</y>      <width>400</width> -    <height>300</height> +    <height>357</height>     </rect>    </property>    <property name="windowTitle"> @@ -130,6 +130,35 @@      </widget>     </item>     <item> +    <widget class="QGroupBox" name="groupBox_4"> +     <property name="title"> +      <string>Dump</string> +     </property> +     <layout class="QVBoxLayout" name="verticalLayout_4"> +      <item> +       <widget class="QCheckBox" name="dump_decompressed_nso"> +        <property name="whatsThis"> +         <string>When checked, any NSO yuzu tries to load or patch will be copied decompressed to the yuzu/dump directory.</string> +        </property> +        <property name="text"> +         <string>Dump Decompressed NSOs</string> +        </property> +       </widget> +      </item> +      <item> +       <widget class="QCheckBox" name="dump_exefs"> +        <property name="whatsThis"> +         <string>When checked, any game that yuzu loads will have its ExeFS dumped to the yuzu/dump directory.</string> +        </property> +        <property name="text"> +         <string>Dump ExeFS</string> +        </property> +       </widget> +      </item> +     </layout> +    </widget> +   </item> +   <item>      <spacer name="verticalSpacer">       <property name="orientation">        <enum>Qt::Vertical</enum> diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp index 3905423e9..90d7c6372 100644 --- a/src/yuzu/configuration/configure_dialog.cpp +++ b/src/yuzu/configuration/configure_dialog.cpp @@ -2,6 +2,8 @@  // Licensed under GPLv2 or any later version  // Refer to the license.txt file included. +#include <QHash> +#include <QListWidgetItem>  #include "core/settings.h"  #include "ui_configure.h"  #include "yuzu/configuration/config.h" @@ -13,6 +15,13 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, const HotkeyRegistry& registry      ui->setupUi(this);      ui->generalTab->PopulateHotkeyList(registry);      this->setConfiguration(); +    this->PopulateSelectionList(); +    connect(ui->selectorList, &QListWidget::itemSelectionChanged, this, +            &ConfigureDialog::UpdateVisibleTabs); + +    adjustSize(); + +    ui->selectorList->setCurrentRow(0);  }  ConfigureDialog::~ConfigureDialog() = default; @@ -30,3 +39,37 @@ void ConfigureDialog::applyConfiguration() {      ui->webTab->applyConfiguration();      Settings::Apply();  } + +void ConfigureDialog::PopulateSelectionList() { +    const std::array<std::pair<QString, QStringList>, 4> items{ +        {{tr("General"), {tr("General"), tr("Web"), tr("Debug"), tr("Game List")}}, +         {tr("System"), {tr("System"), tr("Audio")}}, +         {tr("Graphics"), {tr("Graphics")}}, +         {tr("Controls"), {tr("Input")}}}}; + +    for (const auto& entry : items) { +        auto* const item = new QListWidgetItem(entry.first); +        item->setData(Qt::UserRole, entry.second); + +        ui->selectorList->addItem(item); +    } +} + +void ConfigureDialog::UpdateVisibleTabs() { +    const auto items = ui->selectorList->selectedItems(); +    if (items.isEmpty()) +        return; + +    const std::map<QString, QWidget*> widgets = { +        {tr("General"), ui->generalTab}, {tr("System"), ui->systemTab}, +        {tr("Input"), ui->inputTab},     {tr("Graphics"), ui->graphicsTab}, +        {tr("Audio"), ui->audioTab},     {tr("Debug"), ui->debugTab}, +        {tr("Web"), ui->webTab},         {tr("Game List"), ui->gameListTab}}; + +    ui->tabWidget->clear(); + +    const QStringList tabs = items[0]->data(Qt::UserRole).toStringList(); + +    for (const auto& tab : tabs) +        ui->tabWidget->addTab(widgets.find(tab)->second, tab); +} diff --git a/src/yuzu/configuration/configure_dialog.h b/src/yuzu/configuration/configure_dialog.h index bbbdacc29..243d9fa09 100644 --- a/src/yuzu/configuration/configure_dialog.h +++ b/src/yuzu/configuration/configure_dialog.h @@ -18,13 +18,14 @@ class ConfigureDialog : public QDialog {  public:      explicit ConfigureDialog(QWidget* parent, const HotkeyRegistry& registry); -    ~ConfigureDialog(); +    ~ConfigureDialog() override;      void applyConfiguration();  private:      void setConfiguration(); +    void UpdateVisibleTabs(); +    void PopulateSelectionList(); -private:      std::unique_ptr<Ui::ConfigureDialog> ui;  }; diff --git a/src/yuzu/configuration/configure_gamelist.cpp b/src/yuzu/configuration/configure_gamelist.cpp index 8743ce982..ae8cac243 100644 --- a/src/yuzu/configuration/configure_gamelist.cpp +++ b/src/yuzu/configuration/configure_gamelist.cpp @@ -36,20 +36,36 @@ ConfigureGameList::ConfigureGameList(QWidget* parent)      InitializeRowComboBoxes();      this->setConfiguration(); + +    // Force game list reload if any of the relevant settings are changed. +    connect(ui->show_unknown, &QCheckBox::stateChanged, this, +            &ConfigureGameList::RequestGameListUpdate); +    connect(ui->icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, +            &ConfigureGameList::RequestGameListUpdate); +    connect(ui->row_1_text_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, +            &ConfigureGameList::RequestGameListUpdate); +    connect(ui->row_2_text_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, +            &ConfigureGameList::RequestGameListUpdate);  }  ConfigureGameList::~ConfigureGameList() = default;  void ConfigureGameList::applyConfiguration() {      UISettings::values.show_unknown = ui->show_unknown->isChecked(); +    UISettings::values.show_add_ons = ui->show_add_ons->isChecked();      UISettings::values.icon_size = ui->icon_size_combobox->currentData().toUInt();      UISettings::values.row_1_text_id = ui->row_1_text_combobox->currentData().toUInt();      UISettings::values.row_2_text_id = ui->row_2_text_combobox->currentData().toUInt();      Settings::Apply();  } +void ConfigureGameList::RequestGameListUpdate() { +    UISettings::values.is_game_list_reload_pending.exchange(true); +} +  void ConfigureGameList::setConfiguration() {      ui->show_unknown->setChecked(UISettings::values.show_unknown); +    ui->show_add_ons->setChecked(UISettings::values.show_add_ons);      ui->icon_size_combobox->setCurrentIndex(          ui->icon_size_combobox->findData(UISettings::values.icon_size));      ui->row_1_text_combobox->setCurrentIndex( diff --git a/src/yuzu/configuration/configure_gamelist.h b/src/yuzu/configuration/configure_gamelist.h index ff7406c60..bf3f1cdfa 100644 --- a/src/yuzu/configuration/configure_gamelist.h +++ b/src/yuzu/configuration/configure_gamelist.h @@ -16,11 +16,13 @@ class ConfigureGameList : public QWidget {  public:      explicit ConfigureGameList(QWidget* parent = nullptr); -    ~ConfigureGameList(); +    ~ConfigureGameList() override;      void applyConfiguration();  private: +    void RequestGameListUpdate(); +      void setConfiguration();      void changeEvent(QEvent*) override; diff --git a/src/yuzu/configuration/configure_gamelist.ui b/src/yuzu/configuration/configure_gamelist.ui index 7471fdb60..7a69377e7 100644 --- a/src/yuzu/configuration/configure_gamelist.ui +++ b/src/yuzu/configuration/configure_gamelist.ui @@ -1,126 +1,133 @@  <?xml version="1.0" encoding="UTF-8"?>  <ui version="4.0">   <class>ConfigureGameList</class> -  <widget class="QWidget" name="ConfigureGeneral"> -    <property name="geometry"> -      <rect> -        <x>0</x> -        <y>0</y> -        <width>300</width> -        <height>377</height> -      </rect> -    </property> -    <property name="windowTitle"> -      <string>Form</string> -    </property> -    <layout class="QHBoxLayout" name="HorizontalLayout"> -      <item> -        <layout class="QVBoxLayout" name="VerticalLayout"> + <widget class="QWidget" name="ConfigureGameList"> +  <property name="geometry"> +   <rect> +    <x>0</x> +    <y>0</y> +    <width>300</width> +    <height>377</height> +   </rect> +  </property> +  <property name="windowTitle"> +   <string>Form</string> +  </property> +  <layout class="QHBoxLayout" name="HorizontalLayout"> +   <item> +    <layout class="QVBoxLayout" name="VerticalLayout"> +     <item> +      <widget class="QGroupBox" name="GeneralGroupBox"> +       <property name="title"> +        <string>General</string> +       </property> +       <layout class="QHBoxLayout" name="GeneralHorizontalLayout"> +        <item> +         <layout class="QVBoxLayout" name="GeneralVerticalLayout">            <item> -            <widget class="QGroupBox" name="GeneralGroupBox"> -              <property name="title"> -                <string>General</string> -              </property> -              <layout class="QHBoxLayout" name="GeneralHorizontalLayout"> -                <item> -                  <layout class="QVBoxLayout" name="GeneralVerticalLayout"> -                    <item> -                      <widget class="QCheckBox" name="show_unknown"> -                        <property name="text"> -                          <string>Show files with type 'Unknown'</string> -                        </property> -                      </widget> -                    </item> -                  </layout> -                </item> -              </layout> -            </widget> +           <widget class="QCheckBox" name="show_unknown"> +            <property name="text"> +             <string>Show files with type 'Unknown'</string> +            </property> +           </widget>            </item>            <item> -            <widget class="QGroupBox" name="IconSizeGroupBox"> -              <property name="title"> -                <string>Icon Size</string> -              </property> -              <layout class="QHBoxLayout" name="icon_size_qhbox_layout"> -                <item> -                  <layout class="QVBoxLayout" name="icon_size_qvbox_layout"> -                    <item> -                      <layout class="QHBoxLayout" name="icon_size_qhbox_layout_2"> -                        <item> -                          <widget class="QLabel" name="icon_size_label"> -                            <property name="text"> -                              <string>Icon Size:</string> -                            </property> -                          </widget> -                        </item> -                        <item> -                          <widget class="QComboBox" name="icon_size_combobox"/> -                        </item> -                      </layout> -                    </item> -                  </layout> -                </item> -              </layout> -            </widget> +           <widget class="QCheckBox" name="show_add_ons"> +            <property name="text"> +             <string>Show Add-Ons Column</string> +            </property> +           </widget>            </item> +         </layout> +        </item> +       </layout> +      </widget> +     </item> +     <item> +      <widget class="QGroupBox" name="IconSizeGroupBox"> +       <property name="title"> +        <string>Icon Size</string> +       </property> +       <layout class="QHBoxLayout" name="icon_size_qhbox_layout"> +        <item> +         <layout class="QVBoxLayout" name="icon_size_qvbox_layout">            <item> -            <widget class="QGroupBox" name="RowGroupBox"> -              <property name="title"> -                <string>Row Text</string> +           <layout class="QHBoxLayout" name="icon_size_qhbox_layout_2"> +            <item> +             <widget class="QLabel" name="icon_size_label"> +              <property name="text"> +               <string>Icon Size:</string>                </property> -              <layout class="QHBoxLayout" name="RowHorizontalLayout"> -                <item> -                  <layout class="QVBoxLayout" name="RowVerticalLayout"> -                    <item> -                      <layout class="QHBoxLayout" name="row_1_qhbox_layout"> -                        <item> -                          <widget class="QLabel" name="row_1_label"> -                            <property name="text"> -                              <string>Row 1 Text:</string> -                            </property> -                          </widget> -                        </item> -                        <item> -                          <widget class="QComboBox" name="row_1_text_combobox"/> -                        </item> -                      </layout> -                    </item> -                    <item> -                      <layout class="QHBoxLayout" name="row_2_qhbox_layout"> -                        <item> -                          <widget class="QLabel" name="row_2_label"> -                            <property name="text"> -                              <string>Row 2 Text:</string> -                            </property> -                          </widget> -                        </item> -                        <item> -                          <widget class="QComboBox" name="row_2_text_combobox"/> -                        </item> -                      </layout> -                    </item> -                  </layout> -                </item> -              </layout> -            </widget> +             </widget> +            </item> +            <item> +             <widget class="QComboBox" name="icon_size_combobox"/> +            </item> +           </layout>            </item> +         </layout> +        </item> +       </layout> +      </widget> +     </item> +     <item> +      <widget class="QGroupBox" name="RowGroupBox"> +       <property name="title"> +        <string>Row Text</string> +       </property> +       <layout class="QHBoxLayout" name="RowHorizontalLayout"> +        <item> +         <layout class="QVBoxLayout" name="RowVerticalLayout">            <item> -            <spacer name="verticalSpacer"> -              <property name="orientation"> -                <enum>Qt::Vertical</enum> +           <layout class="QHBoxLayout" name="row_1_qhbox_layout"> +            <item> +             <widget class="QLabel" name="row_1_label"> +              <property name="text"> +               <string>Row 1 Text:</string>                </property> -              <property name="sizeHint" stdset="0"> -                <size> -                  <width>20</width> -                  <height>40</height> -                </size> +             </widget> +            </item> +            <item> +             <widget class="QComboBox" name="row_1_text_combobox"/> +            </item> +           </layout> +          </item> +          <item> +           <layout class="QHBoxLayout" name="row_2_qhbox_layout"> +            <item> +             <widget class="QLabel" name="row_2_label"> +              <property name="text"> +               <string>Row 2 Text:</string>                </property> -            </spacer> +             </widget> +            </item> +            <item> +             <widget class="QComboBox" name="row_2_text_combobox"/> +            </item> +           </layout>            </item> -        </layout> -      </item> +         </layout> +        </item> +       </layout> +      </widget> +     </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> +   </item> +  </layout> + </widget>   <resources/>   <connections/>  </ui> diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp index 537d6e576..4116b6cd7 100644 --- a/src/yuzu/configuration/configure_general.cpp +++ b/src/yuzu/configuration/configure_general.cpp @@ -19,8 +19,10 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent)      this->setConfiguration(); +    connect(ui->toggle_deepscan, &QCheckBox::stateChanged, this, +            [] { UISettings::values.is_game_list_reload_pending.exchange(true); }); +      ui->use_cpu_jit->setEnabled(!Core::System::GetInstance().IsPoweredOn()); -    ui->use_docked_mode->setEnabled(!Core::System::GetInstance().IsPoweredOn());  }  ConfigureGeneral::~ConfigureGeneral() = default; @@ -28,9 +30,9 @@ ConfigureGeneral::~ConfigureGeneral() = default;  void ConfigureGeneral::setConfiguration() {      ui->toggle_deepscan->setChecked(UISettings::values.gamedir_deepscan);      ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing); +    ui->toggle_user_on_boot->setChecked(UISettings::values.select_user_on_boot);      ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme));      ui->use_cpu_jit->setChecked(Settings::values.use_cpu_jit); -    ui->use_docked_mode->setChecked(Settings::values.use_docked_mode);      ui->enable_nfc->setChecked(Settings::values.enable_nfc);  } @@ -41,10 +43,10 @@ void ConfigureGeneral::PopulateHotkeyList(const HotkeyRegistry& registry) {  void ConfigureGeneral::applyConfiguration() {      UISettings::values.gamedir_deepscan = ui->toggle_deepscan->isChecked();      UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked(); +    UISettings::values.select_user_on_boot = ui->toggle_user_on_boot->isChecked();      UISettings::values.theme =          ui->theme_combobox->itemData(ui->theme_combobox->currentIndex()).toString();      Settings::values.use_cpu_jit = ui->use_cpu_jit->isChecked(); -    Settings::values.use_docked_mode = ui->use_docked_mode->isChecked();      Settings::values.enable_nfc = ui->enable_nfc->isChecked();  } diff --git a/src/yuzu/configuration/configure_general.h b/src/yuzu/configuration/configure_general.h index 4770034cc..59738af40 100644 --- a/src/yuzu/configuration/configure_general.h +++ b/src/yuzu/configuration/configure_general.h @@ -18,7 +18,7 @@ class ConfigureGeneral : public QWidget {  public:      explicit ConfigureGeneral(QWidget* parent = nullptr); -    ~ConfigureGeneral(); +    ~ConfigureGeneral() override;      void PopulateHotkeyList(const HotkeyRegistry& registry);      void applyConfiguration(); diff --git a/src/yuzu/configuration/configure_general.ui b/src/yuzu/configuration/configure_general.ui index b82fffde8..dff0ad5d0 100644 --- a/src/yuzu/configuration/configure_general.ui +++ b/src/yuzu/configuration/configure_general.ui @@ -7,7 +7,7 @@      <x>0</x>      <y>0</y>      <width>300</width> -    <height>377</height> +    <height>407</height>     </rect>    </property>    <property name="windowTitle"> @@ -38,6 +38,13 @@              </property>             </widget>            </item> +          <item> +           <widget class="QCheckBox" name="toggle_user_on_boot"> +            <property name="text"> +             <string>Prompt for user on game boot</string> +            </property> +           </widget> +          </item>           </layout>          </item>         </layout> @@ -72,13 +79,6 @@          <item>           <layout class="QVBoxLayout" name="EmulationVerticalLayout">            <item> -           <widget class="QCheckBox" name="use_docked_mode"> -            <property name="text"> -             <string>Enable docked mode</string> -            </property> -           </widget> -          </item> -          <item>             <widget class="QCheckBox" name="enable_nfc">              <property name="text">               <string>Enable NFC</string> diff --git a/src/yuzu/configuration/configure_graphics.h b/src/yuzu/configuration/configure_graphics.h index 9bda26fd6..d6ffc6fde 100644 --- a/src/yuzu/configuration/configure_graphics.h +++ b/src/yuzu/configuration/configure_graphics.h @@ -16,14 +16,13 @@ class ConfigureGraphics : public QWidget {  public:      explicit ConfigureGraphics(QWidget* parent = nullptr); -    ~ConfigureGraphics(); +    ~ConfigureGraphics() override;      void applyConfiguration();  private:      void setConfiguration(); -private:      std::unique_ptr<Ui::ConfigureGraphics> ui;      QColor bg_color;  }; diff --git a/src/yuzu/configuration/configure_graphics.ui b/src/yuzu/configuration/configure_graphics.ui index 91fcad994..e278cdd05 100644 --- a/src/yuzu/configuration/configure_graphics.ui +++ b/src/yuzu/configuration/configure_graphics.ui @@ -23,31 +23,31 @@         </property>         <layout class="QVBoxLayout" name="verticalLayout_2">          <item> -          <layout class="QHBoxLayout" name="horizontalLayout_2"> -            <item> -              <widget class="QCheckBox" name="toggle_frame_limit"> -                <property name="text"> -                  <string>Limit Speed Percent</string> -                </property> -              </widget> -            </item> -            <item> -              <widget class="QSpinBox" name="frame_limit"> -                <property name="suffix"> -                  <string>%</string> -                </property> -                <property name="minimum"> -                  <number>1</number> -                </property> -                <property name="maximum"> -                  <number>9999</number> -                </property> -                <property name="value"> -                  <number>100</number> -                </property> -              </widget> -            </item> -          </layout> +         <layout class="QHBoxLayout" name="horizontalLayout_2"> +          <item> +           <widget class="QCheckBox" name="toggle_frame_limit"> +            <property name="text"> +             <string>Limit Speed Percent</string> +            </property> +           </widget> +          </item> +          <item> +           <widget class="QSpinBox" name="frame_limit"> +            <property name="suffix"> +             <string>%</string> +            </property> +            <property name="minimum"> +             <number>1</number> +            </property> +            <property name="maximum"> +             <number>9999</number> +            </property> +            <property name="value"> +             <number>100</number> +            </property> +           </widget> +          </item> +         </layout>          </item>          <item>           <widget class="QCheckBox" name="use_accurate_gpu_emulation"> @@ -61,7 +61,7 @@            <item>             <widget class="QLabel" name="label">              <property name="text"> -             <string>Internal Resolution:(Currently does nothing.)</string> +             <string>Internal Resolution</string>              </property>             </widget>            </item> @@ -96,27 +96,27 @@            </item>           </layout>          </item> -         <item> -           <layout class="QHBoxLayout" name="horizontalLayout_6"> -             <item> -               <widget class="QLabel" name="bg_label"> -                 <property name="text"> -                   <string>Background Color:</string> -                 </property> -               </widget> -             </item> -             <item> -               <widget class="QPushButton" name="bg_button"> -                 <property name="maximumSize"> -                   <size> -                     <width>40</width> -                     <height>16777215</height> -                   </size> -                 </property> -               </widget> -             </item> -           </layout> -         </item> +        <item> +         <layout class="QHBoxLayout" name="horizontalLayout_6"> +          <item> +           <widget class="QLabel" name="bg_label"> +            <property name="text"> +             <string>Background Color:</string> +            </property> +           </widget> +          </item> +          <item> +           <widget class="QPushButton" name="bg_button"> +            <property name="maximumSize"> +             <size> +              <width>40</width> +              <height>16777215</height> +             </size> +            </property> +           </widget> +          </item> +         </layout> +        </item>         </layout>        </widget>       </item> diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp index 94789c064..f39d57998 100644 --- a/src/yuzu/configuration/configure_input.cpp +++ b/src/yuzu/configuration/configure_input.cpp @@ -4,339 +4,205 @@  #include <algorithm>  #include <memory> -#include <utility> -#include <QMenu> -#include <QMessageBox> +  #include <QTimer> -#include "common/param_package.h" -#include "input_common/main.h" -#include "yuzu/configuration/config.h" + +#include "configuration/configure_touchscreen_advanced.h" +#include "core/core.h" +#include "core/hle/service/am/am.h" +#include "core/hle/service/am/applet_ae.h" +#include "core/hle/service/am/applet_oe.h" +#include "core/hle/service/hid/controllers/npad.h" +#include "core/hle/service/sm/sm.h" +#include "ui_configure_input.h" +#include "ui_configure_input_player.h"  #include "yuzu/configuration/configure_input.h" +#include "yuzu/configuration/configure_input_player.h" +#include "yuzu/configuration/configure_mouse_advanced.h" -const std::array<std::string, ConfigureInput::ANALOG_SUB_BUTTONS_NUM> -    ConfigureInput::analog_sub_buttons{{ -        "up", -        "down", -        "left", -        "right", -        "modifier", -    }}; - -static QString getKeyName(int key_code) { -    switch (key_code) { -    case Qt::Key_Shift: -        return QObject::tr("Shift"); -    case Qt::Key_Control: -        return QObject::tr("Ctrl"); -    case Qt::Key_Alt: -        return QObject::tr("Alt"); -    case Qt::Key_Meta: -        return ""; -    default: -        return QKeySequence(key_code).toString(); +void OnDockedModeChanged(bool last_state, bool new_state) { +    if (last_state == new_state) { +        return;      } -} -static void SetAnalogButton(const Common::ParamPackage& input_param, -                            Common::ParamPackage& analog_param, const std::string& button_name) { -    if (analog_param.Get("engine", "") != "analog_from_button") { -        analog_param = { -            {"engine", "analog_from_button"}, -            {"modifier_scale", "0.5"}, -        }; +    Core::System& system{Core::System::GetInstance()}; +    if (!system.IsPoweredOn()) { +        return;      } -    analog_param.Set(button_name, input_param.Serialize()); -} +    Service::SM::ServiceManager& sm = system.ServiceManager(); -static QString ButtonToText(const Common::ParamPackage& param) { -    if (!param.Has("engine")) { -        return QObject::tr("[not set]"); -    } else if (param.Get("engine", "") == "keyboard") { -        return getKeyName(param.Get("code", 0)); -    } else if (param.Get("engine", "") == "sdl") { -        if (param.Has("hat")) { -            return QString(QObject::tr("Hat %1 %2")) -                .arg(param.Get("hat", "").c_str(), param.Get("direction", "").c_str()); -        } -        if (param.Has("axis")) { -            return QString(QObject::tr("Axis %1%2")) -                .arg(param.Get("axis", "").c_str(), param.Get("direction", "").c_str()); -        } -        if (param.Has("button")) { -            return QString(QObject::tr("Button %1")).arg(param.Get("button", "").c_str()); -        } -        return QString(); -    } else { -        return QObject::tr("[unknown]"); -    } -}; - -static QString AnalogToText(const Common::ParamPackage& param, const std::string& dir) { -    if (!param.Has("engine")) { -        return QObject::tr("[not set]"); -    } else if (param.Get("engine", "") == "analog_from_button") { -        return ButtonToText(Common::ParamPackage{param.Get(dir, "")}); -    } else if (param.Get("engine", "") == "sdl") { -        if (dir == "modifier") { -            return QString(QObject::tr("[unused]")); -        } +    // 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 (dir == "left" || dir == "right") { -            return QString(QObject::tr("Axis %1")).arg(param.Get("axis_x", "").c_str()); -        } else if (dir == "up" || dir == "down") { -            return QString(QObject::tr("Axis %1")).arg(param.Get("axis_y", "").c_str()); -        } -        return QString(); -    } else { -        return QObject::tr("[unknown]"); +    if (applet_oe != nullptr) { +        applet_oe->GetMessageQueue()->OperationModeChanged(); +        has_signalled = true;      } -}; -ConfigureInput::ConfigureInput(QWidget* parent) -    : QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()), -      timeout_timer(std::make_unique<QTimer>()), poll_timer(std::make_unique<QTimer>()) { +    if (applet_ae != nullptr && !has_signalled) { +        applet_ae->GetMessageQueue()->OperationModeChanged(); +    } +} -    ui->setupUi(this); -    setFocusPolicy(Qt::ClickFocus); - -    button_map = { -        ui->buttonA,          ui->buttonB,        ui->buttonX,           ui->buttonY, -        ui->buttonLStick,     ui->buttonRStick,   ui->buttonL,           ui->buttonR, -        ui->buttonZL,         ui->buttonZR,       ui->buttonPlus,        ui->buttonMinus, -        ui->buttonDpadLeft,   ui->buttonDpadUp,   ui->buttonDpadRight,   ui->buttonDpadDown, -        ui->buttonLStickLeft, ui->buttonLStickUp, ui->buttonLStickRight, ui->buttonLStickDown, -        ui->buttonRStickLeft, ui->buttonRStickUp, ui->buttonRStickRight, ui->buttonRStickDown, -        ui->buttonSL,         ui->buttonSR,       ui->buttonHome,        ui->buttonScreenshot, -    }; +namespace { +template <typename Dialog, typename... Args> +void CallConfigureDialog(ConfigureInput& parent, Args&&... args) { +    parent.applyConfiguration(); +    Dialog dialog(&parent, std::forward<Args>(args)...); -    analog_map_buttons = {{ -        { -            ui->buttonLStickUp, -            ui->buttonLStickDown, -            ui->buttonLStickLeft, -            ui->buttonLStickRight, -            ui->buttonLStickMod, -        }, -        { -            ui->buttonRStickUp, -            ui->buttonRStickDown, -            ui->buttonRStickLeft, -            ui->buttonRStickRight, -            ui->buttonRStickMod, -        }, -    }}; - -    analog_map_stick = {ui->buttonLStickAnalog, ui->buttonRStickAnalog}; - -    for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) { -        if (!button_map[button_id]) -            continue; -        button_map[button_id]->setContextMenuPolicy(Qt::CustomContextMenu); -        connect(button_map[button_id], &QPushButton::released, [=]() { -            handleClick( -                button_map[button_id], -                [=](const Common::ParamPackage& params) { buttons_param[button_id] = params; }, -                InputCommon::Polling::DeviceType::Button); -        }); -        connect(button_map[button_id], &QPushButton::customContextMenuRequested, -                [=](const QPoint& menu_location) { -                    QMenu context_menu; -                    context_menu.addAction(tr("Clear"), [&] { -                        buttons_param[button_id].Clear(); -                        button_map[button_id]->setText(tr("[not set]")); -                    }); -                    context_menu.addAction(tr("Restore Default"), [&] { -                        buttons_param[button_id] = Common::ParamPackage{ -                            InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])}; -                        button_map[button_id]->setText(ButtonToText(buttons_param[button_id])); -                    }); -                    context_menu.exec(button_map[button_id]->mapToGlobal(menu_location)); -                }); +    const auto res = dialog.exec(); +    if (res == QDialog::Accepted) { +        dialog.applyConfiguration();      } +} +} // Anonymous namespace -    for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) { -        for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) { -            if (!analog_map_buttons[analog_id][sub_button_id]) -                continue; -            analog_map_buttons[analog_id][sub_button_id]->setContextMenuPolicy( -                Qt::CustomContextMenu); -            connect(analog_map_buttons[analog_id][sub_button_id], &QPushButton::released, [=]() { -                handleClick(analog_map_buttons[analog_id][sub_button_id], -                            [=](const Common::ParamPackage& params) { -                                SetAnalogButton(params, analogs_param[analog_id], -                                                analog_sub_buttons[sub_button_id]); -                            }, -                            InputCommon::Polling::DeviceType::Button); -            }); -            connect(analog_map_buttons[analog_id][sub_button_id], -                    &QPushButton::customContextMenuRequested, [=](const QPoint& menu_location) { -                        QMenu context_menu; -                        context_menu.addAction(tr("Clear"), [&] { -                            analogs_param[analog_id].Erase(analog_sub_buttons[sub_button_id]); -                            analog_map_buttons[analog_id][sub_button_id]->setText(tr("[not set]")); -                        }); -                        context_menu.addAction(tr("Restore Default"), [&] { -                            Common::ParamPackage params{InputCommon::GenerateKeyboardParam( -                                Config::default_analogs[analog_id][sub_button_id])}; -                            SetAnalogButton(params, analogs_param[analog_id], -                                            analog_sub_buttons[sub_button_id]); -                            analog_map_buttons[analog_id][sub_button_id]->setText(AnalogToText( -                                analogs_param[analog_id], analog_sub_buttons[sub_button_id])); -                        }); -                        context_menu.exec(analog_map_buttons[analog_id][sub_button_id]->mapToGlobal( -                            menu_location)); -                    }); -        } -        connect(analog_map_stick[analog_id], &QPushButton::released, [=]() { -            QMessageBox::information(this, tr("Information"), -                                     tr("After pressing OK, first move your joystick horizontally, " -                                        "and then vertically.")); -            handleClick( -                analog_map_stick[analog_id], -                [=](const Common::ParamPackage& params) { analogs_param[analog_id] = params; }, -                InputCommon::Polling::DeviceType::Analog); -        }); -    } +ConfigureInput::ConfigureInput(QWidget* parent) +    : QDialog(parent), ui(std::make_unique<Ui::ConfigureInput>()) { +    ui->setupUi(this); -    connect(ui->buttonClearAll, &QPushButton::released, [this] { ClearAll(); }); -    connect(ui->buttonRestoreDefaults, &QPushButton::released, [this]() { restoreDefaults(); }); +    players_controller = { +        ui->player1_combobox, ui->player2_combobox, ui->player3_combobox, ui->player4_combobox, +        ui->player5_combobox, ui->player6_combobox, ui->player7_combobox, ui->player8_combobox, +    }; -    timeout_timer->setSingleShot(true); -    connect(timeout_timer.get(), &QTimer::timeout, [this]() { setPollingResult({}, true); }); +    players_configure = { +        ui->player1_configure, ui->player2_configure, ui->player3_configure, ui->player4_configure, +        ui->player5_configure, ui->player6_configure, ui->player7_configure, ui->player8_configure, +    }; -    connect(poll_timer.get(), &QTimer::timeout, [this]() { -        Common::ParamPackage params; -        for (auto& poller : device_pollers) { -            params = poller->GetNextInput(); -            if (params.Has("engine")) { -                setPollingResult(params, false); -                return; -            } -        } -    }); +    for (auto* controller_box : players_controller) { +        controller_box->addItems({"None", "Pro Controller", "Dual Joycons", "Single Right Joycon", +                                  "Single Left Joycon"}); +    }      this->loadConfiguration(); +    updateUIEnabled(); -    // TODO(wwylele): enable this when we actually emulate it -    ui->buttonHome->setEnabled(false); -} +    connect(ui->restore_defaults_button, &QPushButton::pressed, this, +            &ConfigureInput::restoreDefaults); -void ConfigureInput::applyConfiguration() { -    std::transform(buttons_param.begin(), buttons_param.end(), Settings::values.buttons.begin(), -                   [](const Common::ParamPackage& param) { return param.Serialize(); }); -    std::transform(analogs_param.begin(), analogs_param.end(), Settings::values.analogs.begin(), -                   [](const Common::ParamPackage& param) { return param.Serialize(); }); -} - -void ConfigureInput::loadConfiguration() { -    std::transform(Settings::values.buttons.begin(), Settings::values.buttons.end(), -                   buttons_param.begin(), -                   [](const std::string& str) { return Common::ParamPackage(str); }); -    std::transform(Settings::values.analogs.begin(), Settings::values.analogs.end(), -                   analogs_param.begin(), -                   [](const std::string& str) { return Common::ParamPackage(str); }); -    updateButtonLabels(); -} +    for (auto* enabled : players_controller) +        connect(enabled, QOverload<int>::of(&QComboBox::currentIndexChanged), this, +                &ConfigureInput::updateUIEnabled); +    connect(ui->use_docked_mode, &QCheckBox::stateChanged, this, &ConfigureInput::updateUIEnabled); +    connect(ui->handheld_connected, &QCheckBox::stateChanged, this, +            &ConfigureInput::updateUIEnabled); +    connect(ui->mouse_enabled, &QCheckBox::stateChanged, this, &ConfigureInput::updateUIEnabled); +    connect(ui->keyboard_enabled, &QCheckBox::stateChanged, this, &ConfigureInput::updateUIEnabled); +    connect(ui->debug_enabled, &QCheckBox::stateChanged, this, &ConfigureInput::updateUIEnabled); +    connect(ui->touchscreen_enabled, &QCheckBox::stateChanged, this, +            &ConfigureInput::updateUIEnabled); -void ConfigureInput::restoreDefaults() { -    for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) { -        buttons_param[button_id] = Common::ParamPackage{ -            InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])}; +    for (std::size_t i = 0; i < players_configure.size(); ++i) { +        connect(players_configure[i], &QPushButton::pressed, this, +                [this, i] { CallConfigureDialog<ConfigureInputPlayer>(*this, i, false); });      } -    for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) { -        for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) { -            Common::ParamPackage params{InputCommon::GenerateKeyboardParam( -                Config::default_analogs[analog_id][sub_button_id])}; -            SetAnalogButton(params, analogs_param[analog_id], analog_sub_buttons[sub_button_id]); -        } -    } -    updateButtonLabels(); -} +    connect(ui->handheld_configure, &QPushButton::pressed, this, +            [this] { CallConfigureDialog<ConfigureInputPlayer>(*this, 8, false); }); -void ConfigureInput::ClearAll() { -    for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) { -        if (button_map[button_id] && button_map[button_id]->isEnabled()) -            buttons_param[button_id].Clear(); -    } -    for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) { -        for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) { -            if (analog_map_buttons[analog_id][sub_button_id] && -                analog_map_buttons[analog_id][sub_button_id]->isEnabled()) -                analogs_param[analog_id].Erase(analog_sub_buttons[sub_button_id]); -        } -    } -    updateButtonLabels(); -} +    connect(ui->debug_configure, &QPushButton::pressed, this, +            [this] { CallConfigureDialog<ConfigureInputPlayer>(*this, 9, true); }); -void ConfigureInput::updateButtonLabels() { -    for (int button = 0; button < Settings::NativeButton::NumButtons; button++) { -        button_map[button]->setText(ButtonToText(buttons_param[button])); -    } +    connect(ui->mouse_advanced, &QPushButton::pressed, this, +            [this] { CallConfigureDialog<ConfigureMouseAdvanced>(*this); }); -    for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) { -        for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) { -            if (analog_map_buttons[analog_id][sub_button_id]) { -                analog_map_buttons[analog_id][sub_button_id]->setText( -                    AnalogToText(analogs_param[analog_id], analog_sub_buttons[sub_button_id])); -            } -        } -        analog_map_stick[analog_id]->setText(tr("Set Analog Stick")); -    } +    connect(ui->touchscreen_advanced, &QPushButton::pressed, this, +            [this] { CallConfigureDialog<ConfigureTouchscreenAdvanced>(*this); });  } -void ConfigureInput::handleClick(QPushButton* button, -                                 std::function<void(const Common::ParamPackage&)> new_input_setter, -                                 InputCommon::Polling::DeviceType type) { -    button->setText(tr("[press key]")); -    button->setFocus(); - -    input_setter = new_input_setter; +ConfigureInput::~ConfigureInput() = default; -    device_pollers = InputCommon::Polling::GetPollers(type); +void ConfigureInput::applyConfiguration() { +    for (std::size_t i = 0; i < players_controller.size(); ++i) { +        const auto controller_type_index = players_controller[i]->currentIndex(); -    // Keyboard keys can only be used as button devices -    want_keyboard_keys = type == InputCommon::Polling::DeviceType::Button; +        Settings::values.players[i].connected = controller_type_index != 0; -    for (auto& poller : device_pollers) { -        poller->Start(); +        if (controller_type_index > 0) { +            Settings::values.players[i].type = +                static_cast<Settings::ControllerType>(controller_type_index - 1); +        } else { +            Settings::values.players[i].type = Settings::ControllerType::DualJoycon; +        }      } -    grabKeyboard(); -    grabMouse(); -    timeout_timer->start(5000); // Cancel after 5 seconds -    poll_timer->start(200);     // Check for new inputs every 200ms +    const bool pre_docked_mode = Settings::values.use_docked_mode; +    Settings::values.use_docked_mode = ui->use_docked_mode->isChecked(); +    OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode); +    Settings::values +        .players[Service::HID::Controller_NPad::NPadIdToIndex(Service::HID::NPAD_HANDHELD)] +        .connected = ui->handheld_connected->isChecked(); +    Settings::values.debug_pad_enabled = ui->debug_enabled->isChecked(); +    Settings::values.mouse_enabled = ui->mouse_enabled->isChecked(); +    Settings::values.keyboard_enabled = ui->keyboard_enabled->isChecked(); +    Settings::values.touchscreen.enabled = ui->touchscreen_enabled->isChecked();  } -void ConfigureInput::setPollingResult(const Common::ParamPackage& params, bool abort) { -    releaseKeyboard(); -    releaseMouse(); -    timeout_timer->stop(); -    poll_timer->stop(); -    for (auto& poller : device_pollers) { -        poller->Stop(); +void ConfigureInput::updateUIEnabled() { +    bool hit_disabled = false; +    for (auto* player : players_controller) { +        player->setDisabled(hit_disabled); +        if (hit_disabled) +            player->setCurrentIndex(0); +        if (!hit_disabled && player->currentIndex() == 0) +            hit_disabled = true;      } -    if (!abort) { -        (*input_setter)(params); +    for (std::size_t i = 0; i < players_controller.size(); ++i) { +        players_configure[i]->setEnabled(players_controller[i]->currentIndex() != 0);      } -    updateButtonLabels(); -    input_setter = boost::none; +    ui->handheld_connected->setEnabled(!ui->use_docked_mode->isChecked()); +    ui->handheld_configure->setEnabled(ui->handheld_connected->isChecked() && +                                       !ui->use_docked_mode->isChecked()); +    ui->mouse_advanced->setEnabled(ui->mouse_enabled->isChecked()); +    ui->debug_configure->setEnabled(ui->debug_enabled->isChecked()); +    ui->touchscreen_advanced->setEnabled(ui->touchscreen_enabled->isChecked());  } -void ConfigureInput::keyPressEvent(QKeyEvent* event) { -    if (!input_setter || !event) -        return; +void ConfigureInput::loadConfiguration() { +    std::stable_partition( +        Settings::values.players.begin(), +        Settings::values.players.begin() + +            Service::HID::Controller_NPad::NPadIdToIndex(Service::HID::NPAD_HANDHELD), +        [](const auto& player) { return player.connected; }); + +    for (std::size_t i = 0; i < players_controller.size(); ++i) { +        const auto connected = Settings::values.players[i].connected; +        players_controller[i]->setCurrentIndex( +            connected ? static_cast<u8>(Settings::values.players[i].type) + 1 : 0); +    } + +    ui->use_docked_mode->setChecked(Settings::values.use_docked_mode); +    ui->handheld_connected->setChecked( +        Settings::values +            .players[Service::HID::Controller_NPad::NPadIdToIndex(Service::HID::NPAD_HANDHELD)] +            .connected); +    ui->debug_enabled->setChecked(Settings::values.debug_pad_enabled); +    ui->mouse_enabled->setChecked(Settings::values.mouse_enabled); +    ui->keyboard_enabled->setChecked(Settings::values.keyboard_enabled); +    ui->touchscreen_enabled->setChecked(Settings::values.touchscreen.enabled); + +    updateUIEnabled(); +} -    if (event->key() != Qt::Key_Escape) { -        if (want_keyboard_keys) { -            setPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())}, -                             false); -        } else { -            // Escape key wasn't pressed and we don't want any keyboard keys, so don't stop polling -            return; -        } +void ConfigureInput::restoreDefaults() { +    players_controller[0]->setCurrentIndex(2); + +    for (std::size_t i = 1; i < players_controller.size(); ++i) { +        players_controller[i]->setCurrentIndex(0);      } -    setPollingResult({}, true); + +    ui->use_docked_mode->setCheckState(Qt::Unchecked); +    ui->handheld_connected->setCheckState(Qt::Unchecked); +    ui->mouse_enabled->setCheckState(Qt::Unchecked); +    ui->keyboard_enabled->setCheckState(Qt::Unchecked); +    ui->debug_enabled->setCheckState(Qt::Unchecked); +    ui->touchscreen_enabled->setCheckState(Qt::Checked); +    updateUIEnabled();  } diff --git a/src/yuzu/configuration/configure_input.h b/src/yuzu/configuration/configure_input.h index d1198db81..b8e62cc2b 100644 --- a/src/yuzu/configuration/configure_input.h +++ b/src/yuzu/configuration/configure_input.h @@ -5,16 +5,11 @@  #pragma once  #include <array> -#include <functional>  #include <memory> -#include <string> -#include <unordered_map> + +#include <QDialog>  #include <QKeyEvent> -#include <QWidget> -#include <boost/optional.hpp> -#include "common/param_package.h" -#include "core/settings.h" -#include "input_common/main.h" +  #include "ui_configure_input.h"  class QPushButton; @@ -25,67 +20,28 @@ namespace Ui {  class ConfigureInput;  } -class ConfigureInput : public QWidget { +void OnDockedModeChanged(bool last_state, bool new_state); + +class ConfigureInput : public QDialog {      Q_OBJECT  public:      explicit ConfigureInput(QWidget* parent = nullptr); +    ~ConfigureInput() override;      /// Save all button configurations to settings file      void applyConfiguration();  private: -    std::unique_ptr<Ui::ConfigureInput> ui; - -    std::unique_ptr<QTimer> timeout_timer; -    std::unique_ptr<QTimer> poll_timer; - -    /// This will be the the setting function when an input is awaiting configuration. -    boost::optional<std::function<void(const Common::ParamPackage&)>> input_setter; - -    std::array<Common::ParamPackage, Settings::NativeButton::NumButtons> buttons_param; -    std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs> analogs_param; - -    static constexpr int ANALOG_SUB_BUTTONS_NUM = 5; - -    /// Each button input is represented by a QPushButton. -    std::array<QPushButton*, Settings::NativeButton::NumButtons> button_map; - -    /// A group of five QPushButtons represent one analog input. The buttons each represent up, -    /// down, left, right, and modifier, respectively. -    std::array<std::array<QPushButton*, ANALOG_SUB_BUTTONS_NUM>, Settings::NativeAnalog::NumAnalogs> -        analog_map_buttons; - -    /// Analog inputs are also represented each with a single button, used to configure with an -    /// actual analog stick -    std::array<QPushButton*, Settings::NativeAnalog::NumAnalogs> analog_map_stick; - -    static const std::array<std::string, ANALOG_SUB_BUTTONS_NUM> analog_sub_buttons; - -    std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> device_pollers; - -    /// A flag to indicate if keyboard keys are okay when configuring an input. If this is false, -    /// keyboard events are ignored. -    bool want_keyboard_keys = false; +    void updateUIEnabled();      /// Load configuration settings.      void loadConfiguration();      /// Restore all buttons to their default values.      void restoreDefaults(); -    /// Clear all input configuration -    void ClearAll(); - -    /// Update UI to reflect current configuration. -    void updateButtonLabels(); -    /// Called when the button was pressed. -    void handleClick(QPushButton* button, -                     std::function<void(const Common::ParamPackage&)> new_input_setter, -                     InputCommon::Polling::DeviceType type); - -    /// Finish polling and configure input using the input_setter -    void setPollingResult(const Common::ParamPackage& params, bool abort); +    std::unique_ptr<Ui::ConfigureInput> ui; -    /// Handle key press events. -    void keyPressEvent(QKeyEvent* event) override; +    std::array<QComboBox*, 8> players_controller; +    std::array<QPushButton*, 8> players_configure;  }; diff --git a/src/yuzu/configuration/configure_input.ui b/src/yuzu/configuration/configure_input.ui index 8a019a693..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>343</width> -    <height>677</height> +    <width>384</width> +    <height>576</height>     </rect>    </property>    <property name="windowTitle"> @@ -15,745 +15,515 @@    </property>    <layout class="QVBoxLayout" name="verticalLayout_5">     <item> -    <layout class="QGridLayout" name="buttons"> -     <item row="3" column="1"> -      <widget class="QGroupBox" name="misc"> +    <layout class="QVBoxLayout" name="verticalLayout"> +     <item> +      <widget class="QGroupBox" name="gridGroupBox">         <property name="title"> -        <string>Misc.</string> -       </property> -       <property name="flat"> -        <bool>false</bool> -       </property> -       <property name="checkable"> -        <bool>false</bool> +        <string>Players</string>         </property> -       <layout class="QGridLayout" name="gridLayout_6"> -        <item row="0" column="0"> -         <layout class="QVBoxLayout" name="buttonMiscMinusVerticalLayout"> -          <item> -           <widget class="QLabel" name="labelMinus"> -            <property name="text"> -             <string>Minus:</string> -            </property> -           </widget> -          </item> -          <item> -           <widget class="QPushButton" name="buttonMinus"> -            <property name="text"> -             <string/> -            </property> -           </widget> -          </item> -         </layout> +       <layout class="QGridLayout" name="gridLayout"> +        <item row="1" column="2"> +         <widget class="QComboBox" name="player1_combobox"> +          <property name="minimumSize"> +           <size> +            <width>110</width> +            <height>0</height> +           </size> +          </property> +         </widget>          </item> -        <item row="0" column="1"> -         <layout class="QVBoxLayout" name="buttonMiscPlusVerticalLayout"> -          <item> -           <widget class="QLabel" name="labelPlus"> -            <property name="text"> -             <string>Plus:</string> -            </property> -           </widget> -          </item> -          <item> -           <widget class="QPushButton" name="buttonPlus"> -            <property name="text"> -             <string/> -            </property> -           </widget> -          </item> -         </layout> +        <item row="1" column="3"> +         <widget class="QPushButton" name="player1_configure"> +          <property name="text"> +           <string>Configure</string> +          </property> +         </widget>          </item> -        <item row="1" column="0"> -         <layout class="QVBoxLayout" name="buttonMiscHomeVerticalLayout"> -          <item> -           <widget class="QLabel" name="labelHome"> -            <property name="text"> -             <string>Home:</string> -            </property> -           </widget> -          </item> -          <item> -           <widget class="QPushButton" name="buttonHome"> -            <property name="text"> -             <string/> -            </property> -           </widget> -          </item> -         </layout> +        <item row="0" column="2"> +         <widget class="QLabel" name="label"> +          <property name="text"> +           <string>Controller Type</string> +          </property> +          <property name="alignment"> +           <set>Qt::AlignCenter</set> +          </property> +         </widget>          </item> -        <item row="1" column="1"> -         <layout class="QVBoxLayout" name="buttonMiscScrCapVerticalLayout"> -          <item> -           <widget class="QLabel" name="labelScrCap"> -            <property name="text"> -             <string>Screen -Capture:</string> -            </property> -           </widget> -          </item> -          <item> -           <widget class="QPushButton" name="buttonScreenshot"> -            <property name="text"> -             <string/> -            </property> -           </widget> -          </item> -         </layout> +        <item row="2" column="2"> +         <widget class="QComboBox" name="player2_combobox"> +          <property name="minimumSize"> +           <size> +            <width>110</width> +            <height>0</height> +           </size> +          </property> +         </widget>          </item> -        <item row="2" column="1"> -         <spacer name="verticalSpacer"> +        <item row="3" column="2"> +         <widget class="QComboBox" name="player3_combobox"> +          <property name="minimumSize"> +           <size> +            <width>110</width> +            <height>0</height> +           </size> +          </property> +         </widget> +        </item> +        <item row="4" column="2"> +         <widget class="QComboBox" name="player4_combobox"> +          <property name="minimumSize"> +           <size> +            <width>110</width> +            <height>0</height> +           </size> +          </property> +         </widget> +        </item> +        <item row="5" column="2"> +         <widget class="QComboBox" name="player5_combobox"> +          <property name="minimumSize"> +           <size> +            <width>110</width> +            <height>0</height> +           </size> +          </property> +         </widget> +        </item> +        <item row="6" column="2"> +         <widget class="QComboBox" name="player6_combobox"> +          <property name="minimumSize"> +           <size> +            <width>110</width> +            <height>0</height> +           </size> +          </property> +         </widget> +        </item> +        <item row="7" column="2"> +         <widget class="QComboBox" name="player7_combobox"> +          <property name="minimumSize"> +           <size> +            <width>110</width> +            <height>0</height> +           </size> +          </property> +         </widget> +        </item> +        <item row="8" column="2"> +         <widget class="QComboBox" name="player8_combobox"> +          <property name="minimumSize"> +           <size> +            <width>110</width> +            <height>0</height> +           </size> +          </property> +         </widget> +        </item> +        <item row="2" column="3"> +         <widget class="QPushButton" name="player2_configure"> +          <property name="text"> +           <string>Configure</string> +          </property> +         </widget> +        </item> +        <item row="3" column="3"> +         <widget class="QPushButton" name="player3_configure"> +          <property name="text"> +           <string>Configure</string> +          </property> +         </widget> +        </item> +        <item row="4" column="3"> +         <widget class="QPushButton" name="player4_configure"> +          <property name="text"> +           <string>Configure</string> +          </property> +         </widget> +        </item> +        <item row="5" column="3"> +         <widget class="QPushButton" name="player5_configure"> +          <property name="text"> +           <string>Configure</string> +          </property> +         </widget> +        </item> +        <item row="6" column="3"> +         <widget class="QPushButton" name="player6_configure"> +          <property name="text"> +           <string>Configure</string> +          </property> +         </widget> +        </item> +        <item row="7" column="3"> +         <widget class="QPushButton" name="player7_configure"> +          <property name="text"> +           <string>Configure</string> +          </property> +         </widget> +        </item> +        <item row="8" column="3"> +         <widget class="QPushButton" name="player8_configure"> +          <property name="text"> +           <string>Configure</string> +          </property> +         </widget> +        </item> +        <item row="0" column="0"> +         <spacer name="horizontalSpacer">            <property name="orientation"> -           <enum>Qt::Vertical</enum> +           <enum>Qt::Horizontal</enum>            </property>            <property name="sizeHint" stdset="0">             <size> -            <width>20</width> -            <height>40</height> +            <width>40</width> +            <height>20</height>             </size>            </property>           </spacer>          </item> -       </layout> -      </widget> -     </item> -     <item row="0" column="0"> -      <widget class="QGroupBox" name="faceButtons"> -       <property name="title"> -        <string>Face Buttons</string> -       </property> -       <property name="flat"> -        <bool>false</bool> -       </property> -       <property name="checkable"> -        <bool>false</bool> -       </property> -       <layout class="QGridLayout" name="gridLayout"> -        <item row="0" column="0"> -         <layout class="QVBoxLayout" name="buttonFaceButtonsAVerticalLayout"> -          <item> -           <widget class="QLabel" name="labelA"> -            <property name="text"> -             <string>A:</string> -            </property> -           </widget> -          </item> -          <item> -           <widget class="QPushButton" name="buttonA"> -            <property name="text"> -             <string/> -            </property> -           </widget> -          </item> -         </layout> +        <item row="0" column="4"> +         <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="0" column="1"> -         <layout class="QVBoxLayout" name="buttonFaceButtonsBVerticalLayout"> -          <item> -           <widget class="QLabel" name="labelB"> -            <property name="text"> -             <string>B:</string> -            </property> -           </widget> -          </item> -          <item> -           <widget class="QPushButton" name="buttonB"> -            <property name="text"> -             <string/> -            </property> -           </widget> -          </item> -         </layout> +        <item row="1" column="1"> +         <widget class="QLabel" name="label_3"> +          <property name="minimumSize"> +           <size> +            <width>55</width> +            <height>0</height> +           </size> +          </property> +          <property name="text"> +           <string>Player 1</string> +          </property> +         </widget>          </item> -        <item row="1" column="0"> -         <layout class="QVBoxLayout" name="buttonFaceButtonsXVerticalLayout"> -          <item> -           <widget class="QLabel" name="labelX"> -            <property name="text"> -             <string>X:</string> -            </property> -           </widget> -          </item> -          <item> -           <widget class="QPushButton" name="buttonX"> -            <property name="text"> -             <string/> -            </property> -           </widget> -          </item> -         </layout> +        <item row="2" column="1"> +         <widget class="QLabel" name="label_4"> +          <property name="text"> +           <string>Player 2</string> +          </property> +         </widget>          </item> -        <item row="1" column="1"> -         <layout class="QVBoxLayout" name="buttonFaceButtonsYVerticalLayout"> -          <item> -           <widget class="QLabel" name="labelY"> -            <property name="text"> -             <string>Y:</string> -            </property> -           </widget> -          </item> -          <item> -           <widget class="QPushButton" name="buttonY"> -            <property name="text"> -             <string/> -            </property> -           </widget> -          </item> -         </layout> +        <item row="3" column="1"> +         <widget class="QLabel" name="label_5"> +          <property name="text"> +           <string>Player 3</string> +          </property> +         </widget>          </item> -       </layout> -      </widget> -     </item> -     <item row="0" column="1"> -      <widget class="QGroupBox" name="Dpad"> -       <property name="title"> -        <string>Directional Pad</string> -       </property> -       <property name="flat"> -        <bool>false</bool> -       </property> -       <property name="checkable"> -        <bool>false</bool> -       </property> -       <layout class="QGridLayout" name="gridLayout_2"> -        <item row="1" column="0"> -         <layout class="QVBoxLayout" name="buttonDpadUpVerticalLayout"> -          <item> -           <widget class="QLabel" name="labelDpadUp"> -            <property name="text"> -             <string>Up:</string> -            </property> -           </widget> -          </item> -          <item> -           <widget class="QPushButton" name="buttonDpadUp"> -            <property name="text"> -             <string/> -            </property> -           </widget> -          </item> -         </layout> +        <item row="4" column="1"> +         <widget class="QLabel" name="label_6"> +          <property name="text"> +           <string>Player 4</string> +          </property> +         </widget>          </item> -        <item row="1" column="1"> -         <layout class="QVBoxLayout" name="buttonDpadDownVerticalLayout"> -          <item> -           <widget class="QLabel" name="labelDpadDown"> -            <property name="text"> -             <string>Down:</string> -            </property> -           </widget> -          </item> -          <item> -           <widget class="QPushButton" name="buttonDpadDown"> -            <property name="text"> -             <string/> -            </property> -           </widget> -          </item> -         </layout> +        <item row="5" column="1"> +         <widget class="QLabel" name="label_7"> +          <property name="text"> +           <string>Player 5</string> +          </property> +         </widget>          </item> -        <item row="0" column="0"> -         <layout class="QVBoxLayout" name="buttonDpadLeftVerticalLayout"> -          <item> -           <widget class="QLabel" name="labelDpadLeft"> -            <property name="text"> -             <string>Left:</string> -            </property> -           </widget> -          </item> -          <item> -           <widget class="QPushButton" name="buttonDpadLeft"> -            <property name="text"> -             <string/> -            </property> -           </widget> -          </item> -         </layout> +        <item row="6" column="1"> +         <widget class="QLabel" name="label_8"> +          <property name="text"> +           <string>Player 6</string> +          </property> +         </widget>          </item> -        <item row="0" column="1"> -         <layout class="QVBoxLayout" name="buttonDpadRightVerticalLayout"> -          <item> -           <widget class="QLabel" name="labelDpadRight"> -            <property name="text"> -             <string>Right:</string> -            </property> -           </widget> -          </item> -          <item> -           <widget class="QPushButton" name="buttonDpadRight"> -            <property name="text"> -             <string/> -            </property> -           </widget> -          </item> -         </layout> +        <item row="7" column="1"> +         <widget class="QLabel" name="label_9"> +          <property name="text"> +           <string>Player 7</string> +          </property> +         </widget> +        </item> +        <item row="8" column="1"> +         <widget class="QLabel" name="label_10"> +          <property name="text"> +           <string>Player 8</string> +          </property> +         </widget>          </item>         </layout>        </widget>       </item> -     <item row="3" column="0"> -      <widget class="QGroupBox" name="shoulderButtons"> +     <item> +      <widget class="QGroupBox" name="gridGroupBox">         <property name="title"> -        <string>Shoulder Buttons</string> -       </property> -       <property name="flat"> -        <bool>false</bool> +        <string>Handheld</string>         </property> -       <property name="checkable"> -        <bool>false</bool> -       </property> -       <layout class="QGridLayout" name="gridLayout_3"> -        <item row="0" column="0"> -         <layout class="QVBoxLayout" name="buttonShoulderButtonsLVerticalLayout"> -          <item> -           <widget class="QLabel" name="labelL"> -            <property name="text"> -             <string>L:</string> -            </property> -           </widget> -          </item> -          <item> -           <widget class="QPushButton" name="buttonL"> -            <property name="text"> -             <string/> -            </property> -           </widget> -          </item> -         </layout> +       <layout class="QGridLayout" name="gridLayout_2"> +        <item row="1" column="2"> +         <spacer name="horizontalSpacer_5"> +          <property name="orientation"> +           <enum>Qt::Horizontal</enum> +          </property> +          <property name="sizeType"> +           <enum>QSizePolicy::Fixed</enum> +          </property> +          <property name="sizeHint" stdset="0"> +           <size> +            <width>72</width> +            <height>20</height> +           </size> +          </property> +         </spacer>          </item> -        <item row="0" column="1"> -         <layout class="QVBoxLayout" name="buttonShoulderButtonsRVerticalLayout"> -          <item> -           <widget class="QLabel" name="labelR"> -            <property name="text"> -             <string>R:</string> -            </property> -           </widget> -          </item> -          <item> -           <widget class="QPushButton" name="buttonR"> -            <property name="text"> -             <string/> -            </property> -           </widget> -          </item> -         </layout> +        <item row="1" column="4"> +         <spacer name="horizontalSpacer_4"> +          <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"> +         <widget class="QPushButton" name="handheld_configure"> +          <property name="text"> +           <string>Configure</string> +          </property> +         </widget>          </item>          <item row="1" column="0"> -         <layout class="QVBoxLayout" name="buttonShoulderButtonsZLVerticalLayout"> -          <item> -           <widget class="QLabel" name="labelZL"> -            <property name="text"> -             <string>ZL:</string> -            </property> -           </widget> -          </item> -          <item> -           <widget class="QPushButton" name="buttonZL"> -            <property name="text"> -             <string/> -            </property> -           </widget> -          </item> -         </layout> +         <spacer name="horizontalSpacer_3"> +          <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"> -         <layout class="QVBoxLayout" name="buttonShoulderButtonsZRVerticalLayout"> -          <item> -           <widget class="QLabel" name="labelZR"> -            <property name="text"> -             <string>ZR:</string> -            </property> -           </widget> -          </item> -          <item> -           <widget class="QPushButton" name="buttonZR"> -            <property name="text"> -             <string/> -            </property> -           </widget> -          </item> -         </layout> -        </item> -        <item row="2" column="0"> -         <layout class="QVBoxLayout" name="buttonShoulderButtonsSLVerticalLayout"> -          <item> -           <widget class="QLabel" name="labelSL"> -            <property name="text"> -             <string>SL:</string> -            </property> -           </widget> -          </item> -          <item> -           <widget class="QPushButton" name="buttonSL"> -            <property name="text"> -             <string/> -            </property> -           </widget> -          </item> -         </layout> +         <widget class="QCheckBox" name="handheld_connected"> +          <property name="text"> +           <string>Joycons Docked</string> +          </property> +         </widget>          </item> -        <item row="2" column="1"> -         <layout class="QVBoxLayout" name="buttonShoulderButtonsSRVerticalLayout"> -          <item> -           <widget class="QLabel" name="labelSR"> -            <property name="text"> -             <string>SR:</string> -            </property> -           </widget> -          </item> -          <item> -           <widget class="QPushButton" name="buttonSR"> -            <property name="text"> -             <string/> -            </property> -           </widget> -          </item> -         </layout> +        <item row="0" column="1"> +         <widget class="QCheckBox" name="use_docked_mode"> +          <property name="text"> +           <string>Use Docked Mode</string> +          </property> +         </widget>          </item>         </layout>        </widget>       </item> -     <item row="1" column="1"> -      <widget class="QGroupBox" name="RStick"> +     <item> +      <widget class="QGroupBox" name="gridGroupBox">         <property name="title"> -        <string>Right Stick</string> -       </property> -       <property name="alignment"> -        <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set> -       </property> -       <property name="flat"> -        <bool>false</bool> +        <string>Other</string>         </property> -       <property name="checkable"> -        <bool>false</bool> -       </property> -       <layout class="QGridLayout" name="gridLayout_5"> +       <layout class="QGridLayout" name="gridLayout_3">          <item row="1" column="1"> -         <layout class="QVBoxLayout" name="buttonRStickDownVerticalLayout"> -          <item> -           <widget class="QLabel" name="labelRStickDown"> -            <property name="text"> -             <string>Down:</string> -            </property> -           </widget> -          </item> -          <item> -           <widget class="QPushButton" name="buttonRStickDown"> -            <property name="text"> -             <string/> -            </property> -           </widget> -          </item> -         </layout> -        </item> -        <item row="0" column="1"> -         <layout class="QVBoxLayout" name="buttonRStickRightVerticalLayout"> -          <item> -           <widget class="QLabel" name="labelRStickRight"> -            <property name="text"> -             <string>Right:</string> -            </property> -           </widget> -          </item> -          <item> -           <widget class="QPushButton" name="buttonRStickRight"> -            <property name="text"> -             <string/> -            </property> -           </widget> -          </item> -         </layout> -        </item> -        <item row="3" column="0" colspan="2"> -         <widget class="QPushButton" name="buttonRStickAnalog"> +         <widget class="QCheckBox" name="keyboard_enabled"> +          <property name="minimumSize"> +           <size> +            <width>0</width> +            <height>23</height> +           </size> +          </property>            <property name="text"> -           <string>Set Analog Stick</string> +           <string>Keyboard</string>            </property>           </widget>          </item> -        <item row="0" column="0"> -         <layout class="QVBoxLayout" name="buttonRStickLeftVerticalLayout"> -          <item> -           <widget class="QLabel" name="labelRStickLeft"> -            <property name="text"> -             <string>Left:</string> -            </property> -           </widget> -          </item> -          <item> -           <widget class="QPushButton" name="buttonRStickLeft"> -            <property name="text"> -             <string/> -            </property> -           </widget> -          </item> -         </layout> -        </item> -        <item row="1" column="0"> -         <layout class="QVBoxLayout" name="buttonRStickUpVerticalLayout"> -          <item> -           <widget class="QLabel" name="labelRStickUp"> -            <property name="text"> -             <string>Up:</string> -            </property> -           </widget> -          </item> -          <item> -           <widget class="QPushButton" name="buttonRStickUp"> -            <property name="text"> -             <string/> -            </property> -           </widget> -          </item> -         </layout> -        </item> -        <item row="2" column="0"> -         <layout class="QVBoxLayout" name="buttonRStickPressedVerticalLayout"> -          <item> -           <widget class="QLabel" name="labelRStickPressed"> -            <property name="text"> -             <string>Pressed:</string> -            </property> -           </widget> -          </item> -          <item> -           <widget class="QPushButton" name="buttonRStick"> -            <property name="text"> -             <string/> -            </property> -           </widget> -          </item> -         </layout> -        </item>          <item row="2" column="1"> -         <layout class="QVBoxLayout" name="buttonRStickModVerticalLayout"> -          <item> -           <widget class="QLabel" name="labelRStickMod"> -            <property name="text"> -             <string>Modifier:</string> -            </property> -           </widget> -          </item> -          <item> -           <widget class="QPushButton" name="buttonRStickMod"> -            <property name="text"> -             <string/> -            </property> -           </widget> -          </item> -         </layout> +         <widget class="QCheckBox" name="debug_enabled"> +          <property name="text"> +           <string>Debug Controller</string> +          </property> +         </widget>          </item> -       </layout> -      </widget> -     </item> -     <item row="1" column="0"> -      <widget class="QGroupBox" name="LStick"> -       <property name="title"> -        <string>Left Stick</string> -       </property> -       <property name="flat"> -        <bool>false</bool> -       </property> -       <property name="checkable"> -        <bool>false</bool> -       </property> -       <layout class="QGridLayout" name="gridLayout_4"> -        <item row="1" column="1"> -         <layout class="QVBoxLayout" name="buttonLStickDownVerticalLayout"> -          <item> -           <widget class="QLabel" name="labelLStickDown"> -            <property name="text"> -             <string>Down:</string> -            </property> -           </widget> -          </item> -          <item> -           <widget class="QPushButton" name="buttonLStickDown"> -            <property name="text"> -             <string/> -            </property> -           </widget> -          </item> -         </layout> -        </item> -        <item row="4" column="0" colspan="2"> -         <widget class="QPushButton" name="buttonLStickAnalog"> +        <item row="3" column="1"> +         <widget class="QCheckBox" name="touchscreen_enabled">            <property name="text"> -           <string>Set Analog Stick</string> +           <string>Touchscreen</string>            </property>           </widget>          </item>          <item row="0" column="1"> -         <layout class="QVBoxLayout" name="buttonLStickRightVerticalLayout"> -          <item> -           <widget class="QLabel" name="labelLStickRight"> -            <property name="text"> -             <string>Right:</string> -            </property> -           </widget> -          </item> -          <item> -           <widget class="QPushButton" name="buttonLStickRight"> -            <property name="text"> -             <string/> -            </property> -           </widget> -          </item> -         </layout> +         <widget class="QCheckBox" name="mouse_enabled"> +          <property name="minimumSize"> +           <size> +            <width>0</width> +            <height>23</height> +           </size> +          </property> +          <property name="text"> +           <string>Mouse</string> +          </property> +         </widget> +        </item> +        <item row="0" column="4"> +         <spacer name="horizontalSpacer_7"> +          <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="0" column="2"> +         <spacer name="horizontalSpacer_8"> +          <property name="orientation"> +           <enum>Qt::Horizontal</enum> +          </property> +          <property name="sizeType"> +           <enum>QSizePolicy::Fixed</enum> +          </property> +          <property name="sizeHint" stdset="0"> +           <size> +            <width>76</width> +            <height>20</height> +           </size> +          </property> +         </spacer>          </item>          <item row="0" column="0"> -         <layout class="QVBoxLayout" name="buttonLStickLeftVerticalLayout"> -          <item> -           <widget class="QLabel" name="labelLStickLeft"> -            <property name="text"> -             <string>Left:</string> -            </property> -           </widget> -          </item> -          <item> -           <widget class="QPushButton" name="buttonLStickLeft"> -            <property name="text"> -             <string/> -            </property> -           </widget> -          </item> -         </layout> +         <spacer name="horizontalSpacer_6"> +          <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="0"> -         <layout class="QVBoxLayout" name="buttonLStickUpVerticalLayout"> -          <item> -           <widget class="QLabel" name="labelLStickUp"> -            <property name="text"> -             <string>Up:</string> -            </property> -           </widget> -          </item> -          <item> -           <widget class="QPushButton" name="buttonLStickUp"> -            <property name="text"> -             <string/> -            </property> -           </widget> -          </item> -         </layout> -        </item> -        <item row="3" column="0"> -         <layout class="QVBoxLayout" name="buttonLStickModVerticalLayout"> -          <item> -           <widget class="QLabel" name="labelLStickMod"> -            <property name="text"> -             <string>Modifier:</string> -            </property> -           </widget> -          </item> -          <item> -           <widget class="QPushButton" name="buttonLStickMod"> -            <property name="text"> -             <string/> -            </property> -           </widget> -          </item> -         </layout> +        <item row="3" column="3"> +         <widget class="QPushButton" name="touchscreen_advanced"> +          <property name="text"> +           <string>Advanced</string> +          </property> +         </widget>          </item> -        <item row="3" column="1"> -         <layout class="QVBoxLayout" name="buttonLStickPressedVerticalLayout" stretch="0,0"> -          <item> -           <widget class="QLabel" name="labelLStickPressed"> -            <property name="text"> -             <string>Pressed:</string> -            </property> -           </widget> -          </item> -          <item> -           <widget class="QPushButton" name="buttonLStick"> -            <property name="text"> -             <string/> -            </property> -           </widget> -          </item> -         </layout> +        <item row="2" column="3"> +         <widget class="QPushButton" name="debug_configure"> +          <property name="text"> +           <string>Configure</string> +          </property> +         </widget> +        </item> +        <item row="0" column="3"> +         <widget class="QPushButton" name="mouse_advanced"> +          <property name="text"> +           <string>Advanced</string> +          </property> +         </widget>          </item>         </layout>        </widget>       </item> -    </layout> -   </item> -   <item> -    <layout class="QHBoxLayout" name="horizontalLayout">       <item> -      <spacer name="horizontalSpacer"> +      <spacer name="verticalSpacer">         <property name="orientation"> -        <enum>Qt::Horizontal</enum> +        <enum>Qt::Vertical</enum>         </property>         <property name="sizeHint" stdset="0">          <size> -         <width>40</width> -         <height>20</height> +         <width>20</width> +         <height>40</height>          </size>         </property>        </spacer>       </item>       <item> -      <widget class="QPushButton" name="buttonClearAll"> -       <property name="sizePolicy"> -        <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> -         <horstretch>0</horstretch> -         <verstretch>0</verstretch> -        </sizepolicy> -       </property> -       <property name="sizeIncrement"> -        <size> -         <width>0</width> -         <height>0</height> -        </size> -       </property> -       <property name="baseSize"> -        <size> -         <width>0</width> -         <height>0</height> -        </size> -       </property> -       <property name="layoutDirection"> -        <enum>Qt::LeftToRight</enum> -       </property> -       <property name="text"> -        <string>Clear All</string> -       </property> -      </widget> -     </item> -     <item> -      <widget class="QPushButton" name="buttonRestoreDefaults"> -       <property name="sizePolicy"> -        <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> -         <horstretch>0</horstretch> -         <verstretch>0</verstretch> -        </sizepolicy> -       </property> -       <property name="sizeIncrement"> -        <size> -         <width>0</width> -         <height>0</height> -        </size> -       </property> -       <property name="baseSize"> -        <size> -         <width>0</width> -         <height>0</height> -        </size> -       </property> -       <property name="layoutDirection"> -        <enum>Qt::LeftToRight</enum> -       </property> -       <property name="text"> -        <string>Restore Defaults</string> -       </property> -      </widget> +      <layout class="QHBoxLayout" name="horizontalLayout"> +       <item> +        <widget class="QPushButton" name="restore_defaults_button"> +         <property name="text"> +          <string>Restore Defaults</string> +         </property> +        </widget> +       </item> +       <item> +        <spacer name="horizontalSpacer_9"> +         <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> +        <widget class="QDialogButtonBox" name="buttonBox"> +         <property name="standardButtons"> +          <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> +         </property> +        </widget> +       </item> +      </layout>       </item>      </layout>     </item>    </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 new file mode 100644 index 000000000..ba2b32c4f --- /dev/null +++ b/src/yuzu/configuration/configure_input_player.cpp @@ -0,0 +1,501 @@ +// 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 <QColorDialog> +#include <QGridLayout> +#include <QMenu> +#include <QMessageBox> +#include <QTimer> +#include "common/assert.h" +#include "common/param_package.h" +#include "input_common/main.h" +#include "ui_configure_input_player.h" +#include "yuzu/configuration/config.h" +#include "yuzu/configuration/configure_input_player.h" + +const std::array<std::string, ConfigureInputPlayer::ANALOG_SUB_BUTTONS_NUM> +    ConfigureInputPlayer::analog_sub_buttons{{ +        "up", +        "down", +        "left", +        "right", +        "modifier", +    }}; + +static void LayerGridElements(QGridLayout* grid, QWidget* item, QWidget* onTopOf) { +    const int index1 = grid->indexOf(item); +    const int index2 = grid->indexOf(onTopOf); +    int row, column, rowSpan, columnSpan; +    grid->getItemPosition(index2, &row, &column, &rowSpan, &columnSpan); +    grid->takeAt(index1); +    grid->addWidget(item, row, column, rowSpan, columnSpan); +} + +static QString GetKeyName(int key_code) { +    switch (key_code) { +    case Qt::Key_Shift: +        return QObject::tr("Shift"); +    case Qt::Key_Control: +        return QObject::tr("Ctrl"); +    case Qt::Key_Alt: +        return QObject::tr("Alt"); +    case Qt::Key_Meta: +        return ""; +    default: +        return QKeySequence(key_code).toString(); +    } +} + +static void SetAnalogButton(const Common::ParamPackage& input_param, +                            Common::ParamPackage& analog_param, const std::string& button_name) { +    if (analog_param.Get("engine", "") != "analog_from_button") { +        analog_param = { +            {"engine", "analog_from_button"}, +            {"modifier_scale", "0.5"}, +        }; +    } +    analog_param.Set(button_name, input_param.Serialize()); +} + +static QString ButtonToText(const Common::ParamPackage& param) { +    if (!param.Has("engine")) { +        return QObject::tr("[not set]"); +    } else if (param.Get("engine", "") == "keyboard") { +        return GetKeyName(param.Get("code", 0)); +    } else if (param.Get("engine", "") == "sdl") { +        if (param.Has("hat")) { +            return QString(QObject::tr("Hat %1 %2")) +                .arg(param.Get("hat", "").c_str(), param.Get("direction", "").c_str()); +        } +        if (param.Has("axis")) { +            return QString(QObject::tr("Axis %1%2")) +                .arg(param.Get("axis", "").c_str(), param.Get("direction", "").c_str()); +        } +        if (param.Has("button")) { +            return QString(QObject::tr("Button %1")).arg(param.Get("button", "").c_str()); +        } +        return QString(); +    } else { +        return QObject::tr("[unknown]"); +    } +}; + +static QString AnalogToText(const Common::ParamPackage& param, const std::string& dir) { +    if (!param.Has("engine")) { +        return QObject::tr("[not set]"); +    } else if (param.Get("engine", "") == "analog_from_button") { +        return ButtonToText(Common::ParamPackage{param.Get(dir, "")}); +    } else if (param.Get("engine", "") == "sdl") { +        if (dir == "modifier") { +            return QString(QObject::tr("[unused]")); +        } + +        if (dir == "left" || dir == "right") { +            return QString(QObject::tr("Axis %1")).arg(param.Get("axis_x", "").c_str()); +        } else if (dir == "up" || dir == "down") { +            return QString(QObject::tr("Axis %1")).arg(param.Get("axis_y", "").c_str()); +        } +        return QString(); +    } else { +        return QObject::tr("[unknown]"); +    } +}; + +ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_index, bool debug) +    : QDialog(parent), ui(std::make_unique<Ui::ConfigureInputPlayer>()), player_index(player_index), +      debug(debug), timeout_timer(std::make_unique<QTimer>()), +      poll_timer(std::make_unique<QTimer>()) { +    ui->setupUi(this); +    setFocusPolicy(Qt::ClickFocus); + +    button_map = { +        ui->buttonA,          ui->buttonB,        ui->buttonX,           ui->buttonY, +        ui->buttonLStick,     ui->buttonRStick,   ui->buttonL,           ui->buttonR, +        ui->buttonZL,         ui->buttonZR,       ui->buttonPlus,        ui->buttonMinus, +        ui->buttonDpadLeft,   ui->buttonDpadUp,   ui->buttonDpadRight,   ui->buttonDpadDown, +        ui->buttonLStickLeft, ui->buttonLStickUp, ui->buttonLStickRight, ui->buttonLStickDown, +        ui->buttonRStickLeft, ui->buttonRStickUp, ui->buttonRStickRight, ui->buttonRStickDown, +        ui->buttonSL,         ui->buttonSR,       ui->buttonHome,        ui->buttonScreenshot, +    }; + +    analog_map_buttons = {{ +        { +            ui->buttonLStickUp, +            ui->buttonLStickDown, +            ui->buttonLStickLeft, +            ui->buttonLStickRight, +            ui->buttonLStickMod, +        }, +        { +            ui->buttonRStickUp, +            ui->buttonRStickDown, +            ui->buttonRStickLeft, +            ui->buttonRStickRight, +            ui->buttonRStickMod, +        }, +    }}; + +    debug_hidden = { +        ui->buttonSL,         ui->labelSL, +        ui->buttonSR,         ui->labelSR, +        ui->buttonLStick,     ui->labelLStickPressed, +        ui->buttonRStick,     ui->labelRStickPressed, +        ui->buttonHome,       ui->labelHome, +        ui->buttonScreenshot, ui->labelScreenshot, +    }; + +    auto layout = Settings::values.players[player_index].type; +    if (debug) +        layout = Settings::ControllerType::DualJoycon; + +    switch (layout) { +    case Settings::ControllerType::ProController: +    case Settings::ControllerType::DualJoycon: +        layout_hidden = { +            ui->buttonSL, +            ui->labelSL, +            ui->buttonSR, +            ui->labelSR, +        }; +        break; +    case Settings::ControllerType::LeftJoycon: +        layout_hidden = { +            ui->right_body_button, +            ui->right_buttons_button, +            ui->right_body_label, +            ui->right_buttons_label, +            ui->buttonR, +            ui->labelR, +            ui->buttonZR, +            ui->labelZR, +            ui->labelHome, +            ui->buttonHome, +            ui->buttonPlus, +            ui->labelPlus, +            ui->RStick, +            ui->faceButtons, +        }; +        break; +    case Settings::ControllerType::RightJoycon: +        layout_hidden = { +            ui->left_body_button, ui->left_buttons_button, +            ui->left_body_label,  ui->left_buttons_label, +            ui->buttonL,          ui->labelL, +            ui->buttonZL,         ui->labelZL, +            ui->labelScreenshot,  ui->buttonScreenshot, +            ui->buttonMinus,      ui->labelMinus, +            ui->LStick,           ui->Dpad, +        }; +        break; +    } + +    if (debug || layout == Settings::ControllerType::ProController) { +        ui->controller_color->hide(); +    } else { +        if (layout == Settings::ControllerType::LeftJoycon || +            layout == Settings::ControllerType::RightJoycon) { +            ui->horizontalSpacer_4->setGeometry({0, 0, 0, 0}); + +            LayerGridElements(ui->buttons, ui->shoulderButtons, ui->Dpad); +            LayerGridElements(ui->buttons, ui->misc, ui->RStick); +            LayerGridElements(ui->buttons, ui->Dpad, ui->faceButtons); +            LayerGridElements(ui->buttons, ui->RStick, ui->LStick); +        } +    } + +    for (auto* widget : layout_hidden) +        widget->setVisible(false); + +    analog_map_stick = {ui->buttonLStickAnalog, ui->buttonRStickAnalog}; + +    for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) { +        if (!button_map[button_id]) +            continue; +        button_map[button_id]->setContextMenuPolicy(Qt::CustomContextMenu); +        connect(button_map[button_id], &QPushButton::released, [=]() { +            handleClick( +                button_map[button_id], +                [=](const Common::ParamPackage& params) { buttons_param[button_id] = params; }, +                InputCommon::Polling::DeviceType::Button); +        }); +        connect(button_map[button_id], &QPushButton::customContextMenuRequested, +                [=](const QPoint& menu_location) { +                    QMenu context_menu; +                    context_menu.addAction(tr("Clear"), [&] { +                        buttons_param[button_id].Clear(); +                        button_map[button_id]->setText(tr("[not set]")); +                    }); +                    context_menu.addAction(tr("Restore Default"), [&] { +                        buttons_param[button_id] = Common::ParamPackage{ +                            InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])}; +                        button_map[button_id]->setText(ButtonToText(buttons_param[button_id])); +                    }); +                    context_menu.exec(button_map[button_id]->mapToGlobal(menu_location)); +                }); +    } + +    for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) { +        for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) { +            if (!analog_map_buttons[analog_id][sub_button_id]) +                continue; +            analog_map_buttons[analog_id][sub_button_id]->setContextMenuPolicy( +                Qt::CustomContextMenu); +            connect(analog_map_buttons[analog_id][sub_button_id], &QPushButton::released, [=]() { +                handleClick(analog_map_buttons[analog_id][sub_button_id], +                            [=](const Common::ParamPackage& params) { +                                SetAnalogButton(params, analogs_param[analog_id], +                                                analog_sub_buttons[sub_button_id]); +                            }, +                            InputCommon::Polling::DeviceType::Button); +            }); +            connect(analog_map_buttons[analog_id][sub_button_id], +                    &QPushButton::customContextMenuRequested, [=](const QPoint& menu_location) { +                        QMenu context_menu; +                        context_menu.addAction(tr("Clear"), [&] { +                            analogs_param[analog_id].Erase(analog_sub_buttons[sub_button_id]); +                            analog_map_buttons[analog_id][sub_button_id]->setText(tr("[not set]")); +                        }); +                        context_menu.addAction(tr("Restore Default"), [&] { +                            Common::ParamPackage params{InputCommon::GenerateKeyboardParam( +                                Config::default_analogs[analog_id][sub_button_id])}; +                            SetAnalogButton(params, analogs_param[analog_id], +                                            analog_sub_buttons[sub_button_id]); +                            analog_map_buttons[analog_id][sub_button_id]->setText(AnalogToText( +                                analogs_param[analog_id], analog_sub_buttons[sub_button_id])); +                        }); +                        context_menu.exec(analog_map_buttons[analog_id][sub_button_id]->mapToGlobal( +                            menu_location)); +                    }); +        } +        connect(analog_map_stick[analog_id], &QPushButton::released, [=]() { +            QMessageBox::information(this, tr("Information"), +                                     tr("After pressing OK, first move your joystick horizontally, " +                                        "and then vertically.")); +            handleClick( +                analog_map_stick[analog_id], +                [=](const Common::ParamPackage& params) { analogs_param[analog_id] = params; }, +                InputCommon::Polling::DeviceType::Analog); +        }); +    } + +    connect(ui->buttonClearAll, &QPushButton::released, [this] { ClearAll(); }); +    connect(ui->buttonRestoreDefaults, &QPushButton::released, [this]() { restoreDefaults(); }); + +    timeout_timer->setSingleShot(true); +    connect(timeout_timer.get(), &QTimer::timeout, [this]() { setPollingResult({}, true); }); + +    connect(poll_timer.get(), &QTimer::timeout, [this]() { +        Common::ParamPackage params; +        for (auto& poller : device_pollers) { +            params = poller->GetNextInput(); +            if (params.Has("engine")) { +                setPollingResult(params, false); +                return; +            } +        } +    }); + +    controller_color_buttons = { +        ui->left_body_button, +        ui->left_buttons_button, +        ui->right_body_button, +        ui->right_buttons_button, +    }; + +    for (std::size_t i = 0; i < controller_color_buttons.size(); ++i) { +        connect(controller_color_buttons[i], &QPushButton::clicked, this, +                [this, i] { OnControllerButtonClick(static_cast<int>(i)); }); +    } + +    this->loadConfiguration(); +    this->resize(0, 0); + +    // TODO(wwylele): enable this when we actually emulate it +    ui->buttonHome->setEnabled(false); +} + +ConfigureInputPlayer::~ConfigureInputPlayer() = default; + +void ConfigureInputPlayer::applyConfiguration() { +    auto& buttons = +        debug ? Settings::values.debug_pad_buttons : Settings::values.players[player_index].buttons; +    auto& analogs = +        debug ? Settings::values.debug_pad_analogs : Settings::values.players[player_index].analogs; + +    std::transform(buttons_param.begin(), buttons_param.end(), buttons.begin(), +                   [](const Common::ParamPackage& param) { return param.Serialize(); }); +    std::transform(analogs_param.begin(), analogs_param.end(), analogs.begin(), +                   [](const Common::ParamPackage& param) { return param.Serialize(); }); + +    if (debug) +        return; + +    std::array<u32, 4> colors{}; +    std::transform(controller_colors.begin(), controller_colors.end(), colors.begin(), +                   [](QColor color) { return color.rgb(); }); + +    Settings::values.players[player_index].body_color_left = colors[0]; +    Settings::values.players[player_index].button_color_left = colors[1]; +    Settings::values.players[player_index].body_color_right = colors[2]; +    Settings::values.players[player_index].button_color_right = colors[3]; +} + +void ConfigureInputPlayer::OnControllerButtonClick(int i) { +    const QColor new_bg_color = QColorDialog::getColor(controller_colors[i]); +    if (!new_bg_color.isValid()) +        return; +    controller_colors[i] = new_bg_color; +    controller_color_buttons[i]->setStyleSheet( +        QString("QPushButton { background-color: %1 }").arg(controller_colors[i].name())); +} + +void ConfigureInputPlayer::loadConfiguration() { +    if (debug) { +        std::transform(Settings::values.debug_pad_buttons.begin(), +                       Settings::values.debug_pad_buttons.end(), buttons_param.begin(), +                       [](const std::string& str) { return Common::ParamPackage(str); }); +        std::transform(Settings::values.debug_pad_analogs.begin(), +                       Settings::values.debug_pad_analogs.end(), analogs_param.begin(), +                       [](const std::string& str) { return Common::ParamPackage(str); }); +    } else { +        std::transform(Settings::values.players[player_index].buttons.begin(), +                       Settings::values.players[player_index].buttons.end(), buttons_param.begin(), +                       [](const std::string& str) { return Common::ParamPackage(str); }); +        std::transform(Settings::values.players[player_index].analogs.begin(), +                       Settings::values.players[player_index].analogs.end(), analogs_param.begin(), +                       [](const std::string& str) { return Common::ParamPackage(str); }); +    } + +    updateButtonLabels(); + +    if (debug) +        return; + +    std::array<u32, 4> colors = { +        Settings::values.players[player_index].body_color_left, +        Settings::values.players[player_index].button_color_left, +        Settings::values.players[player_index].body_color_right, +        Settings::values.players[player_index].button_color_right, +    }; + +    std::transform(colors.begin(), colors.end(), controller_colors.begin(), +                   [](u32 rgb) { return QColor::fromRgb(rgb); }); + +    for (std::size_t i = 0; i < colors.size(); ++i) { +        controller_color_buttons[i]->setStyleSheet( +            QString("QPushButton { background-color: %1 }").arg(controller_colors[i].name())); +    } +} + +void ConfigureInputPlayer::restoreDefaults() { +    for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) { +        buttons_param[button_id] = Common::ParamPackage{ +            InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])}; +    } + +    for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) { +        for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) { +            Common::ParamPackage params{InputCommon::GenerateKeyboardParam( +                Config::default_analogs[analog_id][sub_button_id])}; +            SetAnalogButton(params, analogs_param[analog_id], analog_sub_buttons[sub_button_id]); +        } +    } +    updateButtonLabels(); +} + +void ConfigureInputPlayer::ClearAll() { +    for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) { +        if (button_map[button_id] && button_map[button_id]->isEnabled()) +            buttons_param[button_id].Clear(); +    } +    for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) { +        for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) { +            if (analog_map_buttons[analog_id][sub_button_id] && +                analog_map_buttons[analog_id][sub_button_id]->isEnabled()) +                analogs_param[analog_id].Erase(analog_sub_buttons[sub_button_id]); +        } +    } + +    updateButtonLabels(); +} + +void ConfigureInputPlayer::updateButtonLabels() { +    for (int button = 0; button < Settings::NativeButton::NumButtons; button++) { +        button_map[button]->setText(ButtonToText(buttons_param[button])); +    } + +    for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) { +        for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) { +            if (analog_map_buttons[analog_id][sub_button_id]) { +                analog_map_buttons[analog_id][sub_button_id]->setText( +                    AnalogToText(analogs_param[analog_id], analog_sub_buttons[sub_button_id])); +            } +        } +        analog_map_stick[analog_id]->setText(tr("Set Analog Stick")); +    } +} + +void ConfigureInputPlayer::handleClick( +    QPushButton* button, std::function<void(const Common::ParamPackage&)> new_input_setter, +    InputCommon::Polling::DeviceType type) { +    button->setText(tr("[press key]")); +    button->setFocus(); + +    const auto iter = std::find(button_map.begin(), button_map.end(), button); +    ASSERT(iter != button_map.end()); +    const auto index = std::distance(button_map.begin(), iter); +    ASSERT(index < Settings::NativeButton::NumButtons && index >= 0); + +    input_setter = new_input_setter; + +    device_pollers = InputCommon::Polling::GetPollers(type); + +    // Keyboard keys can only be used as button devices +    want_keyboard_keys = type == InputCommon::Polling::DeviceType::Button; + +    for (auto& poller : device_pollers) { +        poller->Start(); +    } + +    grabKeyboard(); +    grabMouse(); +    timeout_timer->start(5000); // Cancel after 5 seconds +    poll_timer->start(200);     // Check for new inputs every 200ms +} + +void ConfigureInputPlayer::setPollingResult(const Common::ParamPackage& params, bool abort) { +    releaseKeyboard(); +    releaseMouse(); +    timeout_timer->stop(); +    poll_timer->stop(); +    for (auto& poller : device_pollers) { +        poller->Stop(); +    } + +    if (!abort) { +        (*input_setter)(params); +    } + +    updateButtonLabels(); +    input_setter = std::nullopt; +} + +void ConfigureInputPlayer::keyPressEvent(QKeyEvent* event) { +    if (!input_setter || !event) +        return; + +    if (event->key() != Qt::Key_Escape) { +        if (want_keyboard_keys) { +            setPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())}, +                             false); +        } else { +            // Escape key wasn't pressed and we don't want any keyboard keys, so don't stop polling +            return; +        } +    } +    setPollingResult({}, true); +} diff --git a/src/yuzu/configuration/configure_input_player.h b/src/yuzu/configuration/configure_input_player.h new file mode 100644 index 000000000..7a53f6715 --- /dev/null +++ b/src/yuzu/configuration/configure_input_player.h @@ -0,0 +1,104 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <functional> +#include <memory> +#include <optional> +#include <string> + +#include <QDialog> +#include <QKeyEvent> + +#include "common/param_package.h" +#include "core/settings.h" +#include "input_common/main.h" +#include "ui_configure_input.h" + +class QPushButton; +class QString; +class QTimer; + +namespace Ui { +class ConfigureInputPlayer; +} + +class ConfigureInputPlayer : public QDialog { +    Q_OBJECT + +public: +    explicit ConfigureInputPlayer(QWidget* parent, std::size_t player_index, bool debug = false); +    ~ConfigureInputPlayer() override; + +    /// Save all button configurations to settings file +    void applyConfiguration(); + +private: +    void OnControllerButtonClick(int i); + +    /// Load configuration settings. +    void loadConfiguration(); +    /// Restore all buttons to their default values. +    void restoreDefaults(); +    /// Clear all input configuration +    void ClearAll(); + +    /// Update UI to reflect current configuration. +    void updateButtonLabels(); + +    /// Called when the button was pressed. +    void handleClick(QPushButton* button, +                     std::function<void(const Common::ParamPackage&)> new_input_setter, +                     InputCommon::Polling::DeviceType type); + +    /// Finish polling and configure input using the input_setter +    void setPollingResult(const Common::ParamPackage& params, bool abort); + +    /// Handle key press events. +    void keyPressEvent(QKeyEvent* event) override; + +    std::unique_ptr<Ui::ConfigureInputPlayer> ui; + +    std::size_t player_index; +    bool debug; + +    std::unique_ptr<QTimer> timeout_timer; +    std::unique_ptr<QTimer> poll_timer; + +    /// This will be the the setting function when an input is awaiting configuration. +    std::optional<std::function<void(const Common::ParamPackage&)>> input_setter; + +    std::array<Common::ParamPackage, Settings::NativeButton::NumButtons> buttons_param; +    std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs> analogs_param; + +    static constexpr int ANALOG_SUB_BUTTONS_NUM = 5; + +    /// Each button input is represented by a QPushButton. +    std::array<QPushButton*, Settings::NativeButton::NumButtons> button_map; + +    std::vector<QWidget*> debug_hidden; +    std::vector<QWidget*> layout_hidden; + +    /// A group of five QPushButtons represent one analog input. The buttons each represent up, +    /// down, left, right, and modifier, respectively. +    std::array<std::array<QPushButton*, ANALOG_SUB_BUTTONS_NUM>, Settings::NativeAnalog::NumAnalogs> +        analog_map_buttons; + +    /// Analog inputs are also represented each with a single button, used to configure with an +    /// actual analog stick +    std::array<QPushButton*, Settings::NativeAnalog::NumAnalogs> analog_map_stick; + +    static const std::array<std::string, ANALOG_SUB_BUTTONS_NUM> analog_sub_buttons; + +    std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> device_pollers; + +    /// A flag to indicate if keyboard keys are okay when configuring an input. If this is false, +    /// keyboard events are ignored. +    bool want_keyboard_keys = false; + +    std::array<QPushButton*, 4> controller_color_buttons; +    std::array<QColor, 4> controller_colors; +}; diff --git a/src/yuzu/configuration/configure_input_player.ui b/src/yuzu/configuration/configure_input_player.ui new file mode 100644 index 000000000..42db020be --- /dev/null +++ b/src/yuzu/configuration/configure_input_player.ui @@ -0,0 +1,1164 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigureInputPlayer</class> + <widget class="QDialog" name="ConfigureInputPlayer"> +  <property name="geometry"> +   <rect> +    <x>0</x> +    <y>0</y> +    <width>408</width> +    <height>731</height> +   </rect> +  </property> +  <property name="windowTitle"> +   <string>Configure Input</string> +  </property> +  <layout class="QVBoxLayout" name="verticalLayout_5"> +   <item> +    <layout class="QGridLayout" name="buttons"> +     <item row="1" column="1"> +      <widget class="QGroupBox" name="RStick"> +       <property name="title"> +        <string>Right Stick</string> +       </property> +       <property name="alignment"> +        <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set> +       </property> +       <property name="flat"> +        <bool>false</bool> +       </property> +       <property name="checkable"> +        <bool>false</bool> +       </property> +       <layout class="QGridLayout" name="gridLayout_5"> +        <item row="1" column="1"> +         <layout class="QVBoxLayout" name="buttonRStickDownVerticalLayout"> +          <item> +           <layout class="QHBoxLayout" name="buttonRStickDownHorizontalLayout"> +            <item> +             <widget class="QLabel" name="labelRStickDown"> +              <property name="text"> +               <string>Down:</string> +              </property> +             </widget> +            </item> +           </layout> +          </item> +          <item> +           <widget class="QPushButton" name="buttonRStickDown"> +            <property name="text"> +             <string/> +            </property> +           </widget> +          </item> +         </layout> +        </item> +        <item row="0" column="1"> +         <layout class="QVBoxLayout" name="buttonRStickRightVerticalLayout"> +          <item> +           <layout class="QHBoxLayout" name="buttonRStickRightHorizontalLayout"> +            <item> +             <widget class="QLabel" name="labelRStickRight"> +              <property name="text"> +               <string>Right:</string> +              </property> +             </widget> +            </item> +           </layout> +          </item> +          <item> +           <widget class="QPushButton" name="buttonRStickRight"> +            <property name="text"> +             <string/> +            </property> +           </widget> +          </item> +         </layout> +        </item> +        <item row="3" column="0" colspan="2"> +         <widget class="QPushButton" name="buttonRStickAnalog"> +          <property name="text"> +           <string>Set Analog Stick</string> +          </property> +         </widget> +        </item> +        <item row="0" column="0"> +         <layout class="QVBoxLayout" name="buttonRStickLeftVerticalLayout"> +          <item> +           <layout class="QHBoxLayout" name="buttonRStickLeftHorizontalLayout"> +            <item> +             <widget class="QLabel" name="labelRStickLeft"> +              <property name="text"> +               <string>Left:</string> +              </property> +             </widget> +            </item> +           </layout> +          </item> +          <item> +           <widget class="QPushButton" name="buttonRStickLeft"> +            <property name="text"> +             <string/> +            </property> +           </widget> +          </item> +         </layout> +        </item> +        <item row="1" column="0"> +         <layout class="QVBoxLayout" name="buttonRStickUpVerticalLayout"> +          <item> +           <layout class="QHBoxLayout" name="buttonRStickUpHorizontalLayout"> +            <item> +             <widget class="QLabel" name="labelRStickUp"> +              <property name="text"> +               <string>Up:</string> +              </property> +             </widget> +            </item> +           </layout> +          </item> +          <item> +           <widget class="QPushButton" name="buttonRStickUp"> +            <property name="text"> +             <string/> +            </property> +           </widget> +          </item> +         </layout> +        </item> +        <item row="2" column="0"> +         <layout class="QVBoxLayout" name="buttonRStickPressedVerticalLayout"> +          <item> +           <layout class="QHBoxLayout" name="buttonRStickPressedHorizontalLayout"> +            <item> +             <widget class="QLabel" name="labelRStickPressed"> +              <property name="text"> +               <string>Pressed:</string> +              </property> +             </widget> +            </item> +           </layout> +          </item> +          <item> +           <widget class="QPushButton" name="buttonRStick"> +            <property name="text"> +             <string/> +            </property> +           </widget> +          </item> +         </layout> +        </item> +        <item row="2" column="1"> +         <layout class="QVBoxLayout" name="buttonRStickModVerticalLayout"> +          <item> +           <layout class="QHBoxLayout" name="buttonRStickModHorizontalLayout"> +            <item> +             <widget class="QLabel" name="labelRStickMod"> +              <property name="text"> +               <string>Modifier:</string> +              </property> +             </widget> +            </item> +           </layout> +          </item> +          <item> +           <widget class="QPushButton" name="buttonRStickMod"> +            <property name="text"> +             <string/> +            </property> +           </widget> +          </item> +         </layout> +        </item> +       </layout> +      </widget> +     </item> +     <item row="0" column="1"> +      <widget class="QGroupBox" name="Dpad"> +       <property name="title"> +        <string>Directional Pad</string> +       </property> +       <property name="flat"> +        <bool>false</bool> +       </property> +       <property name="checkable"> +        <bool>false</bool> +       </property> +       <layout class="QGridLayout" name="gridLayout_2"> +        <item row="1" column="0"> +         <layout class="QVBoxLayout" name="buttonDpadUpVerticalLayout"> +          <item> +           <layout class="QHBoxLayout" name="buttonDpadUpHorizontalLayout"> +            <item> +             <widget class="QLabel" name="labelDpadUp"> +              <property name="text"> +               <string>Up:</string> +              </property> +             </widget> +            </item> +           </layout> +          </item> +          <item> +           <widget class="QPushButton" name="buttonDpadUp"> +            <property name="text"> +             <string/> +            </property> +           </widget> +          </item> +         </layout> +        </item> +        <item row="1" column="1"> +         <layout class="QVBoxLayout" name="buttonDpadDownVerticalLayout"> +          <item> +           <layout class="QHBoxLayout" name="buttonDpadDownHorizontalLayout"> +            <item> +             <widget class="QLabel" name="labelDpadDown"> +              <property name="text"> +               <string>Down:</string> +              </property> +             </widget> +            </item> +           </layout> +          </item> +          <item> +           <widget class="QPushButton" name="buttonDpadDown"> +            <property name="text"> +             <string/> +            </property> +           </widget> +          </item> +         </layout> +        </item> +        <item row="0" column="0"> +         <layout class="QVBoxLayout" name="buttonDpadLeftVerticalLayout"> +          <item> +           <layout class="QHBoxLayout" name="buttonDpadLeftHorizontalLayout"> +            <item> +             <widget class="QLabel" name="labelDpadLeft"> +              <property name="minimumSize"> +               <size> +                <width>80</width> +                <height>0</height> +               </size> +              </property> +              <property name="text"> +               <string>Left:</string> +              </property> +             </widget> +            </item> +           </layout> +          </item> +          <item> +           <widget class="QPushButton" name="buttonDpadLeft"> +            <property name="text"> +             <string/> +            </property> +           </widget> +          </item> +         </layout> +        </item> +        <item row="0" column="1"> +         <layout class="QVBoxLayout" name="buttonDpadRightVerticalLayout"> +          <item> +           <layout class="QHBoxLayout" name="buttonDpadRightHorizontalLayout"> +            <item> +             <widget class="QLabel" name="labelDpadRight"> +              <property name="minimumSize"> +               <size> +                <width>80</width> +                <height>0</height> +               </size> +              </property> +              <property name="text"> +               <string>Right:</string> +              </property> +             </widget> +            </item> +           </layout> +          </item> +          <item> +           <widget class="QPushButton" name="buttonDpadRight"> +            <property name="text"> +             <string/> +            </property> +           </widget> +          </item> +         </layout> +        </item> +       </layout> +      </widget> +     </item> +     <item row="0" column="0"> +      <widget class="QGroupBox" name="faceButtons"> +       <property name="title"> +        <string>Face Buttons</string> +       </property> +       <property name="flat"> +        <bool>false</bool> +       </property> +       <property name="checkable"> +        <bool>false</bool> +       </property> +       <layout class="QGridLayout" name="gridLayout"> +        <item row="0" column="0"> +         <layout class="QVBoxLayout" name="buttonFaceButtonsAVerticalLayout"> +          <item> +           <layout class="QHBoxLayout" name="buttonFaceButtonsAHorizontalLayout"> +            <item> +             <widget class="QLabel" name="labelA"> +              <property name="minimumSize"> +               <size> +                <width>80</width> +                <height>0</height> +               </size> +              </property> +              <property name="text"> +               <string>A:</string> +              </property> +             </widget> +            </item> +           </layout> +          </item> +          <item> +           <widget class="QPushButton" name="buttonA"> +            <property name="text"> +             <string/> +            </property> +           </widget> +          </item> +         </layout> +        </item> +        <item row="0" column="1"> +         <layout class="QVBoxLayout" name="buttonFaceButtonsBVerticalLayout"> +          <item> +           <layout class="QHBoxLayout" name="buttonFaceButtonsBHorizontalLayout"> +            <item> +             <widget class="QLabel" name="labelB"> +              <property name="minimumSize"> +               <size> +                <width>80</width> +                <height>0</height> +               </size> +              </property> +              <property name="text"> +               <string>B:</string> +              </property> +             </widget> +            </item> +           </layout> +          </item> +          <item> +           <widget class="QPushButton" name="buttonB"> +            <property name="text"> +             <string/> +            </property> +           </widget> +          </item> +         </layout> +        </item> +        <item row="1" column="0"> +         <layout class="QVBoxLayout" name="buttonFaceButtonsXVerticalLayout"> +          <item> +           <layout class="QHBoxLayout" name="buttonFaceButtonsXHorizontalLayout"> +            <item> +             <widget class="QLabel" name="labelX"> +              <property name="text"> +               <string>X:</string> +              </property> +             </widget> +            </item> +           </layout> +          </item> +          <item> +           <widget class="QPushButton" name="buttonX"> +            <property name="text"> +             <string/> +            </property> +           </widget> +          </item> +         </layout> +        </item> +        <item row="1" column="1"> +         <layout class="QVBoxLayout" name="buttonFaceButtonsYVerticalLayout"> +          <item> +           <layout class="QHBoxLayout" name="buttonFaceButtonsYHorizontalLayout"> +            <item> +             <widget class="QLabel" name="labelY"> +              <property name="text"> +               <string>Y:</string> +              </property> +             </widget> +            </item> +           </layout> +          </item> +          <item> +           <widget class="QPushButton" name="buttonY"> +            <property name="text"> +             <string/> +            </property> +           </widget> +          </item> +         </layout> +        </item> +       </layout> +      </widget> +     </item> +     <item row="5" column="0" colspan="2"> +      <widget class="QGroupBox" name="controller_color"> +       <property name="title"> +        <string>Controller Color</string> +       </property> +       <layout class="QGridLayout" name="gridLayout_10" columnstretch="0,0,0,0,0,0,0"> +        <item row="0" column="0"> +         <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="0" column="1"> +         <widget class="QLabel" name="left_body_label"> +          <property name="text"> +           <string>Left Body</string> +          </property> +         </widget> +        </item> +        <item row="0" column="6"> +         <spacer name="horizontalSpacer_3"> +          <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="QLabel" name="left_buttons_label"> +          <property name="minimumSize"> +           <size> +            <width>90</width> +            <height>0</height> +           </size> +          </property> +          <property name="text"> +           <string>Left Buttons</string> +          </property> +         </widget> +        </item> +        <item row="1" column="5"> +         <widget class="QPushButton" name="right_buttons_button"> +          <property name="sizePolicy"> +           <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> +            <horstretch>0</horstretch> +            <verstretch>0</verstretch> +           </sizepolicy> +          </property> +          <property name="minimumSize"> +           <size> +            <width>32</width> +            <height>0</height> +           </size> +          </property> +          <property name="maximumSize"> +           <size> +            <width>40</width> +            <height>16777215</height> +           </size> +          </property> +          <property name="text"> +           <string/> +          </property> +         </widget> +        </item> +        <item row="0" column="4"> +         <widget class="QLabel" name="right_body_label"> +          <property name="text"> +           <string>Right Body</string> +          </property> +         </widget> +        </item> +        <item row="1" column="4"> +         <widget class="QLabel" name="right_buttons_label"> +          <property name="minimumSize"> +           <size> +            <width>90</width> +            <height>0</height> +           </size> +          </property> +          <property name="text"> +           <string>Right Buttons</string> +          </property> +         </widget> +        </item> +        <item row="1" column="2"> +         <widget class="QPushButton" name="left_buttons_button"> +          <property name="sizePolicy"> +           <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> +            <horstretch>0</horstretch> +            <verstretch>0</verstretch> +           </sizepolicy> +          </property> +          <property name="minimumSize"> +           <size> +            <width>32</width> +            <height>0</height> +           </size> +          </property> +          <property name="maximumSize"> +           <size> +            <width>40</width> +            <height>16777215</height> +           </size> +          </property> +          <property name="text"> +           <string/> +          </property> +         </widget> +        </item> +        <item row="0" column="2"> +         <widget class="QPushButton" name="left_body_button"> +          <property name="sizePolicy"> +           <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> +            <horstretch>0</horstretch> +            <verstretch>0</verstretch> +           </sizepolicy> +          </property> +          <property name="minimumSize"> +           <size> +            <width>32</width> +            <height>0</height> +           </size> +          </property> +          <property name="maximumSize"> +           <size> +            <width>40</width> +            <height>16777215</height> +           </size> +          </property> +          <property name="text"> +           <string/> +          </property> +         </widget> +        </item> +        <item row="0" column="5"> +         <widget class="QPushButton" name="right_body_button"> +          <property name="sizePolicy"> +           <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> +            <horstretch>0</horstretch> +            <verstretch>0</verstretch> +           </sizepolicy> +          </property> +          <property name="minimumSize"> +           <size> +            <width>32</width> +            <height>0</height> +           </size> +          </property> +          <property name="maximumSize"> +           <size> +            <width>40</width> +            <height>16777215</height> +           </size> +          </property> +          <property name="text"> +           <string/> +          </property> +         </widget> +        </item> +        <item row="0" column="3"> +         <spacer name="horizontalSpacer_4"> +          <property name="orientation"> +           <enum>Qt::Horizontal</enum> +          </property> +          <property name="sizeType"> +           <enum>QSizePolicy::Fixed</enum> +          </property> +          <property name="sizeHint" stdset="0"> +           <size> +            <width>20</width> +            <height>20</height> +           </size> +          </property> +         </spacer> +        </item> +       </layout> +      </widget> +     </item> +     <item row="1" column="0"> +      <widget class="QGroupBox" name="LStick"> +       <property name="title"> +        <string>Left Stick</string> +       </property> +       <property name="flat"> +        <bool>false</bool> +       </property> +       <property name="checkable"> +        <bool>false</bool> +       </property> +       <layout class="QGridLayout" name="gridLayout_4"> +        <item row="1" column="1"> +         <layout class="QVBoxLayout" name="buttonLStickUpVerticalLayout"> +          <item> +           <layout class="QHBoxLayout" name="buttonLStickUpHorizontalLayout"> +            <item> +             <widget class="QLabel" name="labelLStickUp"> +              <property name="text"> +               <string>Up:</string> +              </property> +             </widget> +            </item> +           </layout> +          </item> +          <item> +           <widget class="QPushButton" name="buttonLStickUp"> +            <property name="text"> +             <string/> +            </property> +           </widget> +          </item> +         </layout> +        </item> +        <item row="0" column="2"> +         <layout class="QVBoxLayout" name="buttonLStickRightVerticalLayout"> +          <item> +           <layout class="QHBoxLayout" name="buttonLStickRightHorizontalLayout"> +            <item> +             <widget class="QLabel" name="labelLStickRight"> +              <property name="text"> +               <string>Right:</string> +              </property> +             </widget> +            </item> +           </layout> +          </item> +          <item> +           <widget class="QPushButton" name="buttonLStickRight"> +            <property name="text"> +             <string/> +            </property> +           </widget> +          </item> +         </layout> +        </item> +        <item row="4" column="1" colspan="2"> +         <widget class="QPushButton" name="buttonLStickAnalog"> +          <property name="text"> +           <string>Set Analog Stick</string> +          </property> +         </widget> +        </item> +        <item row="0" column="1"> +         <layout class="QVBoxLayout" name="buttonLStickLeftVerticalLayout"> +          <item> +           <layout class="QHBoxLayout" name="buttonLStickLeftHorizontalLayout_2"> +            <item> +             <widget class="QLabel" name="labelLStickLeft"> +              <property name="text"> +               <string>Left:</string> +              </property> +             </widget> +            </item> +           </layout> +          </item> +          <item> +           <widget class="QPushButton" name="buttonLStickLeft"> +            <property name="text"> +             <string/> +            </property> +           </widget> +          </item> +         </layout> +        </item> +        <item row="1" column="2"> +         <layout class="QVBoxLayout" name="buttonLStickDownVerticalLayout"> +          <item> +           <layout class="QHBoxLayout" name="buttonLStickDownHorizontalLayout"> +            <item> +             <widget class="QLabel" name="labelLStickDown"> +              <property name="text"> +               <string>Down:</string> +              </property> +             </widget> +            </item> +           </layout> +          </item> +          <item> +           <widget class="QPushButton" name="buttonLStickDown"> +            <property name="text"> +             <string/> +            </property> +           </widget> +          </item> +         </layout> +        </item> +        <item row="3" column="2"> +         <layout class="QVBoxLayout" name="buttonLStickModVerticalLayout"> +          <item> +           <layout class="QHBoxLayout" name="buttonLStickModHorizontalLayout"> +            <item> +             <widget class="QLabel" name="labelLStickMod"> +              <property name="text"> +               <string>Modifier:</string> +              </property> +             </widget> +            </item> +           </layout> +          </item> +          <item> +           <widget class="QPushButton" name="buttonLStickMod"> +            <property name="text"> +             <string/> +            </property> +           </widget> +          </item> +         </layout> +        </item> +        <item row="3" column="1"> +         <layout class="QVBoxLayout" name="buttonLStickPressedVerticalLayout" stretch="0,0"> +          <item> +           <layout class="QHBoxLayout" name="buttonLStickPressedHorizontalLayout"> +            <item> +             <widget class="QLabel" name="labelLStickPressed"> +              <property name="text"> +               <string>Pressed:</string> +              </property> +             </widget> +            </item> +           </layout> +          </item> +          <item> +           <widget class="QPushButton" name="buttonLStick"> +            <property name="text"> +             <string/> +            </property> +           </widget> +          </item> +         </layout> +        </item> +       </layout> +      </widget> +     </item> +     <item row="3" column="0"> +      <widget class="QGroupBox" name="shoulderButtons"> +       <property name="title"> +        <string>Shoulder Buttons</string> +       </property> +       <property name="flat"> +        <bool>false</bool> +       </property> +       <property name="checkable"> +        <bool>false</bool> +       </property> +       <layout class="QGridLayout" name="gridLayout_3"> +        <item row="3" column="0"> +         <layout class="QVBoxLayout" name="buttonShoulderButtonsSLVerticalLayout"> +          <item> +           <layout class="QHBoxLayout" name="buttonShoulderButtonsSLHorizontalLayout"> +            <item> +             <widget class="QLabel" name="labelSL"> +              <property name="text"> +               <string>SL:</string> +              </property> +             </widget> +            </item> +           </layout> +          </item> +          <item> +           <widget class="QPushButton" name="buttonSL"> +            <property name="text"> +             <string/> +            </property> +           </widget> +          </item> +         </layout> +        </item> +        <item row="2" column="1"> +         <layout class="QVBoxLayout" name="buttonShoulderButtonsZRVerticalLayout"> +          <item> +           <layout class="QHBoxLayout" name="buttonShoulderButtonsZRHorizontalLayout"> +            <item> +             <widget class="QLabel" name="labelZR"> +              <property name="text"> +               <string>ZR:</string> +              </property> +             </widget> +            </item> +           </layout> +          </item> +          <item> +           <widget class="QPushButton" name="buttonZR"> +            <property name="text"> +             <string/> +            </property> +           </widget> +          </item> +         </layout> +        </item> +        <item row="3" column="1"> +         <layout class="QVBoxLayout" name="buttonShoulderButtonsSRVerticalLayout"> +          <item> +           <layout class="QHBoxLayout" name="buttonShoulderButtonsSRHorizontalLayout"> +            <item> +             <widget class="QLabel" name="labelSR"> +              <property name="text"> +               <string>SR:</string> +              </property> +             </widget> +            </item> +           </layout> +          </item> +          <item> +           <widget class="QPushButton" name="buttonSR"> +            <property name="text"> +             <string/> +            </property> +           </widget> +          </item> +         </layout> +        </item> +        <item row="0" column="1"> +         <layout class="QVBoxLayout" name="buttonShoulderButtonsZLVerticalLayout"> +          <item> +           <layout class="QHBoxLayout" name="buttonShoulderButtonsZLHorizontalLayout"> +            <item> +             <widget class="QLabel" name="labelZL"> +              <property name="text"> +               <string>ZL:</string> +              </property> +             </widget> +            </item> +           </layout> +          </item> +          <item> +           <widget class="QPushButton" name="buttonZL"> +            <property name="text"> +             <string/> +            </property> +           </widget> +          </item> +         </layout> +        </item> +        <item row="0" column="0"> +         <layout class="QVBoxLayout" name="buttonShoulderButtonsLVerticalLayout"> +          <item> +           <layout class="QHBoxLayout" name="buttonShoulderButtonsLHorizontalLayout"> +            <item> +             <widget class="QLabel" name="labelL"> +              <property name="text"> +               <string>L:</string> +              </property> +             </widget> +            </item> +           </layout> +          </item> +          <item> +           <widget class="QPushButton" name="buttonL"> +            <property name="text"> +             <string/> +            </property> +           </widget> +          </item> +         </layout> +        </item> +        <item row="2" column="0"> +         <layout class="QVBoxLayout" name="buttonShoulderButtonsRVerticalLayout"> +          <item> +           <layout class="QHBoxLayout" name="buttonShoulderButtonsRHorizontalLayout"> +            <item> +             <widget class="QLabel" name="labelR"> +              <property name="text"> +               <string>R:</string> +              </property> +             </widget> +            </item> +           </layout> +          </item> +          <item> +           <widget class="QPushButton" name="buttonR"> +            <property name="text"> +             <string/> +            </property> +           </widget> +          </item> +         </layout> +        </item> +       </layout> +      </widget> +     </item> +     <item row="3" column="1"> +      <widget class="QGroupBox" name="misc"> +       <property name="title"> +        <string>Misc.</string> +       </property> +       <property name="flat"> +        <bool>false</bool> +       </property> +       <property name="checkable"> +        <bool>false</bool> +       </property> +       <layout class="QGridLayout" name="gridLayout_6"> +        <item row="1" column="0"> +         <layout class="QVBoxLayout" name="buttonMiscMinusVerticalLayout"> +          <item> +           <layout class="QHBoxLayout" name="buttonMiscMinusHorizontalLayout"> +            <item> +             <widget class="QLabel" name="labelMinus"> +              <property name="text"> +               <string>Minus:</string> +              </property> +             </widget> +            </item> +           </layout> +          </item> +          <item> +           <widget class="QPushButton" name="buttonMinus"> +            <property name="text"> +             <string/> +            </property> +           </widget> +          </item> +         </layout> +        </item> +        <item row="3" column="1"> +         <spacer name="verticalSpacer_2"> +          <property name="orientation"> +           <enum>Qt::Vertical</enum> +          </property> +          <property name="sizeHint" stdset="0"> +           <size> +            <width>20</width> +            <height>40</height> +           </size> +          </property> +         </spacer> +        </item> +        <item row="0" column="0"> +         <layout class="QVBoxLayout" name="buttonMiscPlusVerticalLayout"> +          <item> +           <layout class="QHBoxLayout" name="buttonMiscPlusHorizontalLayout"> +            <item> +             <widget class="QLabel" name="labelPlus"> +              <property name="text"> +               <string>Plus:</string> +              </property> +             </widget> +            </item> +           </layout> +          </item> +          <item> +           <widget class="QPushButton" name="buttonPlus"> +            <property name="text"> +             <string/> +            </property> +           </widget> +          </item> +         </layout> +        </item> +        <item row="0" column="1"> +         <layout class="QVBoxLayout" name="buttonMiscHomeVerticalLayout"> +          <item> +           <layout class="QHBoxLayout" name="buttonMiscHomeHorizontalLayout"> +            <item> +             <widget class="QLabel" name="labelHome"> +              <property name="text"> +               <string>Home:</string> +              </property> +             </widget> +            </item> +           </layout> +          </item> +          <item> +           <widget class="QPushButton" name="buttonHome"> +            <property name="text"> +             <string/> +            </property> +           </widget> +          </item> +         </layout> +        </item> +        <item row="1" column="1"> +         <layout class="QVBoxLayout" name="buttonMiscScrCapVerticalLayout"> +          <item> +           <layout class="QHBoxLayout" name="buttonMiscScrCapHorizontalLayout"> +            <item> +             <widget class="QLabel" name="labelScreenshot"> +              <property name="text"> +               <string>Screen Capture:</string> +              </property> +              <property name="wordWrap"> +               <bool>false</bool> +              </property> +             </widget> +            </item> +           </layout> +          </item> +          <item> +           <widget class="QPushButton" name="buttonScreenshot"> +            <property name="sizePolicy"> +             <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> +              <horstretch>0</horstretch> +              <verstretch>0</verstretch> +             </sizepolicy> +            </property> +            <property name="maximumSize"> +             <size> +              <width>80</width> +              <height>16777215</height> +             </size> +            </property> +            <property name="text"> +             <string/> +            </property> +           </widget> +          </item> +         </layout> +        </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> +   <item> +    <layout class="QHBoxLayout" name="horizontalLayout"/> +   </item> +   <item> +    <layout class="QHBoxLayout" name="horizontalLayout_2"> +     <item> +      <widget class="QPushButton" name="buttonClearAll"> +       <property name="sizePolicy"> +        <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> +         <horstretch>0</horstretch> +         <verstretch>0</verstretch> +        </sizepolicy> +       </property> +       <property name="sizeIncrement"> +        <size> +         <width>0</width> +         <height>0</height> +        </size> +       </property> +       <property name="baseSize"> +        <size> +         <width>0</width> +         <height>0</height> +        </size> +       </property> +       <property name="layoutDirection"> +        <enum>Qt::LeftToRight</enum> +       </property> +       <property name="text"> +        <string>Clear All</string> +       </property> +      </widget> +     </item> +     <item> +      <widget class="QPushButton" name="buttonRestoreDefaults"> +       <property name="sizePolicy"> +        <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> +         <horstretch>0</horstretch> +         <verstretch>0</verstretch> +        </sizepolicy> +       </property> +       <property name="sizeIncrement"> +        <size> +         <width>0</width> +         <height>0</height> +        </size> +       </property> +       <property name="baseSize"> +        <size> +         <width>0</width> +         <height>0</height> +        </size> +       </property> +       <property name="layoutDirection"> +        <enum>Qt::LeftToRight</enum> +       </property> +       <property name="text"> +        <string>Restore Defaults</string> +       </property> +      </widget> +     </item> +     <item> +      <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> +      <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>ConfigureInputPlayer</receiver> +   <slot>accept()</slot> +   <hints> +    <hint type="sourcelabel"> +     <x>371</x> +     <y>730</y> +    </hint> +    <hint type="destinationlabel"> +     <x>229</x> +     <y>375</y> +    </hint> +   </hints> +  </connection> +  <connection> +   <sender>buttonBox</sender> +   <signal>rejected()</signal> +   <receiver>ConfigureInputPlayer</receiver> +   <slot>reject()</slot> +   <hints> +    <hint type="sourcelabel"> +     <x>371</x> +     <y>730</y> +    </hint> +    <hint type="destinationlabel"> +     <x>229</x> +     <y>375</y> +    </hint> +   </hints> +  </connection> + </connections> +</ui> diff --git a/src/yuzu/configuration/configure_input_simple.cpp b/src/yuzu/configuration/configure_input_simple.cpp new file mode 100644 index 000000000..07d71e9d1 --- /dev/null +++ b/src/yuzu/configuration/configure_input_simple.cpp @@ -0,0 +1,137 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <array> +#include <tuple> + +#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<const char*, void (*)(), void (*)(ConfigureInputSimple*)>; + +constexpr std::array<InputProfile, 3> INPUT_PROFILES{{ +    {QT_TR_NOOP("Single Player - Handheld - Undocked"), HandheldOnProfileSelect, +     [](ConfigureInputSimple* caller) { +         CallConfigureDialog<ConfigureInputPlayer>(caller, HANDHELD_INDEX, false); +     }}, +    {QT_TR_NOOP("Single Player - Dual Joycons - Docked"), DualJoyconsDockedOnProfileSelect, +     [](ConfigureInputSimple* caller) { +         CallConfigureDialog<ConfigureInputPlayer>(caller, 1, false); +     }}, +    {QT_TR_NOOP("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) { +        const QString label = tr(std::get<0>(profile)); +        ui->profile_combobox->addItem(label, label); +    } + +    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_mouse_advanced.cpp b/src/yuzu/configuration/configure_mouse_advanced.cpp new file mode 100644 index 000000000..ef857035e --- /dev/null +++ b/src/yuzu/configuration/configure_mouse_advanced.cpp @@ -0,0 +1,213 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <memory> + +#include <QKeyEvent> +#include <QMenu> +#include <QTimer> + +#include "common/assert.h" +#include "common/param_package.h" +#include "input_common/main.h" +#include "ui_configure_mouse_advanced.h" +#include "yuzu/configuration/config.h" +#include "yuzu/configuration/configure_mouse_advanced.h" + +static QString GetKeyName(int key_code) { +    switch (key_code) { +    case Qt::Key_Shift: +        return QObject::tr("Shift"); +    case Qt::Key_Control: +        return QObject::tr("Ctrl"); +    case Qt::Key_Alt: +        return QObject::tr("Alt"); +    case Qt::Key_Meta: +        return ""; +    default: +        return QKeySequence(key_code).toString(); +    } +} + +static QString ButtonToText(const Common::ParamPackage& param) { +    if (!param.Has("engine")) { +        return QObject::tr("[not set]"); +    } else if (param.Get("engine", "") == "keyboard") { +        return GetKeyName(param.Get("code", 0)); +    } else if (param.Get("engine", "") == "sdl") { +        if (param.Has("hat")) { +            return QString(QObject::tr("Hat %1 %2")) +                .arg(param.Get("hat", "").c_str(), param.Get("direction", "").c_str()); +        } +        if (param.Has("axis")) { +            return QString(QObject::tr("Axis %1%2")) +                .arg(param.Get("axis", "").c_str(), param.Get("direction", "").c_str()); +        } +        if (param.Has("button")) { +            return QString(QObject::tr("Button %1")).arg(param.Get("button", "").c_str()); +        } +        return QString(); +    } else { +        return QObject::tr("[unknown]"); +    } +} + +ConfigureMouseAdvanced::ConfigureMouseAdvanced(QWidget* parent) +    : QDialog(parent), ui(std::make_unique<Ui::ConfigureMouseAdvanced>()), +      timeout_timer(std::make_unique<QTimer>()), poll_timer(std::make_unique<QTimer>()) { +    ui->setupUi(this); +    setFocusPolicy(Qt::ClickFocus); + +    button_map = { +        ui->left_button, ui->right_button, ui->middle_button, ui->forward_button, ui->back_button, +    }; + +    for (int button_id = 0; button_id < Settings::NativeMouseButton::NumMouseButtons; button_id++) { +        if (!button_map[button_id]) +            continue; +        button_map[button_id]->setContextMenuPolicy(Qt::CustomContextMenu); +        connect(button_map[button_id], &QPushButton::released, [=]() { +            handleClick( +                button_map[button_id], +                [=](const Common::ParamPackage& params) { buttons_param[button_id] = params; }, +                InputCommon::Polling::DeviceType::Button); +        }); +        connect(button_map[button_id], &QPushButton::customContextMenuRequested, +                [=](const QPoint& menu_location) { +                    QMenu context_menu; +                    context_menu.addAction(tr("Clear"), [&] { +                        buttons_param[button_id].Clear(); +                        button_map[button_id]->setText(tr("[not set]")); +                    }); +                    context_menu.addAction(tr("Restore Default"), [&] { +                        buttons_param[button_id] = +                            Common::ParamPackage{InputCommon::GenerateKeyboardParam( +                                Config::default_mouse_buttons[button_id])}; +                        button_map[button_id]->setText(ButtonToText(buttons_param[button_id])); +                    }); +                    context_menu.exec(button_map[button_id]->mapToGlobal(menu_location)); +                }); +    } + +    connect(ui->buttonClearAll, &QPushButton::released, [this] { ClearAll(); }); +    connect(ui->buttonRestoreDefaults, &QPushButton::released, [this]() { restoreDefaults(); }); + +    timeout_timer->setSingleShot(true); +    connect(timeout_timer.get(), &QTimer::timeout, [this]() { setPollingResult({}, true); }); + +    connect(poll_timer.get(), &QTimer::timeout, [this]() { +        Common::ParamPackage params; +        for (auto& poller : device_pollers) { +            params = poller->GetNextInput(); +            if (params.Has("engine")) { +                setPollingResult(params, false); +                return; +            } +        } +    }); + +    loadConfiguration(); +    resize(0, 0); +} + +ConfigureMouseAdvanced::~ConfigureMouseAdvanced() = default; + +void ConfigureMouseAdvanced::applyConfiguration() { +    std::transform(buttons_param.begin(), buttons_param.end(), +                   Settings::values.mouse_buttons.begin(), +                   [](const Common::ParamPackage& param) { return param.Serialize(); }); +} + +void ConfigureMouseAdvanced::loadConfiguration() { +    std::transform(Settings::values.mouse_buttons.begin(), Settings::values.mouse_buttons.end(), +                   buttons_param.begin(), +                   [](const std::string& str) { return Common::ParamPackage(str); }); +    updateButtonLabels(); +} + +void ConfigureMouseAdvanced::restoreDefaults() { +    for (int button_id = 0; button_id < Settings::NativeMouseButton::NumMouseButtons; button_id++) { +        buttons_param[button_id] = Common::ParamPackage{ +            InputCommon::GenerateKeyboardParam(Config::default_mouse_buttons[button_id])}; +    } + +    updateButtonLabels(); +} + +void ConfigureMouseAdvanced::ClearAll() { +    for (int i = 0; i < Settings::NativeMouseButton::NumMouseButtons; ++i) { +        if (button_map[i] && button_map[i]->isEnabled()) +            buttons_param[i].Clear(); +    } + +    updateButtonLabels(); +} + +void ConfigureMouseAdvanced::updateButtonLabels() { +    for (int button = 0; button < Settings::NativeMouseButton::NumMouseButtons; button++) { +        button_map[button]->setText(ButtonToText(buttons_param[button])); +    } +} + +void ConfigureMouseAdvanced::handleClick( +    QPushButton* button, std::function<void(const Common::ParamPackage&)> new_input_setter, +    InputCommon::Polling::DeviceType type) { +    button->setText(tr("[press key]")); +    button->setFocus(); + +    const auto iter = std::find(button_map.begin(), button_map.end(), button); +    ASSERT(iter != button_map.end()); +    const auto index = std::distance(button_map.begin(), iter); +    ASSERT(index < Settings::NativeButton::NumButtons && index >= 0); + +    input_setter = new_input_setter; + +    device_pollers = InputCommon::Polling::GetPollers(type); + +    // Keyboard keys can only be used as button devices +    want_keyboard_keys = type == InputCommon::Polling::DeviceType::Button; + +    for (auto& poller : device_pollers) { +        poller->Start(); +    } + +    grabKeyboard(); +    grabMouse(); +    timeout_timer->start(5000); // Cancel after 5 seconds +    poll_timer->start(200);     // Check for new inputs every 200ms +} + +void ConfigureMouseAdvanced::setPollingResult(const Common::ParamPackage& params, bool abort) { +    releaseKeyboard(); +    releaseMouse(); +    timeout_timer->stop(); +    poll_timer->stop(); +    for (auto& poller : device_pollers) { +        poller->Stop(); +    } + +    if (!abort) { +        (*input_setter)(params); +    } + +    updateButtonLabels(); +    input_setter = std::nullopt; +} + +void ConfigureMouseAdvanced::keyPressEvent(QKeyEvent* event) { +    if (!input_setter || !event) +        return; + +    if (event->key() != Qt::Key_Escape) { +        if (want_keyboard_keys) { +            setPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())}, +                             false); +        } else { +            // Escape key wasn't pressed and we don't want any keyboard keys, so don't stop polling +            return; +        } +    } +    setPollingResult({}, true); +} diff --git a/src/yuzu/configuration/configure_mouse_advanced.h b/src/yuzu/configuration/configure_mouse_advanced.h new file mode 100644 index 000000000..e04da4bf2 --- /dev/null +++ b/src/yuzu/configuration/configure_mouse_advanced.h @@ -0,0 +1,68 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <optional> +#include <QDialog> + +#include "core/settings.h" + +class QCheckBox; +class QPushButton; +class QTimer; + +namespace Ui { +class ConfigureMouseAdvanced; +} + +class ConfigureMouseAdvanced : public QDialog { +    Q_OBJECT + +public: +    explicit ConfigureMouseAdvanced(QWidget* parent); +    ~ConfigureMouseAdvanced() override; + +    void applyConfiguration(); + +private: +    /// Load configuration settings. +    void loadConfiguration(); +    /// Restore all buttons to their default values. +    void restoreDefaults(); +    /// Clear all input configuration +    void ClearAll(); + +    /// Update UI to reflect current configuration. +    void updateButtonLabels(); + +    /// Called when the button was pressed. +    void handleClick(QPushButton* button, +                     std::function<void(const Common::ParamPackage&)> new_input_setter, +                     InputCommon::Polling::DeviceType type); + +    /// Finish polling and configure input using the input_setter +    void setPollingResult(const Common::ParamPackage& params, bool abort); + +    /// Handle key press events. +    void keyPressEvent(QKeyEvent* event) override; + +    std::unique_ptr<Ui::ConfigureMouseAdvanced> ui; + +    /// This will be the the setting function when an input is awaiting configuration. +    std::optional<std::function<void(const Common::ParamPackage&)>> input_setter; + +    std::array<QPushButton*, Settings::NativeMouseButton::NumMouseButtons> button_map; +    std::array<Common::ParamPackage, Settings::NativeMouseButton::NumMouseButtons> buttons_param; + +    std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> device_pollers; + +    std::unique_ptr<QTimer> timeout_timer; +    std::unique_ptr<QTimer> poll_timer; + +    /// A flag to indicate if keyboard keys are okay when configuring an input. If this is false, +    /// keyboard events are ignored. +    bool want_keyboard_keys = false; +}; diff --git a/src/yuzu/configuration/configure_mouse_advanced.ui b/src/yuzu/configuration/configure_mouse_advanced.ui new file mode 100644 index 000000000..08245ecf0 --- /dev/null +++ b/src/yuzu/configuration/configure_mouse_advanced.ui @@ -0,0 +1,261 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigureMouseAdvanced</class> + <widget class="QDialog" name="ConfigureMouseAdvanced"> +  <property name="geometry"> +   <rect> +    <x>0</x> +    <y>0</y> +    <width>250</width> +    <height>261</height> +   </rect> +  </property> +  <property name="windowTitle"> +   <string>Configure Mouse</string> +  </property> +  <layout class="QVBoxLayout" name="verticalLayout"> +   <item> +    <widget class="QGroupBox" name="gridGroupBox"> +     <property name="title"> +      <string>Mouse Buttons</string> +     </property> +     <layout class="QGridLayout" name="gridLayout"> +      <item row="0" column="4"> +       <spacer name="horizontalSpacer_2"> +        <property name="orientation"> +         <enum>Qt::Horizontal</enum> +        </property> +        <property name="sizeType"> +         <enum>QSizePolicy::Fixed</enum> +        </property> +        <property name="sizeHint" stdset="0"> +         <size> +          <width>20</width> +          <height>20</height> +         </size> +        </property> +       </spacer> +      </item> +      <item row="0" column="3"> +       <layout class="QVBoxLayout" name="verticalLayout_4"> +        <item> +         <layout class="QHBoxLayout" name="horizontalLayout_3"> +          <item> +           <widget class="QLabel" name="label_3"> +            <property name="text"> +             <string>Right:</string> +            </property> +           </widget> +          </item> +         </layout> +        </item> +        <item> +         <widget class="QPushButton" name="right_button"> +          <property name="minimumSize"> +           <size> +            <width>75</width> +            <height>0</height> +           </size> +          </property> +          <property name="text"> +           <string/> +          </property> +         </widget> +        </item> +       </layout> +      </item> +      <item row="0" column="0"> +       <spacer name="horizontalSpacer"> +        <property name="orientation"> +         <enum>Qt::Horizontal</enum> +        </property> +        <property name="sizeType"> +         <enum>QSizePolicy::Fixed</enum> +        </property> +        <property name="sizeHint" stdset="0"> +         <size> +          <width>20</width> +          <height>20</height> +         </size> +        </property> +       </spacer> +      </item> +      <item row="2" column="1"> +       <layout class="QVBoxLayout" name="verticalLayout_3"> +        <item> +         <layout class="QHBoxLayout" name="horizontalLayout_2"> +          <item> +           <widget class="QLabel" name="label_2"> +            <property name="text"> +             <string>Middle:</string> +            </property> +           </widget> +          </item> +         </layout> +        </item> +        <item> +         <widget class="QPushButton" name="middle_button"> +          <property name="text"> +           <string/> +          </property> +         </widget> +        </item> +       </layout> +      </item> +      <item row="3" column="1"> +       <layout class="QVBoxLayout" name="verticalLayout_5"> +        <item> +         <layout class="QHBoxLayout" name="horizontalLayout_4"> +          <item> +           <widget class="QLabel" name="label_4"> +            <property name="minimumSize"> +             <size> +              <width>54</width> +              <height>0</height> +             </size> +            </property> +            <property name="text"> +             <string>Back:</string> +            </property> +           </widget> +          </item> +         </layout> +        </item> +        <item> +         <widget class="QPushButton" name="back_button"> +          <property name="text"> +           <string/> +          </property> +         </widget> +        </item> +       </layout> +      </item> +      <item row="0" column="1"> +       <layout class="QVBoxLayout" name="verticalLayout_2"> +        <item> +         <layout class="QHBoxLayout" name="horizontalLayout"> +          <item> +           <widget class="QLabel" name="label"> +            <property name="text"> +             <string>Left:</string> +            </property> +           </widget> +          </item> +         </layout> +        </item> +        <item> +         <widget class="QPushButton" name="left_button"> +          <property name="minimumSize"> +           <size> +            <width>75</width> +            <height>0</height> +           </size> +          </property> +          <property name="text"> +           <string/> +          </property> +         </widget> +        </item> +       </layout> +      </item> +      <item row="3" column="3"> +       <layout class="QVBoxLayout" name="verticalLayout_6"> +        <item> +         <layout class="QHBoxLayout" name="horizontalLayout_5"> +          <item> +           <widget class="QLabel" name="label_5"> +            <property name="text"> +             <string>Forward:</string> +            </property> +           </widget> +          </item> +         </layout> +        </item> +        <item> +         <widget class="QPushButton" name="forward_button"> +          <property name="text"> +           <string/> +          </property> +         </widget> +        </item> +       </layout> +      </item> +     </layout> +    </widget> +   </item> +   <item> +    <layout class="QHBoxLayout" name="horizontalLayout_6"> +     <item> +      <widget class="QPushButton" name="buttonClearAll"> +       <property name="text"> +        <string>Clear All</string> +       </property> +      </widget> +     </item> +     <item> +      <widget class="QPushButton" name="buttonRestoreDefaults"> +       <property name="text"> +        <string>Restore Defaults</string> +       </property> +      </widget> +     </item> +     <item> +      <spacer name="horizontalSpacer_3"> +       <property name="orientation"> +        <enum>Qt::Horizontal</enum> +       </property> +       <property name="sizeHint" stdset="0"> +        <size> +         <width>40</width> +         <height>20</height> +        </size> +       </property> +      </spacer> +     </item> +    </layout> +   </item> +   <item> +    <widget class="QDialogButtonBox" name="buttonBox"> +     <property name="standardButtons"> +      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> +     </property> +    </widget> +   </item> +  </layout> + </widget> + <resources/> + <connections> +  <connection> +   <sender>buttonBox</sender> +   <signal>accepted()</signal> +   <receiver>ConfigureMouseAdvanced</receiver> +   <slot>accept()</slot> +   <hints> +    <hint type="sourcelabel"> +     <x>124</x> +     <y>266</y> +    </hint> +    <hint type="destinationlabel"> +     <x>124</x> +     <y>143</y> +    </hint> +   </hints> +  </connection> +  <connection> +   <sender>buttonBox</sender> +   <signal>rejected()</signal> +   <receiver>ConfigureMouseAdvanced</receiver> +   <slot>reject()</slot> +   <hints> +    <hint type="sourcelabel"> +     <x>124</x> +     <y>266</y> +    </hint> +    <hint type="destinationlabel"> +     <x>124</x> +     <y>143</y> +    </hint> +   </hints> +  </connection> + </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..e13d2eac8 --- /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, tr("Patch Name")); +    item_model->setHeaderData(1, Qt::Horizontal, tr("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)); + +        FileSys::NACP nacp; +        if (loader->ReadControlData(nacp) == Loader::ResultStatus::Success) +            ui->display_developer->setText(QString::fromStdString(nacp.GetDeveloperName())); + +        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(), static_cast<u32>(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(), static_cast<u32>(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/configuration/configure_system.cpp b/src/yuzu/configuration/configure_system.cpp index 4b34c1e28..ab5d46492 100644 --- a/src/yuzu/configuration/configure_system.cpp +++ b/src/yuzu/configuration/configure_system.cpp @@ -6,20 +6,20 @@  #include <QFileDialog>  #include <QGraphicsItem>  #include <QGraphicsScene> -#include <QInputDialog> +#include <QHeaderView>  #include <QMessageBox>  #include <QStandardItemModel>  #include <QTreeView>  #include <QVBoxLayout> -#include "common/common_paths.h" -#include "common/logging/backend.h" +#include "common/assert.h" +#include "common/file_util.h"  #include "common/string_util.h"  #include "core/core.h"  #include "core/hle/service/acc/profile_manager.h"  #include "core/settings.h"  #include "ui_configure_system.h"  #include "yuzu/configuration/configure_system.h" -#include "yuzu/main.h" +#include "yuzu/util/limitable_input_dialog.h"  namespace {  constexpr std::array<int, 12> days_in_month = {{ @@ -78,11 +78,17 @@ QPixmap GetIcon(Service::Account::UUID uuid) {      if (!icon) {          icon.fill(Qt::black); -        icon.loadFromData(backup_jpeg.data(), backup_jpeg.size()); +        icon.loadFromData(backup_jpeg.data(), static_cast<u32>(backup_jpeg.size()));      }      return icon.scaled(64, 64, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);  } + +QString GetProfileUsernameFromUser(QWidget* parent, const QString& description_text) { +    return LimitableInputDialog::GetText(parent, ConfigureSystem::tr("Enter Username"), +                                         description_text, 1, +                                         static_cast<int>(Service::Account::profile_username_size)); +}  } // Anonymous namespace  ConfigureSystem::ConfigureSystem(QWidget* parent) @@ -131,6 +137,12 @@ ConfigureSystem::ConfigureSystem(QWidget* parent)      connect(ui->pm_remove, &QPushButton::pressed, this, &ConfigureSystem::DeleteUser);      connect(ui->pm_set_image, &QPushButton::pressed, this, &ConfigureSystem::SetUserImage); +    connect(ui->rng_seed_checkbox, &QCheckBox::stateChanged, this, [this](bool checked) { +        ui->rng_seed_edit->setEnabled(checked); +        if (!checked) +            ui->rng_seed_edit->setText(QStringLiteral("00000000")); +    }); +      scene = new QGraphicsScene;      ui->current_user_icon->setScene(scene); @@ -149,6 +161,13 @@ void ConfigureSystem::setConfiguration() {      PopulateUserList();      UpdateCurrentUser(); + +    ui->rng_seed_checkbox->setChecked(Settings::values.rng_seed.has_value()); +    ui->rng_seed_edit->setEnabled(Settings::values.rng_seed.has_value()); + +    const auto rng_seed = +        QString("%1").arg(Settings::values.rng_seed.value_or(0), 8, 16, QLatin1Char{'0'}).toUpper(); +    ui->rng_seed_edit->setText(rng_seed);  }  void ConfigureSystem::PopulateUserList() { @@ -173,7 +192,7 @@ void ConfigureSystem::UpdateCurrentUser() {      ui->pm_add->setEnabled(profile_manager->GetUserCount() < Service::Account::MAX_USERS);      const auto& current_user = profile_manager->GetUser(Settings::values.current_user); -    ASSERT(current_user != std::nullopt); +    ASSERT(current_user);      const auto username = GetAccountUsername(*profile_manager, *current_user);      scene->clear(); @@ -189,6 +208,12 @@ void ConfigureSystem::applyConfiguration() {          return;      Settings::values.language_index = ui->combo_language->currentIndex(); + +    if (ui->rng_seed_checkbox->isChecked()) +        Settings::values.rng_seed = ui->rng_seed_edit->text().toULongLong(nullptr, 16); +    else +        Settings::values.rng_seed = std::nullopt; +      Settings::Apply();  } @@ -234,7 +259,7 @@ void ConfigureSystem::RefreshConsoleID() {  void ConfigureSystem::SelectUser(const QModelIndex& index) {      Settings::values.current_user = -        std::clamp<std::size_t>(index.row(), 0, profile_manager->GetUserCount() - 1); +        std::clamp<s32>(index.row(), 0, static_cast<s32>(profile_manager->GetUserCount() - 1));      UpdateCurrentUser(); @@ -244,15 +269,13 @@ void ConfigureSystem::SelectUser(const QModelIndex& index) {  }  void ConfigureSystem::AddUser() { -    const auto uuid = Service::Account::UUID::Generate(); - -    bool ok = false;      const auto username = -        QInputDialog::getText(this, tr("Enter Username"), tr("Enter a username for the new user:"), -                              QLineEdit::Normal, QString(), &ok); -    if (!ok) +        GetProfileUsernameFromUser(this, tr("Enter a username for the new user:")); +    if (username.isEmpty()) {          return; +    } +    const auto uuid = Service::Account::UUID::Generate();      profile_manager->CreateNewUser(uuid, username.toStdString());      item_model->appendRow(new QStandardItem{GetIcon(uuid), FormatUserEntryText(username, uuid)}); @@ -261,29 +284,20 @@ void ConfigureSystem::AddUser() {  void ConfigureSystem::RenameUser() {      const auto user = tree_view->currentIndex().row();      const auto uuid = profile_manager->GetUser(user); -    ASSERT(uuid != std::nullopt); +    ASSERT(uuid);      Service::Account::ProfileBase profile;      if (!profile_manager->GetProfileBase(*uuid, profile))          return; -    bool ok = false; -    const auto old_username = GetAccountUsername(*profile_manager, *uuid); -    const auto new_username = -        QInputDialog::getText(this, tr("Enter Username"), tr("Enter a new username:"), -                              QLineEdit::Normal, old_username, &ok); - -    if (!ok) +    const auto new_username = GetProfileUsernameFromUser(this, tr("Enter a new username:")); +    if (new_username.isEmpty()) {          return; +    } -    std::fill(profile.username.begin(), profile.username.end(), '\0');      const auto username_std = new_username.toStdString(); -    if (username_std.size() > profile.username.size()) { -        std::copy_n(username_std.begin(), std::min(profile.username.size(), username_std.size()), -                    profile.username.begin()); -    } else { -        std::copy(username_std.begin(), username_std.end(), profile.username.begin()); -    } +    std::fill(profile.username.begin(), profile.username.end(), '\0'); +    std::copy(username_std.begin(), username_std.end(), profile.username.begin());      profile_manager->SetProfileBase(*uuid, profile); @@ -297,7 +311,7 @@ void ConfigureSystem::RenameUser() {  void ConfigureSystem::DeleteUser() {      const auto index = tree_view->currentIndex().row();      const auto uuid = profile_manager->GetUser(index); -    ASSERT(uuid != std::nullopt); +    ASSERT(uuid);      const auto username = GetAccountUsername(*profile_manager, *uuid);      const auto confirm = QMessageBox::question( @@ -324,7 +338,7 @@ void ConfigureSystem::DeleteUser() {  void ConfigureSystem::SetUserImage() {      const auto index = tree_view->currentIndex().row();      const auto uuid = profile_manager->GetUser(index); -    ASSERT(uuid != std::nullopt); +    ASSERT(uuid);      const auto file = QFileDialog::getOpenFileName(this, tr("Select User Image"), QString(),                                                     tr("JPEG Images (*.jpg *.jpeg)")); diff --git a/src/yuzu/configuration/configure_system.ui b/src/yuzu/configuration/configure_system.ui index 020b32a37..a91580893 100644 --- a/src/yuzu/configuration/configure_system.ui +++ b/src/yuzu/configuration/configure_system.ui @@ -6,7 +6,7 @@     <rect>      <x>0</x>      <y>0</y> -    <width>360</width> +    <width>366</width>      <height>483</height>     </rect>    </property> @@ -22,98 +22,6 @@          <string>System Settings</string>         </property>         <layout class="QGridLayout" name="gridLayout"> -        <item row="1" column="0"> -         <widget class="QLabel" name="label_language"> -          <property name="text"> -           <string>Language</string> -          </property> -         </widget> -        </item> -        <item row="0" column="0"> -         <widget class="QLabel" name="label_birthday"> -          <property name="text"> -           <string>Birthday</string> -          </property> -         </widget> -        </item> -        <item row="3" column="0"> -         <widget class="QLabel" name="label_console_id"> -          <property name="text"> -           <string>Console ID:</string> -          </property> -         </widget> -        </item> -        <item row="0" column="1"> -         <layout class="QHBoxLayout" name="horizontalLayout_birthday2"> -          <item> -           <widget class="QComboBox" name="combo_birthmonth"> -            <item> -             <property name="text"> -              <string>January</string> -             </property> -            </item> -            <item> -             <property name="text"> -              <string>February</string> -             </property> -            </item> -            <item> -             <property name="text"> -              <string>March</string> -             </property> -            </item> -            <item> -             <property name="text"> -              <string>April</string> -             </property> -            </item> -            <item> -             <property name="text"> -              <string>May</string> -             </property> -            </item> -            <item> -             <property name="text"> -              <string>June</string> -             </property> -            </item> -            <item> -             <property name="text"> -              <string>July</string> -             </property> -            </item> -            <item> -             <property name="text"> -              <string>August</string> -             </property> -            </item> -            <item> -             <property name="text"> -              <string>September</string> -             </property> -            </item> -            <item> -             <property name="text"> -              <string>October</string> -             </property> -            </item> -            <item> -             <property name="text"> -              <string>November</string> -             </property> -            </item> -            <item> -             <property name="text"> -              <string>December</string> -             </property> -            </item> -           </widget> -          </item> -          <item> -           <widget class="QComboBox" name="combo_birthday"/> -          </item> -         </layout> -        </item>          <item row="1" column="1">           <widget class="QComboBox" name="combo_language">            <property name="toolTip"> @@ -206,6 +114,13 @@            </item>           </widget>          </item> +        <item row="3" column="0"> +         <widget class="QLabel" name="label_console_id"> +          <property name="text"> +           <string>Console ID:</string> +          </property> +         </widget> +        </item>          <item row="2" column="0">           <widget class="QLabel" name="label_sound">            <property name="text"> @@ -213,6 +128,100 @@            </property>           </widget>          </item> +        <item row="0" column="0"> +         <widget class="QLabel" name="label_birthday"> +          <property name="text"> +           <string>Birthday</string> +          </property> +         </widget> +        </item> +        <item row="0" column="1"> +         <layout class="QHBoxLayout" name="horizontalLayout_birthday2"> +          <item> +           <widget class="QComboBox" name="combo_birthmonth"> +            <item> +             <property name="text"> +              <string>January</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>February</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>March</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>April</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>May</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>June</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>July</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>August</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>September</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>October</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>November</string> +             </property> +            </item> +            <item> +             <property name="text"> +              <string>December</string> +             </property> +            </item> +           </widget> +          </item> +          <item> +           <widget class="QComboBox" name="combo_birthday"/> +          </item> +         </layout> +        </item> +        <item row="3" column="1"> +         <widget class="QPushButton" name="button_regenerate_console_id"> +          <property name="sizePolicy"> +           <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> +            <horstretch>0</horstretch> +            <verstretch>0</verstretch> +           </sizepolicy> +          </property> +          <property name="layoutDirection"> +           <enum>Qt::RightToLeft</enum> +          </property> +          <property name="text"> +           <string>Regenerate</string> +          </property> +         </widget> +        </item>          <item row="2" column="1">           <widget class="QComboBox" name="combo_sound">            <item> @@ -232,19 +241,38 @@            </item>           </widget>          </item> -        <item row="3" column="1"> -         <widget class="QPushButton" name="button_regenerate_console_id"> +        <item row="1" column="0"> +         <widget class="QLabel" name="label_language"> +          <property name="text"> +           <string>Language</string> +          </property> +         </widget> +        </item> +        <item row="4" column="0"> +         <widget class="QCheckBox" name="rng_seed_checkbox"> +          <property name="text"> +           <string>RNG Seed</string> +          </property> +         </widget> +        </item> +        <item row="4" column="1"> +         <widget class="QLineEdit" name="rng_seed_edit">            <property name="sizePolicy"> -           <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> +           <sizepolicy hsizetype="Minimum" vsizetype="Fixed">              <horstretch>0</horstretch>              <verstretch>0</verstretch>             </sizepolicy>            </property> -          <property name="layoutDirection"> -           <enum>Qt::RightToLeft</enum> +          <property name="font"> +           <font> +            <family>Lucida Console</family> +           </font>            </property> -          <property name="text"> -           <string>Regenerate</string> +          <property name="inputMask"> +           <string notr="true">HHHHHHHH</string> +          </property> +          <property name="maxLength"> +           <number>8</number>            </property>           </widget>          </item> diff --git a/src/yuzu/configuration/configure_touchscreen_advanced.cpp b/src/yuzu/configuration/configure_touchscreen_advanced.cpp new file mode 100644 index 000000000..9c1561e9d --- /dev/null +++ b/src/yuzu/configuration/configure_touchscreen_advanced.cpp @@ -0,0 +1,42 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <memory> +#include "ui_configure_touchscreen_advanced.h" +#include "yuzu/configuration/config.h" +#include "yuzu/configuration/configure_touchscreen_advanced.h" + +ConfigureTouchscreenAdvanced::ConfigureTouchscreenAdvanced(QWidget* parent) +    : QDialog(parent), ui(std::make_unique<Ui::ConfigureTouchscreenAdvanced>()) { +    ui->setupUi(this); + +    connect(ui->restore_defaults_button, &QPushButton::pressed, this, +            &ConfigureTouchscreenAdvanced::restoreDefaults); + +    loadConfiguration(); +    resize(0, 0); +} + +ConfigureTouchscreenAdvanced::~ConfigureTouchscreenAdvanced() = default; + +void ConfigureTouchscreenAdvanced::applyConfiguration() { +    Settings::values.touchscreen.finger = ui->finger_box->value(); +    Settings::values.touchscreen.diameter_x = ui->diameter_x_box->value(); +    Settings::values.touchscreen.diameter_y = ui->diameter_y_box->value(); +    Settings::values.touchscreen.rotation_angle = ui->angle_box->value(); +} + +void ConfigureTouchscreenAdvanced::loadConfiguration() { +    ui->finger_box->setValue(Settings::values.touchscreen.finger); +    ui->diameter_x_box->setValue(Settings::values.touchscreen.diameter_x); +    ui->diameter_y_box->setValue(Settings::values.touchscreen.diameter_y); +    ui->angle_box->setValue(Settings::values.touchscreen.rotation_angle); +} + +void ConfigureTouchscreenAdvanced::restoreDefaults() { +    ui->finger_box->setValue(0); +    ui->diameter_x_box->setValue(15); +    ui->diameter_y_box->setValue(15); +    ui->angle_box->setValue(0); +} diff --git a/src/yuzu/configuration/configure_touchscreen_advanced.h b/src/yuzu/configuration/configure_touchscreen_advanced.h new file mode 100644 index 000000000..41cd255fb --- /dev/null +++ b/src/yuzu/configuration/configure_touchscreen_advanced.h @@ -0,0 +1,32 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <QDialog> +#include <QWidget> +#include "yuzu/configuration/config.h" + +namespace Ui { +class ConfigureTouchscreenAdvanced; +} + +class ConfigureTouchscreenAdvanced : public QDialog { +    Q_OBJECT + +public: +    explicit ConfigureTouchscreenAdvanced(QWidget* parent); +    ~ConfigureTouchscreenAdvanced() override; + +    void applyConfiguration(); + +private: +    /// Load configuration settings. +    void loadConfiguration(); +    /// Restore all buttons to their default values. +    void restoreDefaults(); + +    std::unique_ptr<Ui::ConfigureTouchscreenAdvanced> ui; +}; diff --git a/src/yuzu/configuration/configure_touchscreen_advanced.ui b/src/yuzu/configuration/configure_touchscreen_advanced.ui new file mode 100644 index 000000000..1171c2dd1 --- /dev/null +++ b/src/yuzu/configuration/configure_touchscreen_advanced.ui @@ -0,0 +1,199 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigureTouchscreenAdvanced</class> + <widget class="QDialog" name="ConfigureTouchscreenAdvanced"> +  <property name="geometry"> +   <rect> +    <x>0</x> +    <y>0</y> +    <width>298</width> +    <height>339</height> +   </rect> +  </property> +  <property name="windowTitle"> +   <string>Configure Touchscreen</string> +  </property> +  <layout class="QVBoxLayout" name="verticalLayout"> +   <item> +    <widget class="QLabel" name="label_2"> +     <property name="minimumSize"> +      <size> +       <width>280</width> +       <height>0</height> +      </size> +     </property> +     <property name="text"> +      <string>Warning: The settings in this page affect the inner workings of yuzu's emulated touchscreen. Changing them may result in undesirable behavior, such as the touchscreen partially or not working. You should only use this page if you know what you are doing.</string> +     </property> +     <property name="wordWrap"> +      <bool>true</bool> +     </property> +    </widget> +   </item> +   <item> +    <spacer name="verticalSpacer_2"> +     <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>20</height> +      </size> +     </property> +    </spacer> +   </item> +   <item> +    <widget class="QGroupBox" name="gridGroupBox"> +     <property name="title"> +      <string>Touch Parameters</string> +     </property> +     <layout class="QGridLayout" name="gridLayout"> +      <item row="0" 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="2" column="1"> +       <widget class="QLabel" name="label_4"> +        <property name="text"> +         <string>Touch Diameter Y</string> +        </property> +       </widget> +      </item> +      <item row="0" column="1"> +       <widget class="QLabel" name="label"> +        <property name="text"> +         <string>Finger</string> +        </property> +       </widget> +      </item> +      <item row="0" 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="QLabel" name="label_3"> +        <property name="text"> +         <string>Touch Diameter X</string> +        </property> +       </widget> +      </item> +      <item row="0" column="2"> +       <widget class="QSpinBox" name="finger_box"> +        <property name="minimumSize"> +         <size> +          <width>80</width> +          <height>0</height> +         </size> +        </property> +       </widget> +      </item> +      <item row="3" column="1"> +       <widget class="QLabel" name="label_5"> +        <property name="text"> +         <string>Rotational Angle</string> +        </property> +       </widget> +      </item> +      <item row="1" column="2"> +       <widget class="QSpinBox" name="diameter_x_box"/> +      </item> +      <item row="2" column="2"> +       <widget class="QSpinBox" name="diameter_y_box"/> +      </item> +      <item row="3" column="2"> +       <widget class="QSpinBox" name="angle_box"/> +      </item> +     </layout> +    </widget> +   </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> +   <item> +    <layout class="QHBoxLayout" name="horizontalLayout"> +     <item> +      <widget class="QPushButton" name="restore_defaults_button"> +       <property name="text"> +        <string>Restore Defaults</string> +       </property> +      </widget> +     </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>ConfigureTouchscreenAdvanced</receiver> +   <slot>accept()</slot> +   <hints> +    <hint type="sourcelabel"> +     <x>140</x> +     <y>318</y> +    </hint> +    <hint type="destinationlabel"> +     <x>140</x> +     <y>169</y> +    </hint> +   </hints> +  </connection> +  <connection> +   <sender>buttonBox</sender> +   <signal>rejected()</signal> +   <receiver>ConfigureTouchscreenAdvanced</receiver> +   <slot>reject()</slot> +   <hints> +    <hint type="sourcelabel"> +     <x>140</x> +     <y>318</y> +    </hint> +    <hint type="destinationlabel"> +     <x>140</x> +     <y>169</y> +    </hint> +   </hints> +  </connection> + </connections> +</ui> diff --git a/src/yuzu/configuration/configure_web.h b/src/yuzu/configuration/configure_web.h index 7741ab95d..7752ae4a1 100644 --- a/src/yuzu/configuration/configure_web.h +++ b/src/yuzu/configuration/configure_web.h @@ -17,18 +17,17 @@ class ConfigureWeb : public QWidget {  public:      explicit ConfigureWeb(QWidget* parent = nullptr); -    ~ConfigureWeb(); +    ~ConfigureWeb() override;      void applyConfiguration();      void retranslateUi(); -public slots: +private:      void RefreshTelemetryID();      void OnLoginChanged();      void VerifyLogin();      void OnLoginVerified(); -private:      void setConfiguration();      bool user_verified = true; diff --git a/src/yuzu/debugger/graphics/graphics_surface.cpp b/src/yuzu/debugger/graphics/graphics_surface.cpp index 44d423da2..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;      }  } @@ -382,13 +383,13 @@ void GraphicsSurfaceWidget::OnUpdate() {      // TODO: Implement a good way to visualize alpha components!      QImage decoded_image(surface_width, surface_height, QImage::Format_ARGB32); -    boost::optional<VAddr> address = gpu.MemoryManager().GpuToCpuAddress(surface_address); +    std::optional<VAddr> address = gpu.MemoryManager().GpuToCpuAddress(surface_address);      // TODO(bunnei): Will not work with BCn formats that swizzle 4x4 tiles.      // Needs to be fixed if we plan to use this feature more, otherwise we may remove it. -    auto unswizzled_data = -        Tegra::Texture::UnswizzleTexture(*address, 1, Tegra::Texture::BytesPerPixel(surface_format), -                                         surface_width, surface_height, 1U); +    auto unswizzled_data = Tegra::Texture::UnswizzleTexture( +        *address, 1, 1, Tegra::Texture::BytesPerPixel(surface_format), surface_width, +        surface_height, 1U);      auto texture_data = Tegra::Texture::DecodeTexture(unswizzled_data, surface_format,                                                        surface_width, surface_height); @@ -444,7 +445,7 @@ void GraphicsSurfaceWidget::SaveSurface() {              pixmap->save(&file, "PNG");      } else if (selectedFilter == bin_filter) {          auto& gpu = Core::System::GetInstance().GPU(); -        boost::optional<VAddr> address = gpu.MemoryManager().GpuToCpuAddress(surface_address); +        std::optional<VAddr> address = gpu.MemoryManager().GpuToCpuAddress(surface_address);          const u8* buffer = Memory::GetPointer(*address);          ASSERT_MSG(buffer != nullptr, "Memory not accessible"); diff --git a/src/yuzu/debugger/wait_tree.cpp b/src/yuzu/debugger/wait_tree.cpp index 0c831c9f4..df6eeb9a6 100644 --- a/src/yuzu/debugger/wait_tree.cpp +++ b/src/yuzu/debugger/wait_tree.cpp @@ -7,10 +7,10 @@  #include "common/assert.h"  #include "core/core.h" -#include "core/hle/kernel/event.h"  #include "core/hle/kernel/handle_table.h"  #include "core/hle/kernel/mutex.h"  #include "core/hle/kernel/process.h" +#include "core/hle/kernel/readable_event.h"  #include "core/hle/kernel/scheduler.h"  #include "core/hle/kernel/thread.h"  #include "core/hle/kernel/timer.h" @@ -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 { @@ -153,8 +153,8 @@ QString WaitTreeWaitObject::GetText() const {  std::unique_ptr<WaitTreeWaitObject> WaitTreeWaitObject::make(const Kernel::WaitObject& object) {      switch (object.GetHandleType()) { -    case Kernel::HandleType::Event: -        return std::make_unique<WaitTreeEvent>(static_cast<const Kernel::Event&>(object)); +    case Kernel::HandleType::ReadableEvent: +        return std::make_unique<WaitTreeEvent>(static_cast<const Kernel::ReadableEvent&>(object));      case Kernel::HandleType::Timer:          return std::make_unique<WaitTreeTimer>(static_cast<const Kernel::Timer&>(object));      case Kernel::HandleType::Thread: @@ -221,6 +221,9 @@ QString WaitTreeThread::GetText() const {      case Kernel::ThreadStatus::Ready:          status = tr("ready");          break; +    case Kernel::ThreadStatus::Paused: +        status = tr("paused"); +        break;      case Kernel::ThreadStatus::WaitHLEEvent:          status = tr("waiting for HLE return");          break; @@ -262,6 +265,8 @@ QColor WaitTreeThread::GetColor() const {          return QColor(Qt::GlobalColor::darkGreen);      case Kernel::ThreadStatus::Ready:          return QColor(Qt::GlobalColor::darkBlue); +    case Kernel::ThreadStatus::Paused: +        return QColor(Qt::GlobalColor::lightGray);      case Kernel::ThreadStatus::WaitHLEEvent:      case Kernel::ThreadStatus::WaitIPC:          return QColor(Qt::GlobalColor::darkRed); @@ -288,8 +293,8 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThread::GetChildren() const {      QString processor;      switch (thread.GetProcessorID()) { -    case Kernel::ThreadProcessorId::THREADPROCESSORID_DEFAULT: -        processor = tr("default"); +    case Kernel::ThreadProcessorId::THREADPROCESSORID_IDEAL: +        processor = tr("ideal");          break;      case Kernel::ThreadProcessorId::THREADPROCESSORID_0:      case Kernel::ThreadProcessorId::THREADPROCESSORID_1: @@ -332,7 +337,7 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThread::GetChildren() const {      return list;  } -WaitTreeEvent::WaitTreeEvent(const Kernel::Event& object) : WaitTreeWaitObject(object) {} +WaitTreeEvent::WaitTreeEvent(const Kernel::ReadableEvent& object) : WaitTreeWaitObject(object) {}  WaitTreeEvent::~WaitTreeEvent() = default;  std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeEvent::GetChildren() const { @@ -340,7 +345,8 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeEvent::GetChildren() const {      list.push_back(std::make_unique<WaitTreeText>(          tr("reset type = %1") -            .arg(GetResetTypeQString(static_cast<const Kernel::Event&>(object).GetResetType())))); +            .arg(GetResetTypeQString( +                static_cast<const Kernel::ReadableEvent&>(object).GetResetType()))));      return list;  } diff --git a/src/yuzu/debugger/wait_tree.h b/src/yuzu/debugger/wait_tree.h index defbf734f..e639ef412 100644 --- a/src/yuzu/debugger/wait_tree.h +++ b/src/yuzu/debugger/wait_tree.h @@ -11,15 +11,14 @@  #include <QAbstractItemModel>  #include <QDockWidget>  #include <QTreeView> -#include <boost/container/flat_set.hpp>  #include "common/common_types.h"  #include "core/hle/kernel/object.h"  class EmuThread;  namespace Kernel { +class ReadableEvent;  class WaitObject; -class Event;  class Thread;  class Timer;  } // namespace Kernel @@ -53,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; @@ -145,7 +144,7 @@ public:  class WaitTreeEvent : public WaitTreeWaitObject {      Q_OBJECT  public: -    explicit WaitTreeEvent(const Kernel::Event& object); +    explicit WaitTreeEvent(const Kernel::ReadableEvent& object);      ~WaitTreeEvent() override;      std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override; diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index a5a4aa432..8e9524fd6 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -214,13 +214,20 @@ GameList::GameList(FileSys::VirtualFilesystem vfs, GMainWindow* parent)      tree_view->setEditTriggers(QHeaderView::NoEditTriggers);      tree_view->setUniformRowHeights(true);      tree_view->setContextMenuPolicy(Qt::CustomContextMenu); +    tree_view->setStyleSheet("QTreeView{ border: none; }"); -    item_model->insertColumns(0, COLUMN_COUNT); +    item_model->insertColumns(0, UISettings::values.show_add_ons ? COLUMN_COUNT : COLUMN_COUNT - 1);      item_model->setHeaderData(COLUMN_NAME, Qt::Horizontal, tr("Name"));      item_model->setHeaderData(COLUMN_COMPATIBILITY, Qt::Horizontal, tr("Compatibility")); -    item_model->setHeaderData(COLUMN_ADD_ONS, Qt::Horizontal, tr("Add-ons")); -    item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, tr("File type")); -    item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, tr("Size")); + +    if (UISettings::values.show_add_ons) { +        item_model->setHeaderData(COLUMN_ADD_ONS, Qt::Horizontal, tr("Add-ons")); +        item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, tr("File type")); +        item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, tr("Size")); +    } else { +        item_model->setHeaderData(COLUMN_FILE_TYPE - 1, Qt::Horizontal, tr("File type")); +        item_model->setHeaderData(COLUMN_SIZE - 1, Qt::Horizontal, tr("Size")); +    }      connect(tree_view, &QTreeView::activated, this, &GameList::ValidateEntry);      connect(tree_view, &QTreeView::customContextMenuRequested, this, &GameList::PopupContextMenu); @@ -326,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); @@ -339,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));  } @@ -394,6 +404,25 @@ void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) {      }      tree_view->setEnabled(false); + +    // Update the columns in case UISettings has changed +    item_model->removeColumns(0, item_model->columnCount()); +    item_model->insertColumns(0, UISettings::values.show_add_ons ? COLUMN_COUNT : COLUMN_COUNT - 1); +    item_model->setHeaderData(COLUMN_NAME, Qt::Horizontal, tr("Name")); +    item_model->setHeaderData(COLUMN_COMPATIBILITY, Qt::Horizontal, tr("Compatibility")); + +    if (UISettings::values.show_add_ons) { +        item_model->setHeaderData(COLUMN_ADD_ONS, Qt::Horizontal, tr("Add-ons")); +        item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, tr("File type")); +        item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, tr("Size")); +    } else { +        item_model->setHeaderData(COLUMN_FILE_TYPE - 1, Qt::Horizontal, tr("File type")); +        item_model->setHeaderData(COLUMN_SIZE - 1, Qt::Horizontal, tr("Size")); +        item_model->removeColumns(COLUMN_COUNT - 1, 1); +    } + +    LoadInterfaceLayout(); +      // Delete any rows that might already exist if we're repopulating      item_model->removeRows(0, item_model->rowCount()); 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 3d865a12d..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;          } @@ -86,6 +86,37 @@ QString FormatPatchNameVersions(const FileSys::PatchManager& patch_manager,      out.chop(1);      return out;  } + +QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::string& name, +                                        const std::vector<u8>& icon, Loader::AppLoader& loader, +                                        u64 program_id, const CompatibilityList& compatibility_list, +                                        const FileSys::PatchManager& patch) { +    const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); + +    // The game list uses this as compatibility number for untested games +    QString compatibility{"99"}; +    if (it != compatibility_list.end()) { +        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), +                             file_type_string, program_id), +        new GameListItemCompat(compatibility), +        new GameListItem(file_type_string), +        new GameListItemSize(FileUtil::GetSize(path)), +    }; + +    if (UISettings::values.show_add_ons) { +        list.insert( +            2, new GameListItem(FormatPatchNameVersions(patch, loader, loader.IsRomFSUpdatable()))); +    } + +    return list; +}  } // Anonymous namespace  GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs, QString dir_path, bool deep_scan, @@ -97,11 +128,11 @@ GameListWorker::~GameListWorker() = default;  void GameListWorker::AddInstalledTitlesToGameList() {      const auto cache = Service::FileSystem::GetUnionContents(); -    const auto installed_games = cache->ListEntriesFilter(FileSys::TitleType::Application, -                                                          FileSys::ContentRecordType::Program); +    const auto installed_games = cache.ListEntriesFilter(FileSys::TitleType::Application, +                                                         FileSys::ContentRecordType::Program);      for (const auto& game : installed_games) { -        const auto file = cache->GetEntryUnparsed(game); +        const auto file = cache.GetEntryUnparsed(game);          std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(file);          if (!loader)              continue; @@ -112,35 +143,19 @@ void GameListWorker::AddInstalledTitlesToGameList() {          loader->ReadProgramId(program_id);          const FileSys::PatchManager patch{program_id}; -        const auto control = cache->GetEntry(game.title_id, FileSys::ContentRecordType::Control); +        const auto control = cache.GetEntry(game.title_id, FileSys::ContentRecordType::Control);          if (control != nullptr)              GetMetadataFromControlNCA(patch, *control, icon, name); -        auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); - -        // The game list uses this as compatibility number for untested games -        QString compatibility("99"); -        if (it != compatibility_list.end()) -            compatibility = it->second.first; - -        emit EntryReady({ -            new GameListItemPath( -                FormatGameName(file->GetFullPath()), icon, QString::fromStdString(name), -                QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())), -                program_id), -            new GameListItemCompat(compatibility), -            new GameListItem(FormatPatchNameVersions(patch, *loader)), -            new GameListItem( -                QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))), -            new GameListItemSize(file->GetSize()), -        }); +        emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, icon, *loader, program_id, +                                          compatibility_list, patch));      } -    const auto control_data = cache->ListEntriesFilter(FileSys::TitleType::Application, -                                                       FileSys::ContentRecordType::Control); +    const auto control_data = cache.ListEntriesFilter(FileSys::TitleType::Application, +                                                      FileSys::ContentRecordType::Control);      for (const auto& entry : control_data) { -        auto nca = cache->GetEntry(entry); +        auto nca = cache.GetEntry(entry);          if (nca != nullptr) {              nca_control_map.insert_or_assign(entry.title_id, std::move(nca));          } @@ -150,14 +165,14 @@ void GameListWorker::AddInstalledTitlesToGameList() {  void GameListWorker::FillControlMap(const std::string& dir_path) {      const auto nca_control_callback = [this](u64* num_entries_out, const std::string& directory,                                               const std::string& virtual_name) -> bool { -        std::string physical_name = directory + DIR_SEP + virtual_name; - -        if (stop_processing) -            return false; // Breaks the callback loop. +        if (stop_processing) { +            // Breaks the callback loop +            return false; +        } -        bool is_dir = FileUtil::IsDirectory(physical_name); -        QFileInfo file_info(physical_name.c_str()); -        if (!is_dir && file_info.suffix().toStdString() == "nca") { +        const std::string physical_name = directory + DIR_SEP + virtual_name; +        const QFileInfo file_info(QString::fromStdString(physical_name)); +        if (!file_info.isDir() && file_info.suffix() == QStringLiteral("nca")) {              auto nca =                  std::make_unique<FileSys::NCA>(vfs->OpenFile(physical_name, FileSys::Mode::Read));              if (nca->GetType() == FileSys::NCAContentType::Control) { @@ -174,20 +189,25 @@ void GameListWorker::FillControlMap(const std::string& dir_path) {  void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion) {      const auto callback = [this, recursion](u64* num_entries_out, const std::string& directory,                                              const std::string& virtual_name) -> bool { -        std::string physical_name = directory + DIR_SEP + virtual_name; - -        if (stop_processing) -            return false; // Breaks the callback loop. +        if (stop_processing) { +            // Breaks the callback loop. +            return false; +        } -        bool is_dir = FileUtil::IsDirectory(physical_name); +        const std::string physical_name = directory + DIR_SEP + virtual_name; +        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); @@ -209,25 +229,8 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign                  }              } -            auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); - -            // The game list uses this as compatibility number for untested games -            QString compatibility("99"); -            if (it != compatibility_list.end()) -                compatibility = it->second.first; - -            emit EntryReady({ -                new GameListItemPath( -                    FormatGameName(physical_name), icon, QString::fromStdString(name), -                    QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())), -                    program_id), -                new GameListItemCompat(compatibility), -                new GameListItem( -                    FormatPatchNameVersions(patch, *loader, loader->IsRomFSUpdatable())), -                new GameListItem( -                    QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))), -                new GameListItemSize(FileUtil::GetSize(physical_name)), -            }); +            emit EntryReady(MakeGameListEntry(physical_name, name, icon, *loader, program_id, +                                              compatibility_list, patch));          } else if (is_dir && recursion > 0) {              watch_list.append(QString::fromStdString(physical_name));              AddFstEntriesToGameList(physical_name, recursion - 1); diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index b5bfa6741..1d5a2b51a 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -8,11 +8,15 @@  #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" +#include "core/hle/service/am/applets/applets.h" -// These are wrappers to avoid the calls to CreateDirectory and CreateFile becuase of the Windows +// These are wrappers to avoid the calls to CreateDirectory and CreateFile because of the Windows  // defines.  static FileSys::VirtualDir VfsFilesystemCreateDirectoryWrapper(      const FileSys::VirtualFilesystem& vfs, const std::string& path, FileSys::Mode mode) { @@ -59,6 +63,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual  #include "core/file_sys/romfs.h"  #include "core/file_sys/savedata_factory.h"  #include "core/file_sys/submission_package.h" +#include "core/frontend/applets/software_keyboard.h"  #include "core/hle/kernel/process.h"  #include "core/hle/service/filesystem/filesystem.h"  #include "core/hle/service/filesystem/fsp_ldr.h" @@ -142,6 +147,9 @@ static void InitializeLogging() {      const std::string& log_dir = FileUtil::GetUserPath(FileUtil::UserPath::LogDir);      FileUtil::CreateFullPath(log_dir);      Log::AddBackend(std::make_unique<Log::FileBackend>(log_dir + LOG_FILE)); +#ifdef _WIN32 +    Log::AddBackend(std::make_unique<Log::DebuggerBackend>()); +#endif  }  GMainWindow::GMainWindow() @@ -201,6 +209,49 @@ 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); +    dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | +                          Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint); +    dialog.setWindowModality(Qt::WindowModal); +    dialog.exec(); + +    if (!dialog.GetStatus()) { +        emit SoftwareKeyboardFinishedText(std::nullopt); +        return; +    } + +    emit SoftwareKeyboardFinishedText(dialog.GetText()); +} + +void GMainWindow::SoftwareKeyboardInvokeCheckDialog(std::u16string error_message) { +    QMessageBox::warning(this, tr("Text Check Failed"), QString::fromStdU16String(error_message)); +    emit SoftwareKeyboardFinishedCheckDialog(); +} +  void GMainWindow::InitializeWidgets() {  #ifdef YUZU_ENABLE_COMPATIBILITY_REPORTING      ui.action_Report_Compatibility->setVisible(true); @@ -305,6 +356,11 @@ void GMainWindow::InitializeHotkeys() {                                     Qt::ApplicationShortcut);      hotkey_registry.RegisterHotkey("Main Window", "Decrease Speed Limit", QKeySequence("-"),                                     Qt::ApplicationShortcut); +    hotkey_registry.RegisterHotkey("Main Window", "Load Amiibo", QKeySequence(Qt::Key_F2), +                                   Qt::ApplicationShortcut); +    hotkey_registry.RegisterHotkey("Main Window", "Capture Screenshot", +                                   QKeySequence(QKeySequence::Print)); +      hotkey_registry.LoadHotkeys();      connect(hotkey_registry.GetHotkey("Main Window", "Load File", this), &QShortcut::activated, @@ -358,6 +414,18 @@ void GMainWindow::InitializeHotkeys() {                      UpdateStatusBar();                  }              }); +    connect(hotkey_registry.GetHotkey("Main Window", "Load Amiibo", this), &QShortcut::activated, +            this, [&] { +                if (ui.action_Load_Amiibo->isEnabled()) { +                    OnLoadAmiibo(); +                } +            }); +    connect(hotkey_registry.GetHotkey("Main Window", "Capture Screenshot", this), +            &QShortcut::activated, this, [&] { +                if (emu_thread->IsRunning()) { +                    OnCaptureScreenshot(); +                } +            });  }  void GMainWindow::SetDefaultUIGeometry() { @@ -406,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); @@ -453,7 +523,12 @@ void GMainWindow::ConnectMenuEvents() {          hotkey_registry.GetHotkey("Main Window", "Fullscreen", this)->key());      connect(ui.action_Fullscreen, &QAction::triggered, this, &GMainWindow::ToggleFullscreen); +    // Movie +    connect(ui.action_Capture_Screenshot, &QAction::triggered, this, +            &GMainWindow::OnCaptureScreenshot); +      // Help +    connect(ui.action_Open_yuzu_Folder, &QAction::triggered, this, &GMainWindow::OnOpenYuzuFolder);      connect(ui.action_Rederive, &QAction::triggered, this,              std::bind(&GMainWindow::OnReinitializeKeys, this, ReinitializeKeyBehavior::Warning));      connect(ui.action_About, &QAction::triggered, this, &GMainWindow::OnAbout); @@ -482,32 +557,20 @@ void GMainWindow::OnDisplayTitleBars(bool show) {  QStringList GMainWindow::GetUnsupportedGLExtensions() {      QStringList unsupported_ext; -    if (!GLAD_GL_ARB_program_interface_query) -        unsupported_ext.append("ARB_program_interface_query"); -    if (!GLAD_GL_ARB_separate_shader_objects) -        unsupported_ext.append("ARB_separate_shader_objects"); -    if (!GLAD_GL_ARB_vertex_attrib_binding) -        unsupported_ext.append("ARB_vertex_attrib_binding"); +    if (!GLAD_GL_ARB_direct_state_access) +        unsupported_ext.append("ARB_direct_state_access");      if (!GLAD_GL_ARB_vertex_type_10f_11f_11f_rev)          unsupported_ext.append("ARB_vertex_type_10f_11f_11f_rev");      if (!GLAD_GL_ARB_texture_mirror_clamp_to_edge)          unsupported_ext.append("ARB_texture_mirror_clamp_to_edge"); -    if (!GLAD_GL_ARB_base_instance) -        unsupported_ext.append("ARB_base_instance"); -    if (!GLAD_GL_ARB_texture_storage) -        unsupported_ext.append("ARB_texture_storage");      if (!GLAD_GL_ARB_multi_bind)          unsupported_ext.append("ARB_multi_bind"); -    if (!GLAD_GL_ARB_copy_image) -        unsupported_ext.append("ARB_copy_image");      // Extensions required to support some texture formats.      if (!GLAD_GL_EXT_texture_compression_s3tc)          unsupported_ext.append("EXT_texture_compression_s3tc");      if (!GLAD_GL_ARB_texture_compression_rgtc)          unsupported_ext.append("ARB_texture_compression_rgtc"); -    if (!GLAD_GL_ARB_texture_compression_bptc) -        unsupported_ext.append("ARB_texture_compression_bptc");      if (!GLAD_GL_ARB_depth_buffer_float)          unsupported_ext.append("ARB_depth_buffer_float"); @@ -526,8 +589,8 @@ bool GMainWindow::LoadROM(const QString& filename) {      render_window->MakeCurrent();      if (!gladLoadGL()) { -        QMessageBox::critical(this, tr("Error while initializing OpenGL 3.3 Core!"), -                              tr("Your GPU may not support OpenGL 3.3, or you do not " +        QMessageBox::critical(this, tr("Error while initializing OpenGL 4.3 Core!"), +                              tr("Your GPU may not support OpenGL 4.3, or you do not "                                   "have the latest graphics driver."));          return false;      } @@ -547,6 +610,9 @@ 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())};      const auto drd_callout = @@ -621,10 +687,26 @@ bool GMainWindow::LoadROM(const QString& filename) {      return true;  } +void GMainWindow::SelectAndSetCurrentUser() { +    QtProfileSelectionDialog dialog(this); +    dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | +                          Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint); +    dialog.setWindowModality(Qt::WindowModal); +    dialog.exec(); + +    if (dialog.GetStatus()) { +        Settings::values.current_user = static_cast<s32>(dialog.GetIndex()); +    } +} +  void GMainWindow::BootGame(const QString& filename) {      LOG_INFO(Frontend, "yuzu starting...");      StoreRecentFile(filename); // Put the filename on top of the list +    if (UISettings::values.select_user_on_boot) { +        SelectAndSetCurrentUser(); +    } +      if (!LoadROM(filename))          return; @@ -698,6 +780,7 @@ void GMainWindow::ShutdownGame() {      ui.action_Restart->setEnabled(false);      ui.action_Report_Compatibility->setEnabled(false);      ui.action_Load_Amiibo->setEnabled(false); +    ui.action_Capture_Screenshot->setEnabled(false);      render_window->hide();      game_list->show();      game_list->setFilterFocus(); @@ -760,33 +843,27 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target          const std::string nand_dir = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir);          ASSERT(program_id != 0); -        Service::Account::ProfileManager manager{}; -        const auto user_ids = manager.GetAllUsers(); -        QStringList list; -        for (const auto& user_id : user_ids) { -            if (user_id == Service::Account::UUID{}) -                continue; -            Service::Account::ProfileBase base; -            if (!manager.GetProfileBase(user_id, base)) -                continue; - -            list.push_back(QString::fromStdString(Common::StringFromFixedZeroTerminatedBuffer( -                reinterpret_cast<const char*>(base.username.data()), base.username.size()))); -        } +        const auto select_profile = [this]() -> s32 { +            QtProfileSelectionDialog dialog(this); +            dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | +                                  Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint); +            dialog.setWindowModality(Qt::WindowModal); +            dialog.exec(); -        bool ok = false; -        const auto index_string = -            QInputDialog::getItem(this, tr("Select User"), -                                  tr("Please select the user's save data you would like to open."), -                                  list, Settings::values.current_user, false, &ok); -        if (!ok) -            return; +            if (!dialog.GetStatus()) { +                return -1; +            } + +            return dialog.GetIndex(); +        }; -        const auto index = list.indexOf(index_string); -        ASSERT(index != -1 && index < 8); +        const auto index = select_profile(); +        if (index == -1) +            return; +        Service::Account::ProfileManager manager;          const auto user_id = manager.GetUser(index); -        ASSERT(user_id != std::nullopt); +        ASSERT(user_id);          path = nand_dir + FileSys::SaveDataFactory::GetFullPath(FileSys::SaveDataSpaceId::NandUser,                                                                  FileSys::SaveDataType::SaveData,                                                                  program_id, user_id->uuid, 0); @@ -881,7 +958,7 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa      }      const auto installed = Service::FileSystem::GetUnionContents(); -    auto romfs_title_id = SelectRomFSDumpTarget(*installed, program_id); +    const auto romfs_title_id = SelectRomFSDumpTarget(installed, program_id);      if (!romfs_title_id) {          failed(); @@ -896,7 +973,7 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa      if (*romfs_title_id == program_id) {          romfs = file;      } else { -        romfs = installed->GetEntry(*romfs_title_id, FileSys::ContentRecordType::Data)->GetRomFS(); +        romfs = installed.GetEntry(*romfs_title_id, FileSys::ContentRecordType::Data)->GetRomFS();      }      const auto extracted = FileSys::ExtractRomFS(romfs, FileSys::RomFSExtractionType::Full); @@ -929,7 +1006,8 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa      const auto full = res == "Full";      const auto entry_size = CalculateRomFSEntrySize(extracted, full); -    QProgressDialog progress(tr("Extracting RomFS..."), tr("Cancel"), 0, entry_size, this); +    QProgressDialog progress(tr("Extracting RomFS..."), tr("Cancel"), 0, +                             static_cast<s32>(entry_size), this);      progress.setWindowModality(Qt::WindowModal);      progress.setMinimumDuration(100); @@ -961,6 +1039,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"); @@ -1080,14 +1184,14 @@ void GMainWindow::OnMenuInstallToNAND() {              return;          }          const auto res = -            Service::FileSystem::GetUserNANDContents()->InstallEntry(nsp, false, qt_raw_copy); +            Service::FileSystem::GetUserNANDContents()->InstallEntry(*nsp, false, qt_raw_copy);          if (res == FileSys::InstallResult::Success) {              success();          } else {              if (res == FileSys::InstallResult::ErrorAlreadyExists) {                  if (overwrite()) {                      const auto res2 = Service::FileSystem::GetUserNANDContents()->InstallEntry( -                        nsp, true, qt_raw_copy); +                        *nsp, true, qt_raw_copy);                      if (res2 == FileSys::InstallResult::Success) {                          success();                      } else { @@ -1142,10 +1246,10 @@ void GMainWindow::OnMenuInstallToNAND() {          FileSys::InstallResult res;          if (index >= static_cast<size_t>(FileSys::TitleType::Application)) {              res = Service::FileSystem::GetUserNANDContents()->InstallEntry( -                nca, static_cast<FileSys::TitleType>(index), false, qt_raw_copy); +                *nca, static_cast<FileSys::TitleType>(index), false, qt_raw_copy);          } else {              res = Service::FileSystem::GetSystemNANDContents()->InstallEntry( -                nca, static_cast<FileSys::TitleType>(index), false, qt_raw_copy); +                *nca, static_cast<FileSys::TitleType>(index), false, qt_raw_copy);          }          if (res == FileSys::InstallResult::Success) { @@ -1153,7 +1257,7 @@ void GMainWindow::OnMenuInstallToNAND() {          } else if (res == FileSys::InstallResult::ErrorAlreadyExists) {              if (overwrite()) {                  const auto res2 = Service::FileSystem::GetUserNANDContents()->InstallEntry( -                    nca, static_cast<FileSys::TitleType>(index), true, qt_raw_copy); +                    *nca, static_cast<FileSys::TitleType>(index), true, qt_raw_copy);                  if (res2 == FileSys::InstallResult::Success) {                      success();                  } else { @@ -1215,8 +1319,13 @@ void GMainWindow::OnMenuRecentFile() {  void GMainWindow::OnStartGame() {      emu_thread->SetRunning(true); + +    qRegisterMetaType<Core::Frontend::SoftwareKeyboardParameters>( +        "Core::Frontend::SoftwareKeyboardParameters");      qRegisterMetaType<Core::System::ResultStatus>("Core::System::ResultStatus");      qRegisterMetaType<std::string>("std::string"); +    qRegisterMetaType<std::optional<std::u16string>>("std::optional<std::u16string>"); +      connect(emu_thread.get(), &EmuThread::ErrorThrown, this, &GMainWindow::OnCoreError);      ui.action_Start->setEnabled(false); @@ -1229,6 +1338,7 @@ void GMainWindow::OnStartGame() {      discord_rpc->Update();      ui.action_Load_Amiibo->setEnabled(true); +    ui.action_Capture_Screenshot->setEnabled(true);  }  void GMainWindow::OnPauseGame() { @@ -1237,6 +1347,7 @@ void GMainWindow::OnPauseGame() {      ui.action_Start->setEnabled(true);      ui.action_Pause->setEnabled(false);      ui.action_Stop->setEnabled(true); +    ui.action_Capture_Screenshot->setEnabled(false);  }  void GMainWindow::OnStopGame() { @@ -1328,7 +1439,13 @@ void GMainWindow::OnConfigure() {              UpdateUITheme();          if (UISettings::values.enable_discord_presence != old_discord_presence)              SetDiscordEnabled(UISettings::values.enable_discord_presence); -        game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); + +        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();      }  } @@ -1374,6 +1491,11 @@ void GMainWindow::OnLoadAmiibo() {      }  } +void GMainWindow::OnOpenYuzuFolder() { +    QDesktopServices::openUrl(QUrl::fromLocalFile( +        QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::UserDir)))); +} +  void GMainWindow::OnAbout() {      AboutDialog aboutDialog(this);      aboutDialog.exec(); @@ -1388,6 +1510,18 @@ void GMainWindow::OnToggleFilterBar() {      }  } +void GMainWindow::OnCaptureScreenshot() { +    OnPauseGame(); +    const QString path = +        QFileDialog::getSaveFileName(this, tr("Capture Screenshot"), +                                     UISettings::values.screenshot_path, tr("PNG Image (*.png)")); +    if (!path.isEmpty()) { +        UISettings::values.screenshot_path = QFileInfo(path).path(); +        render_window->CaptureScreenshot(UISettings::values.screenshot_resolution_factor, path); +    } +    OnStartGame(); +} +  void GMainWindow::UpdateStatusBar() {      if (emu_thread == nullptr) {          status_bar_update_timer.stop(); @@ -1532,7 +1666,7 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {                     "derivation. It will be attempted but may not complete.<br><br>") +                      errors +                      tr("<br><br>You can get all of these and dump all of your games easily by " -                       "following <a href='https://yuzu-emu.org/help/quickstart/quickstart/'>the " +                       "following <a href='https://yuzu-emu.org/help/quickstart/'>the "                         "quickstart guide</a>. Alternatively, you can use another method of dumping "                         "to obtain all of your keys."));          } @@ -1560,7 +1694,7 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {      }  } -boost::optional<u64> GMainWindow::SelectRomFSDumpTarget( +std::optional<u64> GMainWindow::SelectRomFSDumpTarget(      const FileSys::RegisteredCacheUnion& installed, u64 program_id) {      const auto dlc_entries =          installed.ListEntriesFilter(FileSys::TitleType::AOC, FileSys::ContentRecordType::Data); @@ -1587,7 +1721,7 @@ boost::optional<u64> GMainWindow::SelectRomFSDumpTarget(              this, tr("Select RomFS Dump Target"),              tr("Please select which RomFS you would like to dump."), list, 0, false, &ok);          if (!ok) { -            return boost::none; +            return {};          }          return romfs_tids[list.indexOf(res)]; @@ -1612,7 +1746,7 @@ void GMainWindow::closeEvent(QCloseEvent* event) {          return;      } -    if (ui.action_Fullscreen->isChecked()) { +    if (!ui.action_Fullscreen->isChecked()) {          UISettings::values.geometry = saveGeometry();          UISettings::values.renderwindow_geometry = render_window->saveGeometry();      } diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 7c7c223e1..d560bf75b 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -5,12 +5,12 @@  #pragma once  #include <memory> +#include <optional>  #include <unordered_map>  #include <QMainWindow>  #include <QTimer> -#include <boost/optional.hpp>  #include "common/common_types.h"  #include "core/core.h"  #include "ui_main.h" @@ -29,6 +29,10 @@ class ProfilerWidget;  class WaitTreeWidget;  enum class GameListOpenTarget; +namespace Core::Frontend { +struct SoftwareKeyboardParameters; +} // namespace Core::Frontend +  namespace FileSys {  class RegisteredCacheUnion;  class VfsFilesystem; @@ -95,6 +99,15 @@ 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); +  private:      void InitializeWidgets();      void InitializeDebugWidgets(); @@ -115,6 +128,8 @@ private:      void ShowTelemetryCallout();      void SetDiscordEnabled(bool state); +    void SelectAndSetCurrentUser(); +      /**       * Stores the filename in the recently loaded files list.       * The new filename is stored at the beginning of the recently loaded files list. @@ -157,6 +172,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(); @@ -167,6 +183,7 @@ private slots:      void OnMenuRecentFile();      void OnConfigure();      void OnLoadAmiibo(); +    void OnOpenYuzuFolder();      void OnAbout();      void OnToggleFilterBar();      void OnDisplayTitleBars(bool); @@ -174,12 +191,12 @@ private slots:      void ShowFullscreen();      void HideFullscreen();      void ToggleWindowMode(); +    void OnCaptureScreenshot();      void OnCoreError(Core::System::ResultStatus, std::string);      void OnReinitializeKeys(ReinitializeKeyBehavior behavior);  private: -    boost::optional<u64> SelectRomFSDumpTarget(const FileSys::RegisteredCacheUnion&, -                                               u64 program_id); +    std::optional<u64> SelectRomFSDumpTarget(const FileSys::RegisteredCacheUnion&, u64 program_id);      void UpdateStatusBar();      Ui::MainWindow ui; diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui index 48d099591..ffcabb495 100644 --- a/src/yuzu/main.ui +++ b/src/yuzu/main.ui @@ -70,6 +70,8 @@      <addaction name="separator"/>      <addaction name="action_Load_Amiibo"/>      <addaction name="separator"/> +    <addaction name="action_Open_yuzu_Folder"/> +    <addaction name="separator"/>      <addaction name="action_Exit"/>     </widget>     <widget class="QMenu" name="menu_Emulation"> @@ -99,11 +101,13 @@      <addaction name="action_Show_Status_Bar"/>      <addaction name="menu_View_Debugging"/>     </widget> -   <widget class ="QMenu" name="menu_Tools"> +   <widget class="QMenu" name="menu_Tools">      <property name="title">       <string>Tools</string>      </property> -    <addaction name="action_Rederive" /> +    <addaction name="action_Rederive"/> +    <addaction name="separator"/> +    <addaction name="action_Capture_Screenshot"/>     </widget>     <widget class="QMenu" name="menu_Help">      <property name="title"> @@ -116,7 +120,7 @@     <addaction name="menu_File"/>     <addaction name="menu_Emulation"/>     <addaction name="menu_View"/> -   <addaction name="menu_Tools" /> +   <addaction name="menu_Tools"/>     <addaction name="menu_Help"/>    </widget>    <action name="action_Install_File_NAND"> @@ -171,11 +175,11 @@      <string>&Stop</string>     </property>    </action> -   <action name="action_Rederive"> -     <property name="text"> -       <string>Reinitialize keys...</string> -     </property> -   </action> +  <action name="action_Rederive"> +   <property name="text"> +    <string>Reinitialize keys...</string> +   </property> +  </action>    <action name="action_About">     <property name="text">      <string>About yuzu</string> @@ -250,34 +254,47 @@      <string>Fullscreen</string>     </property>    </action> -   <action name="action_Restart"> -     <property name="enabled"> -       <bool>false</bool> -     </property> -     <property name="text"> -       <string>Restart</string> -     </property> -   </action> +  <action name="action_Restart"> +   <property name="enabled"> +    <bool>false</bool> +   </property> +   <property name="text"> +    <string>Restart</string> +   </property> +  </action>    <action name="action_Load_Amiibo"> -    <property name="enabled"> -      <bool>false</bool> -    </property> -    <property name="text"> -      <string>Load Amiibo...</string> -    </property> +   <property name="enabled"> +    <bool>false</bool> +   </property> +   <property name="text"> +    <string>Load Amiibo...</string> +   </property>    </action> -   <action name="action_Report_Compatibility"> -     <property name="enabled"> -       <bool>false</bool> -     </property> -     <property name="text"> -       <string>Report Compatibility</string> -     </property> -     <property name="visible"> -       <bool>false</bool> -     </property> -   </action> -  </widget> +  <action name="action_Report_Compatibility"> +   <property name="enabled"> +    <bool>false</bool> +   </property> +   <property name="text"> +    <string>Report Compatibility</string> +   </property> +   <property name="visible"> +    <bool>false</bool> +   </property> +  </action> +  <action name="action_Open_yuzu_Folder"> +   <property name="text"> +    <string>Open yuzu Folder</string> +   </property> +  </action> +  <action name="action_Capture_Screenshot"> +   <property name="enabled"> +    <bool>false</bool> +   </property> +   <property name="text"> +    <string>Capture Screenshot</string> +   </property> +  </action> + </widget>   <resources/>   <connections/>  </ui> diff --git a/src/yuzu/ui_settings.h b/src/yuzu/ui_settings.h index 2e617d52a..82aaeedb0 100644 --- a/src/yuzu/ui_settings.h +++ b/src/yuzu/ui_settings.h @@ -5,10 +5,12 @@  #pragma once  #include <array> +#include <atomic>  #include <vector>  #include <QByteArray>  #include <QString>  #include <QStringList> +#include "common/common_types.h"  namespace UISettings { @@ -38,11 +40,16 @@ struct Values {      bool confirm_before_closing;      bool first_start; +    bool select_user_on_boot; +      // Discord RPC      bool enable_discord_presence; +    u16 screenshot_resolution_factor; +      QString roms_path;      QString symbols_path; +    QString screenshot_path;      QString gamedir;      bool gamedir_deepscan;      QStringList recent_files; @@ -57,11 +64,16 @@ struct Values {      // logging      bool show_console; +    // Controllers +    int profile_index; +      // Game List      bool show_unknown; +    bool show_add_ons;      uint32_t icon_size;      uint8_t row_1_text_id;      uint8_t row_2_text_id; +    std::atomic_bool is_game_list_reload_pending{false};  };  extern Values values; diff --git a/src/yuzu/util/limitable_input_dialog.cpp b/src/yuzu/util/limitable_input_dialog.cpp new file mode 100644 index 000000000..edd78e579 --- /dev/null +++ b/src/yuzu/util/limitable_input_dialog.cpp @@ -0,0 +1,59 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <QDialogButtonBox> +#include <QLabel> +#include <QLineEdit> +#include <QPushButton> +#include <QVBoxLayout> +#include "yuzu/util/limitable_input_dialog.h" + +LimitableInputDialog::LimitableInputDialog(QWidget* parent) : QDialog{parent} { +    CreateUI(); +    ConnectEvents(); +} + +LimitableInputDialog::~LimitableInputDialog() = default; + +void LimitableInputDialog::CreateUI() { +    setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + +    text_label = new QLabel(this); +    text_entry = new QLineEdit(this); +    buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); + +    auto* const layout = new QVBoxLayout; +    layout->addWidget(text_label); +    layout->addWidget(text_entry); +    layout->addWidget(buttons); + +    setLayout(layout); +} + +void LimitableInputDialog::ConnectEvents() { +    connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept); +    connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject); +} + +QString LimitableInputDialog::GetText(QWidget* parent, const QString& title, const QString& text, +                                      int min_character_limit, int max_character_limit) { +    Q_ASSERT(min_character_limit <= max_character_limit); + +    LimitableInputDialog dialog{parent}; +    dialog.setWindowTitle(title); +    dialog.text_label->setText(text); +    dialog.text_entry->setMaxLength(max_character_limit); + +    auto* const ok_button = dialog.buttons->button(QDialogButtonBox::Ok); +    ok_button->setEnabled(false); +    connect(dialog.text_entry, &QLineEdit::textEdited, [&](const QString& new_text) { +        ok_button->setEnabled(new_text.length() >= min_character_limit); +    }); + +    if (dialog.exec() != QDialog::Accepted) { +        return {}; +    } + +    return dialog.text_entry->text(); +} diff --git a/src/yuzu/util/limitable_input_dialog.h b/src/yuzu/util/limitable_input_dialog.h new file mode 100644 index 000000000..164ad7301 --- /dev/null +++ b/src/yuzu/util/limitable_input_dialog.h @@ -0,0 +1,31 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <QDialog> + +class QDialogButtonBox; +class QLabel; +class QLineEdit; + +/// A QDialog that functions similarly to QInputDialog, however, it allows +/// restricting the minimum and total number of characters that can be entered. +class LimitableInputDialog final : public QDialog { +    Q_OBJECT +public: +    explicit LimitableInputDialog(QWidget* parent = nullptr); +    ~LimitableInputDialog() override; + +    static QString GetText(QWidget* parent, const QString& title, const QString& text, +                           int min_character_limit, int max_character_limit); + +private: +    void CreateUI(); +    void ConnectEvents(); + +    QLabel* text_label; +    QLineEdit* text_entry; +    QDialogButtonBox* buttons; +}; | 
