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:
11
src/yuzu/util/clickable_label.cpp
Normal file
11
src/yuzu/util/clickable_label.cpp
Normal 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();
|
||||
}
|
21
src/yuzu/util/clickable_label.h
Normal file
21
src/yuzu/util/clickable_label.h
Normal 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);
|
||||
};
|
179
src/yuzu/util/controller_navigation.cpp
Normal file
179
src/yuzu/util/controller_navigation.cpp
Normal 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;
|
||||
}
|
||||
}
|
50
src/yuzu/util/controller_navigation.h
Normal file
50
src/yuzu/util/controller_navigation.h
Normal 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;
|
||||
};
|
88
src/yuzu/util/limitable_input_dialog.cpp
Normal file
88
src/yuzu/util/limitable_input_dialog.cpp
Normal 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);
|
||||
}
|
40
src/yuzu/util/limitable_input_dialog.h
Normal file
40
src/yuzu/util/limitable_input_dialog.h
Normal 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;
|
||||
};
|
268
src/yuzu/util/overlay_dialog.cpp
Normal file
268
src/yuzu/util/overlay_dialog.cpp
Normal 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);
|
||||
}
|
||||
}
|
108
src/yuzu/util/overlay_dialog.h
Normal file
108
src/yuzu/util/overlay_dialog.h
Normal 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{};
|
||||
};
|
404
src/yuzu/util/overlay_dialog.ui
Normal file
404
src/yuzu/util/overlay_dialog.ui
Normal 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><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||
p, li { white-space: pre-wrap; }
|
||||
</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:18pt; font-weight:400; font-style:normal;">
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html></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>
|
38
src/yuzu/util/sequence_dialog/sequence_dialog.cpp
Normal file
38
src/yuzu/util/sequence_dialog/sequence_dialog.cpp
Normal 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();
|
||||
}
|
23
src/yuzu/util/sequence_dialog/sequence_dialog.h
Normal file
23
src/yuzu/util/sequence_dialog/sequence_dialog.h
Normal 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;
|
||||
};
|
33
src/yuzu/util/url_request_interceptor.cpp
Normal file
33
src/yuzu/util/url_request_interceptor.cpp
Normal 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
|
29
src/yuzu/util/url_request_interceptor.h
Normal file
29
src/yuzu/util/url_request_interceptor.h
Normal 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
152
src/yuzu/util/util.cpp
Normal 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
29
src/yuzu/util/util.h
Normal 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);
|
Reference in New Issue
Block a user