4143675b2d
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. Co-authored-by: Its-Rei <kupfel@gmail.com>
249 lines
8.1 KiB
C++
249 lines
8.1 KiB
C++
// Copyright 2021 yuzu Emulator Project
|
|
// Licensed under GPLv2 or any later version
|
|
// Refer to the license.txt file included.
|
|
|
|
#include <QKeyEvent>
|
|
#include <QScreen>
|
|
|
|
#include "core/core.h"
|
|
#include "core/frontend/input_interpreter.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()) {
|
|
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);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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 = qApp->screenAt(pos)->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 <HIDButton... T>
|
|
void OverlayDialog::HandleButtonPressedOnce() {
|
|
const auto f = [this](HIDButton button) {
|
|
if (input_interpreter->IsButtonPressedOnce(button)) {
|
|
TranslateButtonPress(button);
|
|
}
|
|
};
|
|
|
|
(f(T), ...);
|
|
}
|
|
|
|
void OverlayDialog::TranslateButtonPress(HIDButton 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 HIDButton::A:
|
|
case HIDButton::B:
|
|
if (left_button->hasFocus()) {
|
|
left_button->click();
|
|
} else if (right_button->hasFocus()) {
|
|
right_button->click();
|
|
}
|
|
break;
|
|
case HIDButton::DLeft:
|
|
case HIDButton::LStickLeft:
|
|
focusPreviousChild();
|
|
break;
|
|
case HIDButton::DRight:
|
|
case HIDButton::LStickRight:
|
|
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<HIDButton::A, HIDButton::B, HIDButton::DLeft, HIDButton::DRight,
|
|
HIDButton::LStickLeft, HIDButton::LStickRight>();
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
|
}
|
|
}
|