1
0
mirror of https://git.suyu.dev/suyu/suyu synced 2025-09-09 15:56:32 -05:00

Revert "Merge branch 'master' into 'dev'"

This reverts merge request !17
This commit is contained in:
Crimson Hawk
2024-03-06 05:26:38 +00:00
parent fdadc50fff
commit cdbdf63ebe
2887 changed files with 18295 additions and 18366 deletions

View File

@@ -0,0 +1,11 @@
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "yuzu/util/clickable_label.h"
ClickableLabel::ClickableLabel(QWidget* parent, [[maybe_unused]] Qt::WindowFlags f)
: QLabel(parent) {}
void ClickableLabel::mouseReleaseEvent([[maybe_unused]] QMouseEvent* event) {
emit clicked();
}

View File

@@ -0,0 +1,21 @@
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <QLabel>
#include <QWidget>
class ClickableLabel : public QLabel {
Q_OBJECT
public:
explicit ClickableLabel(QWidget* parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags());
~ClickableLabel() = default;
signals:
void clicked();
protected:
void mouseReleaseEvent(QMouseEvent* event);
};

View File

@@ -0,0 +1,179 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/settings_input.h"
#include "hid_core/frontend/emulated_controller.h"
#include "hid_core/hid_core.h"
#include "yuzu/util/controller_navigation.h"
ControllerNavigation::ControllerNavigation(Core::HID::HIDCore& hid_core, QWidget* parent) {
player1_controller = hid_core.GetEmulatedController(Core::HID::NpadIdType::Player1);
handheld_controller = hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld);
Core::HID::ControllerUpdateCallback engine_callback{
.on_change = [this](Core::HID::ControllerTriggerType type) { ControllerUpdateEvent(type); },
.is_npad_service = false,
};
player1_callback_key = player1_controller->SetCallback(engine_callback);
handheld_callback_key = handheld_controller->SetCallback(engine_callback);
is_controller_set = true;
}
ControllerNavigation::~ControllerNavigation() {
UnloadController();
}
void ControllerNavigation::UnloadController() {
if (is_controller_set) {
player1_controller->DeleteCallback(player1_callback_key);
handheld_controller->DeleteCallback(handheld_callback_key);
is_controller_set = false;
}
}
void ControllerNavigation::TriggerButton(Settings::NativeButton::Values native_button,
Qt::Key key) {
if (button_values[native_button].value && !button_values[native_button].locked) {
emit TriggerKeyboardEvent(key);
}
}
void ControllerNavigation::ControllerUpdateEvent(Core::HID::ControllerTriggerType type) {
std::scoped_lock lock{mutex};
if (!Settings::values.controller_navigation) {
return;
}
if (type == Core::HID::ControllerTriggerType::Button) {
ControllerUpdateButton();
return;
}
if (type == Core::HID::ControllerTriggerType::Stick) {
ControllerUpdateStick();
return;
}
}
void ControllerNavigation::ControllerUpdateButton() {
const auto controller_type = player1_controller->GetNpadStyleIndex();
const auto& player1_buttons = player1_controller->GetButtonsValues();
const auto& handheld_buttons = handheld_controller->GetButtonsValues();
for (std::size_t i = 0; i < player1_buttons.size(); ++i) {
const bool button = player1_buttons[i].value || handheld_buttons[i].value;
// Trigger only once
button_values[i].locked = button == button_values[i].value;
button_values[i].value = button;
}
switch (controller_type) {
case Core::HID::NpadStyleIndex::Fullkey:
case Core::HID::NpadStyleIndex::JoyconDual:
case Core::HID::NpadStyleIndex::Handheld:
case Core::HID::NpadStyleIndex::GameCube:
TriggerButton(Settings::NativeButton::A, Qt::Key_Enter);
TriggerButton(Settings::NativeButton::B, Qt::Key_Escape);
TriggerButton(Settings::NativeButton::DDown, Qt::Key_Down);
TriggerButton(Settings::NativeButton::DLeft, Qt::Key_Left);
TriggerButton(Settings::NativeButton::DRight, Qt::Key_Right);
TriggerButton(Settings::NativeButton::DUp, Qt::Key_Up);
break;
case Core::HID::NpadStyleIndex::JoyconLeft:
TriggerButton(Settings::NativeButton::DDown, Qt::Key_Enter);
TriggerButton(Settings::NativeButton::DLeft, Qt::Key_Escape);
break;
case Core::HID::NpadStyleIndex::JoyconRight:
TriggerButton(Settings::NativeButton::X, Qt::Key_Enter);
TriggerButton(Settings::NativeButton::A, Qt::Key_Escape);
break;
default:
break;
}
}
void ControllerNavigation::ControllerUpdateStick() {
const auto controller_type = player1_controller->GetNpadStyleIndex();
const auto& player1_sticks = player1_controller->GetSticksValues();
const auto& handheld_sticks = player1_controller->GetSticksValues();
bool update = false;
for (std::size_t i = 0; i < player1_sticks.size(); ++i) {
const Common::Input::StickStatus stick{
.left = player1_sticks[i].left || handheld_sticks[i].left,
.right = player1_sticks[i].right || handheld_sticks[i].right,
.up = player1_sticks[i].up || handheld_sticks[i].up,
.down = player1_sticks[i].down || handheld_sticks[i].down,
};
// Trigger only once
if (stick.down != stick_values[i].down || stick.left != stick_values[i].left ||
stick.right != stick_values[i].right || stick.up != stick_values[i].up) {
update = true;
}
stick_values[i] = stick;
}
if (!update) {
return;
}
switch (controller_type) {
case Core::HID::NpadStyleIndex::Fullkey:
case Core::HID::NpadStyleIndex::JoyconDual:
case Core::HID::NpadStyleIndex::Handheld:
case Core::HID::NpadStyleIndex::GameCube:
if (stick_values[Settings::NativeAnalog::LStick].down) {
emit TriggerKeyboardEvent(Qt::Key_Down);
return;
}
if (stick_values[Settings::NativeAnalog::LStick].left) {
emit TriggerKeyboardEvent(Qt::Key_Left);
return;
}
if (stick_values[Settings::NativeAnalog::LStick].right) {
emit TriggerKeyboardEvent(Qt::Key_Right);
return;
}
if (stick_values[Settings::NativeAnalog::LStick].up) {
emit TriggerKeyboardEvent(Qt::Key_Up);
return;
}
break;
case Core::HID::NpadStyleIndex::JoyconLeft:
if (stick_values[Settings::NativeAnalog::LStick].left) {
emit TriggerKeyboardEvent(Qt::Key_Down);
return;
}
if (stick_values[Settings::NativeAnalog::LStick].up) {
emit TriggerKeyboardEvent(Qt::Key_Left);
return;
}
if (stick_values[Settings::NativeAnalog::LStick].down) {
emit TriggerKeyboardEvent(Qt::Key_Right);
return;
}
if (stick_values[Settings::NativeAnalog::LStick].right) {
emit TriggerKeyboardEvent(Qt::Key_Up);
return;
}
break;
case Core::HID::NpadStyleIndex::JoyconRight:
if (stick_values[Settings::NativeAnalog::RStick].right) {
emit TriggerKeyboardEvent(Qt::Key_Down);
return;
}
if (stick_values[Settings::NativeAnalog::RStick].down) {
emit TriggerKeyboardEvent(Qt::Key_Left);
return;
}
if (stick_values[Settings::NativeAnalog::RStick].up) {
emit TriggerKeyboardEvent(Qt::Key_Right);
return;
}
if (stick_values[Settings::NativeAnalog::RStick].left) {
emit TriggerKeyboardEvent(Qt::Key_Up);
return;
}
break;
default:
break;
}
}

View File

@@ -0,0 +1,50 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <QKeyEvent>
#include <QObject>
#include "common/input.h"
#include "common/settings_input.h"
namespace Core::HID {
using ButtonValues = std::array<Common::Input::ButtonStatus, Settings::NativeButton::NumButtons>;
using SticksValues = std::array<Common::Input::StickStatus, Settings::NativeAnalog::NumAnalogs>;
enum class ControllerTriggerType;
class EmulatedController;
class HIDCore;
} // namespace Core::HID
class ControllerNavigation : public QObject {
Q_OBJECT
public:
explicit ControllerNavigation(Core::HID::HIDCore& hid_core, QWidget* parent = nullptr);
~ControllerNavigation();
/// Disables events from the emulated controller
void UnloadController();
signals:
void TriggerKeyboardEvent(Qt::Key key);
private:
void TriggerButton(Settings::NativeButton::Values native_button, Qt::Key key);
void ControllerUpdateEvent(Core::HID::ControllerTriggerType type);
void ControllerUpdateButton();
void ControllerUpdateStick();
Core::HID::ButtonValues button_values{};
Core::HID::SticksValues stick_values{};
int player1_callback_key{};
int handheld_callback_key{};
bool is_controller_set{};
mutable std::mutex mutex;
Core::HID::EmulatedController* player1_controller;
Core::HID::EmulatedController* handheld_controller;
};

View File

@@ -0,0 +1,88 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#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() {
text_label = new QLabel(this);
text_entry = new QLineEdit(this);
text_label_invalid = new QLabel(this);
buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
auto* const layout = new QVBoxLayout;
layout->addWidget(text_label);
layout->addWidget(text_entry);
layout->addWidget(text_label_invalid);
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,
InputLimiter limit_type) {
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);
dialog.text_label_invalid->show();
switch (limit_type) {
case InputLimiter::Filesystem:
dialog.invalid_characters = QStringLiteral("<>:;\"/\\|,.!?*");
break;
default:
dialog.invalid_characters.clear();
dialog.text_label_invalid->hide();
break;
}
dialog.text_label_invalid->setText(
tr("The text can't contain any of the following characters:\n%1")
.arg(dialog.invalid_characters));
auto* const ok_button = dialog.buttons->button(QDialogButtonBox::Ok);
ok_button->setEnabled(false);
connect(dialog.text_entry, &QLineEdit::textEdited, [&] {
if (!dialog.invalid_characters.isEmpty()) {
dialog.RemoveInvalidCharacters();
}
ok_button->setEnabled(dialog.text_entry->text().length() >= min_character_limit);
});
if (dialog.exec() != QDialog::Accepted) {
return {};
}
return dialog.text_entry->text();
}
void LimitableInputDialog::RemoveInvalidCharacters() {
auto cpos = text_entry->cursorPosition();
for (int i = 0; i < text_entry->text().length(); i++) {
if (invalid_characters.contains(text_entry->text().at(i))) {
text_entry->setText(text_entry->text().remove(i, 1));
i--;
cpos--;
}
}
text_entry->setCursorPosition(cpos);
}

View File

@@ -0,0 +1,40 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#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;
enum class InputLimiter {
None,
Filesystem,
};
static QString GetText(QWidget* parent, const QString& title, const QString& text,
int min_character_limit, int max_character_limit,
InputLimiter limit_type = InputLimiter::None);
private:
void CreateUI();
void ConnectEvents();
void RemoveInvalidCharacters();
QString invalid_characters;
QLabel* text_label;
QLineEdit* text_entry;
QLabel* text_label_invalid;
QDialogButtonBox* buttons;
};

View File

@@ -0,0 +1,268 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <QKeyEvent>
#include <QScreen>
#include <QWindow>
#include "core/core.h"
#include "hid_core/frontend/input_interpreter.h"
#include "hid_core/hid_types.h"
#include "ui_overlay_dialog.h"
#include "yuzu/util/overlay_dialog.h"
namespace {
constexpr float BASE_TITLE_FONT_SIZE = 14.0f;
constexpr float BASE_FONT_SIZE = 18.0f;
constexpr float BASE_WIDTH = 1280.0f;
constexpr float BASE_HEIGHT = 720.0f;
} // Anonymous namespace
OverlayDialog::OverlayDialog(QWidget* parent, Core::System& system, const QString& title_text,
const QString& body_text, const QString& left_button_text,
const QString& right_button_text, Qt::Alignment alignment,
bool use_rich_text_)
: QDialog(parent), ui{std::make_unique<Ui::OverlayDialog>()}, use_rich_text{use_rich_text_} {
ui->setupUi(this);
setWindowFlags(Qt::Dialog | Qt::FramelessWindowHint | Qt::WindowTitleHint |
Qt::WindowSystemMenuHint | Qt::CustomizeWindowHint);
setWindowModality(Qt::WindowModal);
setAttribute(Qt::WA_TranslucentBackground);
if (use_rich_text) {
InitializeRichTextDialog(title_text, body_text, left_button_text, right_button_text,
alignment);
} else {
InitializeRegularTextDialog(title_text, body_text, left_button_text, right_button_text,
alignment);
}
MoveAndResizeWindow();
// TODO (Morph): Remove this when InputInterpreter no longer relies on the HID backend
if (system.IsPoweredOn() && !ui->buttonsDialog->isHidden()) {
input_interpreter = std::make_unique<InputInterpreter>(system);
StartInputThread();
}
}
OverlayDialog::~OverlayDialog() {
StopInputThread();
}
void OverlayDialog::InitializeRegularTextDialog(const QString& title_text, const QString& body_text,
const QString& left_button_text,
const QString& right_button_text,
Qt::Alignment alignment) {
ui->stackedDialog->setCurrentIndex(0);
ui->label_title->setText(title_text);
ui->label_dialog->setText(body_text);
ui->button_cancel->setText(left_button_text);
ui->button_ok_label->setText(right_button_text);
ui->label_dialog->setAlignment(alignment);
if (title_text.isEmpty()) {
ui->label_title->hide();
ui->verticalLayout_2->setStretch(0, 0);
ui->verticalLayout_2->setStretch(1, 219);
ui->verticalLayout_2->setStretch(2, 82);
}
if (left_button_text.isEmpty()) {
ui->button_cancel->hide();
ui->button_cancel->setEnabled(false);
}
if (right_button_text.isEmpty()) {
ui->button_ok_label->hide();
ui->button_ok_label->setEnabled(false);
}
if (ui->button_cancel->isHidden() && ui->button_ok_label->isHidden()) {
ui->buttonsDialog->hide();
return;
}
connect(
ui->button_cancel, &QPushButton::clicked, this,
[this](bool) {
StopInputThread();
QDialog::reject();
},
Qt::QueuedConnection);
connect(
ui->button_ok_label, &QPushButton::clicked, this,
[this](bool) {
StopInputThread();
QDialog::accept();
},
Qt::QueuedConnection);
}
void OverlayDialog::InitializeRichTextDialog(const QString& title_text, const QString& body_text,
const QString& left_button_text,
const QString& right_button_text,
Qt::Alignment alignment) {
ui->stackedDialog->setCurrentIndex(1);
ui->label_title_rich->setText(title_text);
ui->text_browser_dialog->setText(body_text);
ui->button_cancel_rich->setText(left_button_text);
ui->button_ok_rich->setText(right_button_text);
// TODO (Morph/Rei): Replace this with something that works better
ui->text_browser_dialog->setAlignment(alignment);
if (title_text.isEmpty()) {
ui->label_title_rich->hide();
ui->verticalLayout_3->setStretch(0, 0);
ui->verticalLayout_3->setStretch(1, 438);
ui->verticalLayout_3->setStretch(2, 82);
}
if (left_button_text.isEmpty()) {
ui->button_cancel_rich->hide();
ui->button_cancel_rich->setEnabled(false);
}
if (right_button_text.isEmpty()) {
ui->button_ok_rich->hide();
ui->button_ok_rich->setEnabled(false);
}
if (ui->button_cancel_rich->isHidden() && ui->button_ok_rich->isHidden()) {
ui->buttonsRichDialog->hide();
return;
}
connect(
ui->button_cancel_rich, &QPushButton::clicked, this,
[this](bool) {
StopInputThread();
QDialog::reject();
},
Qt::QueuedConnection);
connect(
ui->button_ok_rich, &QPushButton::clicked, this,
[this](bool) {
StopInputThread();
QDialog::accept();
},
Qt::QueuedConnection);
}
void OverlayDialog::MoveAndResizeWindow() {
const auto pos = parentWidget()->mapToGlobal(parentWidget()->rect().topLeft());
const auto width = static_cast<float>(parentWidget()->width());
const auto height = static_cast<float>(parentWidget()->height());
// High DPI
const float dpi_scale = screen()->logicalDotsPerInch() / 96.0f;
const auto title_text_font_size = BASE_TITLE_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale;
const auto body_text_font_size =
BASE_FONT_SIZE * (((width / BASE_WIDTH) + (height / BASE_HEIGHT)) / 2.0f) / dpi_scale;
const auto button_text_font_size = BASE_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale;
QFont title_text_font(QStringLiteral("MS Shell Dlg 2"), title_text_font_size, QFont::Normal);
QFont body_text_font(QStringLiteral("MS Shell Dlg 2"), body_text_font_size, QFont::Normal);
QFont button_text_font(QStringLiteral("MS Shell Dlg 2"), button_text_font_size, QFont::Normal);
if (use_rich_text) {
ui->label_title_rich->setFont(title_text_font);
ui->text_browser_dialog->setFont(body_text_font);
ui->button_cancel_rich->setFont(button_text_font);
ui->button_ok_rich->setFont(button_text_font);
} else {
ui->label_title->setFont(title_text_font);
ui->label_dialog->setFont(body_text_font);
ui->button_cancel->setFont(button_text_font);
ui->button_ok_label->setFont(button_text_font);
}
QDialog::move(pos);
QDialog::resize(width, height);
}
template <Core::HID::NpadButton... T>
void OverlayDialog::HandleButtonPressedOnce() {
const auto f = [this](Core::HID::NpadButton button) {
if (input_interpreter->IsButtonPressedOnce(button)) {
TranslateButtonPress(button);
}
};
(f(T), ...);
}
void OverlayDialog::TranslateButtonPress(Core::HID::NpadButton button) {
QPushButton* left_button = use_rich_text ? ui->button_cancel_rich : ui->button_cancel;
QPushButton* right_button = use_rich_text ? ui->button_ok_rich : ui->button_ok_label;
// TODO (Morph): Handle QTextBrowser text scrolling
// TODO (Morph): focusPrevious/NextChild() doesn't work well with the rich text dialog, fix it
switch (button) {
case Core::HID::NpadButton::A:
case Core::HID::NpadButton::B:
if (left_button->hasFocus()) {
left_button->click();
} else if (right_button->hasFocus()) {
right_button->click();
}
break;
case Core::HID::NpadButton::Left:
case Core::HID::NpadButton::StickLLeft:
focusPreviousChild();
break;
case Core::HID::NpadButton::Right:
case Core::HID::NpadButton::StickLRight:
focusNextChild();
break;
default:
break;
}
}
void OverlayDialog::StartInputThread() {
if (input_thread_running) {
return;
}
input_thread_running = true;
input_thread = std::thread(&OverlayDialog::InputThread, this);
}
void OverlayDialog::StopInputThread() {
input_thread_running = false;
if (input_thread.joinable()) {
input_thread.join();
}
}
void OverlayDialog::InputThread() {
while (input_thread_running) {
input_interpreter->PollInput();
HandleButtonPressedOnce<Core::HID::NpadButton::A, Core::HID::NpadButton::B,
Core::HID::NpadButton::Left, Core::HID::NpadButton::Right,
Core::HID::NpadButton::StickLLeft,
Core::HID::NpadButton::StickLRight>();
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
}
void OverlayDialog::keyPressEvent(QKeyEvent* e) {
if (!ui->buttonsDialog->isHidden() || e->key() != Qt::Key_Escape) {
QDialog::keyPressEvent(e);
}
}

View File

@@ -0,0 +1,108 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <atomic>
#include <memory>
#include <thread>
#include <QDialog>
#include "common/common_types.h"
class InputInterpreter;
namespace Core {
class System;
}
namespace Core::HID {
enum class NpadButton : u64;
}
namespace Ui {
class OverlayDialog;
}
/**
* An OverlayDialog is an interactive dialog that accepts controller input (while a game is running)
* This dialog attempts to replicate the look and feel of the Nintendo Switch's overlay dialogs and
* provide some extra features such as embedding HTML/Rich Text content in a QTextBrowser.
* The OverlayDialog provides 2 modes: one to embed regular text into a QLabel and another to embed
* HTML/Rich Text content into a QTextBrowser.
*/
class OverlayDialog final : public QDialog {
Q_OBJECT
public:
explicit OverlayDialog(QWidget* parent, Core::System& system, const QString& title_text,
const QString& body_text, const QString& left_button_text,
const QString& right_button_text,
Qt::Alignment alignment = Qt::AlignCenter, bool use_rich_text_ = false);
~OverlayDialog() override;
private:
/**
* Initializes a text dialog with a QLabel storing text.
* Only use this for short text as the dialog buttons would be squashed with longer text.
*
* @param title_text Title text to be displayed
* @param body_text Main text to be displayed
* @param left_button_text Left button text. If empty, the button is hidden and disabled
* @param right_button_text Right button text. If empty, the button is hidden and disabled
* @param alignment Main text alignment
*/
void InitializeRegularTextDialog(const QString& title_text, const QString& body_text,
const QString& left_button_text,
const QString& right_button_text, Qt::Alignment alignment);
/**
* Initializes a text dialog with a QTextBrowser storing text.
* This is ideal for longer text or rich text content. A scrollbar is shown for longer text.
*
* @param title_text Title text to be displayed
* @param body_text Main text to be displayed
* @param left_button_text Left button text. If empty, the button is hidden and disabled
* @param right_button_text Right button text. If empty, the button is hidden and disabled
* @param alignment Main text alignment
*/
void InitializeRichTextDialog(const QString& title_text, const QString& body_text,
const QString& left_button_text, const QString& right_button_text,
Qt::Alignment alignment);
/// Moves and resizes the dialog to be fully overlaid on top of the parent window.
void MoveAndResizeWindow();
/**
* Handles button presses and converts them into keyboard input.
*
* @tparam HIDButton The list of buttons that can be converted into keyboard input.
*/
template <Core::HID::NpadButton... T>
void HandleButtonPressedOnce();
/**
* Translates a button press to focus or click either the left or right buttons.
*
* @param button The button press to process.
*/
void TranslateButtonPress(Core::HID::NpadButton button);
void StartInputThread();
void StopInputThread();
/// The thread where input is being polled and processed.
void InputThread();
void keyPressEvent(QKeyEvent* e) override;
std::unique_ptr<Ui::OverlayDialog> ui;
bool use_rich_text;
std::unique_ptr<InputInterpreter> input_interpreter;
std::thread input_thread;
std::atomic<bool> input_thread_running{};
};

View File

@@ -0,0 +1,404 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>OverlayDialog</class>
<widget class="QDialog" name="OverlayDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1280</width>
<height>720</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<property name="styleSheet">
<string notr="true"/>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QStackedWidget" name="stackedDialog">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="lineDialog">
<layout class="QGridLayout" name="lineDialogGridLayout" rowstretch="210,300,210" columnstretch="250,780,250">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<property name="spacing">
<number>0</number>
</property>
<item row="1" column="1">
<widget class="QWidget" name="contentDialog" native="true">
<layout class="QVBoxLayout" name="verticalLayout_2" stretch="70,149,82">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_title">
<property name="font">
<font>
<pointsize>14</pointsize>
</font>
</property>
<property name="alignment">
<set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_dialog">
<property name="font">
<font>
<pointsize>18</pointsize>
</font>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="buttonsDialog" native="true">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QPushButton" name="button_cancel">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<pointsize>18</pointsize>
</font>
</property>
<property name="text">
<string>Cancel</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="button_ok_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<pointsize>18</pointsize>
</font>
</property>
<property name="text">
<string>OK</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="1">
<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 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="2" 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="1" column="2">
<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>
</layout>
</widget>
<widget class="QWidget" name="richDialog">
<layout class="QGridLayout" name="richDialogGridLayout" rowstretch="100,520,100" columnstretch="165,950,165">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<property name="spacing">
<number>0</number>
</property>
<item row="1" column="0">
<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="2" column="1">
<spacer name="verticalSpacer_4">
<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="1">
<spacer name="verticalSpacer_3">
<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="1" column="1">
<widget class="QWidget" name="contentRichDialog" native="true">
<layout class="QVBoxLayout" name="verticalLayout_3" stretch="70,368,82">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_title_rich">
<property name="font">
<font>
<pointsize>14</pointsize>
</font>
</property>
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set>
</property>
</widget>
</item>
<item>
<widget class="QTextBrowser" name="text_browser_dialog">
<property name="font">
<font>
<pointsize>18</pointsize>
</font>
</property>
<property name="html">
<string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'MS Shell Dlg 2'; font-size:18pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="buttonsRichDialog" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QPushButton" name="button_cancel_rich">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<pointsize>18</pointsize>
</font>
</property>
<property name="text">
<string>Cancel</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="button_ok_rich">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<pointsize>18</pointsize>
</font>
</property>
<property name="text">
<string>OK</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="2">
<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>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<resources>
<include location="../../../dist/icons/overlay/overlay.qrc"/>
</resources>
<connections/>
</ui>

View File

@@ -0,0 +1,38 @@
// SPDX-FileCopyrightText: 2018 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <QDialogButtonBox>
#include <QKeySequenceEdit>
#include <QVBoxLayout>
#include "yuzu/util/sequence_dialog/sequence_dialog.h"
SequenceDialog::SequenceDialog(QWidget* parent) : QDialog(parent) {
setWindowTitle(tr("Enter a hotkey"));
key_sequence = new QKeySequenceEdit;
auto* const buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
buttons->setCenterButtons(true);
auto* const layout = new QVBoxLayout(this);
layout->addWidget(key_sequence);
layout->addWidget(buttons);
connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
}
SequenceDialog::~SequenceDialog() = default;
QKeySequence SequenceDialog::GetSequence() const {
// Only the first key is returned. The other 3, if present, are ignored.
return QKeySequence(key_sequence->keySequence()[0]);
}
bool SequenceDialog::focusNextPrevChild(bool next) {
return false;
}
void SequenceDialog::closeEvent(QCloseEvent*) {
reject();
}

View File

@@ -0,0 +1,23 @@
// SPDX-FileCopyrightText: 2018 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <QDialog>
class QKeySequenceEdit;
class SequenceDialog : public QDialog {
Q_OBJECT
public:
explicit SequenceDialog(QWidget* parent = nullptr);
~SequenceDialog() override;
QKeySequence GetSequence() const;
void closeEvent(QCloseEvent*) override;
private:
QKeySequenceEdit* key_sequence;
bool focusNextPrevChild(bool next) override;
};

View File

@@ -0,0 +1,33 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#ifdef YUZU_USE_QT_WEB_ENGINE
#include "yuzu/util/url_request_interceptor.h"
UrlRequestInterceptor::UrlRequestInterceptor(QObject* p) : QWebEngineUrlRequestInterceptor(p) {}
UrlRequestInterceptor::~UrlRequestInterceptor() = default;
void UrlRequestInterceptor::interceptRequest(QWebEngineUrlRequestInfo& info) {
const auto resource_type = info.resourceType();
switch (resource_type) {
case QWebEngineUrlRequestInfo::ResourceTypeMainFrame:
requested_url = info.requestUrl();
emit FrameChanged();
break;
case QWebEngineUrlRequestInfo::ResourceTypeSubFrame:
case QWebEngineUrlRequestInfo::ResourceTypeXhr:
emit FrameChanged();
break;
default:
break;
}
}
QUrl UrlRequestInterceptor::GetRequestedURL() const {
return requested_url;
}
#endif

View File

@@ -0,0 +1,29 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#ifdef YUZU_USE_QT_WEB_ENGINE
#include <QObject>
#include <QWebEngineUrlRequestInterceptor>
class UrlRequestInterceptor : public QWebEngineUrlRequestInterceptor {
Q_OBJECT
public:
explicit UrlRequestInterceptor(QObject* p = nullptr);
~UrlRequestInterceptor() override;
void interceptRequest(QWebEngineUrlRequestInfo& info) override;
QUrl GetRequestedURL() const;
signals:
void FrameChanged();
private:
QUrl requested_url;
};
#endif

152
src/yuzu/util/util.cpp Normal file
View File

@@ -0,0 +1,152 @@
// SPDX-FileCopyrightText: 2015 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
#include <cmath>
#include <QPainter>
#include "common/logging/log.h"
#include "yuzu/util/util.h"
#ifdef _WIN32
#include <windows.h>
#include "common/fs/file.h"
#endif
QFont GetMonospaceFont() {
QFont font(QStringLiteral("monospace"));
// Automatic fallback to a monospace font on on platforms without a font called "monospace"
font.setStyleHint(QFont::Monospace);
font.setFixedPitch(true);
return font;
}
QString ReadableByteSize(qulonglong size) {
static constexpr std::array units{"B", "KiB", "MiB", "GiB", "TiB", "PiB"};
if (size == 0) {
return QStringLiteral("0");
}
const int digit_groups = std::min(static_cast<int>(std::log10(size) / std::log10(1024)),
static_cast<int>(units.size()));
return QStringLiteral("%L1 %2")
.arg(size / std::pow(1024, digit_groups), 0, 'f', 1)
.arg(QString::fromUtf8(units[digit_groups]));
}
QPixmap CreateCirclePixmapFromColor(const QColor& color) {
QPixmap circle_pixmap(16, 16);
circle_pixmap.fill(Qt::transparent);
QPainter painter(&circle_pixmap);
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(color);
painter.setBrush(color);
painter.drawEllipse({circle_pixmap.width() / 2.0, circle_pixmap.height() / 2.0}, 7.0, 7.0);
return circle_pixmap;
}
bool SaveIconToFile(const std::filesystem::path& icon_path, const QImage& image) {
#if defined(WIN32)
#pragma pack(push, 2)
struct IconDir {
WORD id_reserved;
WORD id_type;
WORD id_count;
};
struct IconDirEntry {
BYTE width;
BYTE height;
BYTE color_count;
BYTE reserved;
WORD planes;
WORD bit_count;
DWORD bytes_in_res;
DWORD image_offset;
};
#pragma pack(pop)
const QImage source_image = image.convertToFormat(QImage::Format_RGB32);
constexpr std::array<int, 7> scale_sizes{256, 128, 64, 48, 32, 24, 16};
constexpr int bytes_per_pixel = 4;
const IconDir icon_dir{
.id_reserved = 0,
.id_type = 1,
.id_count = static_cast<WORD>(scale_sizes.size()),
};
Common::FS::IOFile icon_file(icon_path.string(), Common::FS::FileAccessMode::Write,
Common::FS::FileType::BinaryFile);
if (!icon_file.IsOpen()) {
return false;
}
if (!icon_file.Write(icon_dir)) {
return false;
}
std::size_t image_offset = sizeof(IconDir) + (sizeof(IconDirEntry) * scale_sizes.size());
for (std::size_t i = 0; i < scale_sizes.size(); i++) {
const int image_size = scale_sizes[i] * scale_sizes[i] * bytes_per_pixel;
const IconDirEntry icon_entry{
.width = static_cast<BYTE>(scale_sizes[i]),
.height = static_cast<BYTE>(scale_sizes[i]),
.color_count = 0,
.reserved = 0,
.planes = 1,
.bit_count = bytes_per_pixel * 8,
.bytes_in_res = static_cast<DWORD>(sizeof(BITMAPINFOHEADER) + image_size),
.image_offset = static_cast<DWORD>(image_offset),
};
image_offset += icon_entry.bytes_in_res;
if (!icon_file.Write(icon_entry)) {
return false;
}
}
for (std::size_t i = 0; i < scale_sizes.size(); i++) {
const QImage scaled_image = source_image.scaled(
scale_sizes[i], scale_sizes[i], Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
const BITMAPINFOHEADER info_header{
.biSize = sizeof(BITMAPINFOHEADER),
.biWidth = scaled_image.width(),
.biHeight = scaled_image.height() * 2,
.biPlanes = 1,
.biBitCount = bytes_per_pixel * 8,
.biCompression = BI_RGB,
.biSizeImage{},
.biXPelsPerMeter{},
.biYPelsPerMeter{},
.biClrUsed{},
.biClrImportant{},
};
if (!icon_file.Write(info_header)) {
return false;
}
for (int y = 0; y < scaled_image.height(); y++) {
const auto* line = scaled_image.scanLine(scaled_image.height() - 1 - y);
std::vector<u8> line_data(scaled_image.width() * bytes_per_pixel);
std::memcpy(line_data.data(), line, line_data.size());
if (!icon_file.Write(line_data)) {
return false;
}
}
}
icon_file.Close();
return true;
#elif defined(__linux__) || defined(__FreeBSD__)
// Convert and write the icon as a PNG
if (!image.save(QString::fromStdString(icon_path.string()))) {
LOG_ERROR(Frontend, "Could not write icon as PNG to file");
} else {
LOG_INFO(Frontend, "Wrote an icon to {}", icon_path.string());
}
return true;
#else
return false;
#endif
}

29
src/yuzu/util/util.h Normal file
View File

@@ -0,0 +1,29 @@
// SPDX-FileCopyrightText: 2015 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <filesystem>
#include <QFont>
#include <QString>
/// Returns a QFont object appropriate to use as a monospace font for debugging widgets, etc.
[[nodiscard]] QFont GetMonospaceFont();
/// Convert a size in bytes into a readable format (KiB, MiB, etc.)
[[nodiscard]] QString ReadableByteSize(qulonglong size);
/**
* Creates a circle pixmap from a specified color
* @param color The color the pixmap shall have
* @return QPixmap circle pixmap
*/
[[nodiscard]] QPixmap CreateCirclePixmapFromColor(const QColor& color);
/**
* Saves a windows icon to a file
* @param path The icons path
* @param image The image to save
* @return bool If the operation succeeded
*/
[[nodiscard]] bool SaveIconToFile(const std::filesystem::path& icon_path, const QImage& image);