overlay_dialog.cpp 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
  2. // SPDX-License-Identifier: GPL-2.0-or-later
  3. #include <QKeyEvent>
  4. #include <QScreen>
  5. #include <QWindow>
  6. #include "core/core.h"
  7. #include "core/hid/hid_types.h"
  8. #include "core/hid/input_interpreter.h"
  9. #include "ui_overlay_dialog.h"
  10. #include "yuzu/util/overlay_dialog.h"
  11. namespace {
  12. constexpr float BASE_TITLE_FONT_SIZE = 14.0f;
  13. constexpr float BASE_FONT_SIZE = 18.0f;
  14. constexpr float BASE_WIDTH = 1280.0f;
  15. constexpr float BASE_HEIGHT = 720.0f;
  16. } // Anonymous namespace
  17. OverlayDialog::OverlayDialog(QWidget* parent, Core::System& system, const QString& title_text,
  18. const QString& body_text, const QString& left_button_text,
  19. const QString& right_button_text, Qt::Alignment alignment,
  20. bool use_rich_text_)
  21. : QDialog(parent), ui{std::make_unique<Ui::OverlayDialog>()}, use_rich_text{use_rich_text_} {
  22. ui->setupUi(this);
  23. setWindowFlags(Qt::Dialog | Qt::FramelessWindowHint | Qt::WindowTitleHint |
  24. Qt::WindowSystemMenuHint | Qt::CustomizeWindowHint);
  25. setWindowModality(Qt::WindowModal);
  26. setAttribute(Qt::WA_TranslucentBackground);
  27. if (use_rich_text) {
  28. InitializeRichTextDialog(title_text, body_text, left_button_text, right_button_text,
  29. alignment);
  30. } else {
  31. InitializeRegularTextDialog(title_text, body_text, left_button_text, right_button_text,
  32. alignment);
  33. }
  34. MoveAndResizeWindow();
  35. // TODO (Morph): Remove this when InputInterpreter no longer relies on the HID backend
  36. if (system.IsPoweredOn() && !ui->buttonsDialog->isHidden()) {
  37. input_interpreter = std::make_unique<InputInterpreter>(system);
  38. StartInputThread();
  39. }
  40. }
  41. OverlayDialog::~OverlayDialog() {
  42. StopInputThread();
  43. }
  44. void OverlayDialog::InitializeRegularTextDialog(const QString& title_text, const QString& body_text,
  45. const QString& left_button_text,
  46. const QString& right_button_text,
  47. Qt::Alignment alignment) {
  48. ui->stackedDialog->setCurrentIndex(0);
  49. ui->label_title->setText(title_text);
  50. ui->label_dialog->setText(body_text);
  51. ui->button_cancel->setText(left_button_text);
  52. ui->button_ok_label->setText(right_button_text);
  53. ui->label_dialog->setAlignment(alignment);
  54. if (title_text.isEmpty()) {
  55. ui->label_title->hide();
  56. ui->verticalLayout_2->setStretch(0, 0);
  57. ui->verticalLayout_2->setStretch(1, 219);
  58. ui->verticalLayout_2->setStretch(2, 82);
  59. }
  60. if (left_button_text.isEmpty()) {
  61. ui->button_cancel->hide();
  62. ui->button_cancel->setEnabled(false);
  63. }
  64. if (right_button_text.isEmpty()) {
  65. ui->button_ok_label->hide();
  66. ui->button_ok_label->setEnabled(false);
  67. }
  68. if (ui->button_cancel->isHidden() && ui->button_ok_label->isHidden()) {
  69. ui->buttonsDialog->hide();
  70. return;
  71. }
  72. connect(
  73. ui->button_cancel, &QPushButton::clicked, this,
  74. [this](bool) {
  75. StopInputThread();
  76. QDialog::reject();
  77. },
  78. Qt::QueuedConnection);
  79. connect(
  80. ui->button_ok_label, &QPushButton::clicked, this,
  81. [this](bool) {
  82. StopInputThread();
  83. QDialog::accept();
  84. },
  85. Qt::QueuedConnection);
  86. }
  87. void OverlayDialog::InitializeRichTextDialog(const QString& title_text, const QString& body_text,
  88. const QString& left_button_text,
  89. const QString& right_button_text,
  90. Qt::Alignment alignment) {
  91. ui->stackedDialog->setCurrentIndex(1);
  92. ui->label_title_rich->setText(title_text);
  93. ui->text_browser_dialog->setText(body_text);
  94. ui->button_cancel_rich->setText(left_button_text);
  95. ui->button_ok_rich->setText(right_button_text);
  96. // TODO (Morph/Rei): Replace this with something that works better
  97. ui->text_browser_dialog->setAlignment(alignment);
  98. if (title_text.isEmpty()) {
  99. ui->label_title_rich->hide();
  100. ui->verticalLayout_3->setStretch(0, 0);
  101. ui->verticalLayout_3->setStretch(1, 438);
  102. ui->verticalLayout_3->setStretch(2, 82);
  103. }
  104. if (left_button_text.isEmpty()) {
  105. ui->button_cancel_rich->hide();
  106. ui->button_cancel_rich->setEnabled(false);
  107. }
  108. if (right_button_text.isEmpty()) {
  109. ui->button_ok_rich->hide();
  110. ui->button_ok_rich->setEnabled(false);
  111. }
  112. if (ui->button_cancel_rich->isHidden() && ui->button_ok_rich->isHidden()) {
  113. ui->buttonsRichDialog->hide();
  114. return;
  115. }
  116. connect(
  117. ui->button_cancel_rich, &QPushButton::clicked, this,
  118. [this](bool) {
  119. StopInputThread();
  120. QDialog::reject();
  121. },
  122. Qt::QueuedConnection);
  123. connect(
  124. ui->button_ok_rich, &QPushButton::clicked, this,
  125. [this](bool) {
  126. StopInputThread();
  127. QDialog::accept();
  128. },
  129. Qt::QueuedConnection);
  130. }
  131. void OverlayDialog::MoveAndResizeWindow() {
  132. const auto pos = parentWidget()->mapToGlobal(parentWidget()->rect().topLeft());
  133. const auto width = static_cast<float>(parentWidget()->width());
  134. const auto height = static_cast<float>(parentWidget()->height());
  135. // High DPI
  136. const float dpi_scale = screen()->logicalDotsPerInch() / 96.0f;
  137. const auto title_text_font_size = BASE_TITLE_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale;
  138. const auto body_text_font_size =
  139. BASE_FONT_SIZE * (((width / BASE_WIDTH) + (height / BASE_HEIGHT)) / 2.0f) / dpi_scale;
  140. const auto button_text_font_size = BASE_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale;
  141. QFont title_text_font(QStringLiteral("MS Shell Dlg 2"), title_text_font_size, QFont::Normal);
  142. QFont body_text_font(QStringLiteral("MS Shell Dlg 2"), body_text_font_size, QFont::Normal);
  143. QFont button_text_font(QStringLiteral("MS Shell Dlg 2"), button_text_font_size, QFont::Normal);
  144. if (use_rich_text) {
  145. ui->label_title_rich->setFont(title_text_font);
  146. ui->text_browser_dialog->setFont(body_text_font);
  147. ui->button_cancel_rich->setFont(button_text_font);
  148. ui->button_ok_rich->setFont(button_text_font);
  149. } else {
  150. ui->label_title->setFont(title_text_font);
  151. ui->label_dialog->setFont(body_text_font);
  152. ui->button_cancel->setFont(button_text_font);
  153. ui->button_ok_label->setFont(button_text_font);
  154. }
  155. QDialog::move(pos);
  156. QDialog::resize(width, height);
  157. }
  158. template <Core::HID::NpadButton... T>
  159. void OverlayDialog::HandleButtonPressedOnce() {
  160. const auto f = [this](Core::HID::NpadButton button) {
  161. if (input_interpreter->IsButtonPressedOnce(button)) {
  162. TranslateButtonPress(button);
  163. }
  164. };
  165. (f(T), ...);
  166. }
  167. void OverlayDialog::TranslateButtonPress(Core::HID::NpadButton button) {
  168. QPushButton* left_button = use_rich_text ? ui->button_cancel_rich : ui->button_cancel;
  169. QPushButton* right_button = use_rich_text ? ui->button_ok_rich : ui->button_ok_label;
  170. // TODO (Morph): Handle QTextBrowser text scrolling
  171. // TODO (Morph): focusPrevious/NextChild() doesn't work well with the rich text dialog, fix it
  172. switch (button) {
  173. case Core::HID::NpadButton::A:
  174. case Core::HID::NpadButton::B:
  175. if (left_button->hasFocus()) {
  176. left_button->click();
  177. } else if (right_button->hasFocus()) {
  178. right_button->click();
  179. }
  180. break;
  181. case Core::HID::NpadButton::Left:
  182. case Core::HID::NpadButton::StickLLeft:
  183. focusPreviousChild();
  184. break;
  185. case Core::HID::NpadButton::Right:
  186. case Core::HID::NpadButton::StickLRight:
  187. focusNextChild();
  188. break;
  189. default:
  190. break;
  191. }
  192. }
  193. void OverlayDialog::StartInputThread() {
  194. if (input_thread_running) {
  195. return;
  196. }
  197. input_thread_running = true;
  198. input_thread = std::thread(&OverlayDialog::InputThread, this);
  199. }
  200. void OverlayDialog::StopInputThread() {
  201. input_thread_running = false;
  202. if (input_thread.joinable()) {
  203. input_thread.join();
  204. }
  205. }
  206. void OverlayDialog::InputThread() {
  207. while (input_thread_running) {
  208. input_interpreter->PollInput();
  209. HandleButtonPressedOnce<Core::HID::NpadButton::A, Core::HID::NpadButton::B,
  210. Core::HID::NpadButton::Left, Core::HID::NpadButton::Right,
  211. Core::HID::NpadButton::StickLLeft,
  212. Core::HID::NpadButton::StickLRight>();
  213. std::this_thread::sleep_for(std::chrono::milliseconds(50));
  214. }
  215. }
  216. void OverlayDialog::keyPressEvent(QKeyEvent* e) {
  217. if (!ui->buttonsDialog->isHidden() || e->key() != Qt::Key_Escape) {
  218. QDialog::keyPressEvent(e);
  219. }
  220. }