TASInputWindow.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. // Copyright 2018 Dolphin Emulator Project
  2. // SPDX-License-Identifier: GPL-2.0-or-later
  3. #include "DolphinQt/TAS/TASInputWindow.h"
  4. #include <cmath>
  5. #include <utility>
  6. #include <QApplication>
  7. #include <QCheckBox>
  8. #include <QEvent>
  9. #include <QGroupBox>
  10. #include <QHBoxLayout>
  11. #include <QLabel>
  12. #include <QShortcut>
  13. #include <QSlider>
  14. #include <QSpinBox>
  15. #include <QVBoxLayout>
  16. #include "Common/CommonTypes.h"
  17. #include "DolphinQt/Host.h"
  18. #include "DolphinQt/QtUtils/AspectRatioWidget.h"
  19. #include "DolphinQt/QtUtils/QueueOnObject.h"
  20. #include "DolphinQt/Resources.h"
  21. #include "DolphinQt/TAS/StickWidget.h"
  22. #include "DolphinQt/TAS/TASCheckBox.h"
  23. #include "DolphinQt/TAS/TASSlider.h"
  24. #include "DolphinQt/TAS/TASSpinBox.h"
  25. #include "InputCommon/ControllerEmu/ControllerEmu.h"
  26. #include "InputCommon/ControllerEmu/StickGate.h"
  27. void InputOverrider::AddFunction(std::string_view group_name, std::string_view control_name,
  28. OverrideFunction function)
  29. {
  30. m_functions.emplace(std::make_pair(group_name, control_name), std::move(function));
  31. }
  32. ControllerEmu::InputOverrideFunction InputOverrider::GetInputOverrideFunction() const
  33. {
  34. return [this](std::string_view group_name, std::string_view control_name,
  35. ControlState controller_state) {
  36. const auto it = m_functions.find(std::make_pair(group_name, control_name));
  37. return it != m_functions.end() ? it->second(controller_state) : std::nullopt;
  38. };
  39. }
  40. TASInputWindow::TASInputWindow(QWidget* parent) : QDialog(parent)
  41. {
  42. setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
  43. setWindowIcon(Resources::GetAppIcon());
  44. QGridLayout* settings_layout = new QGridLayout;
  45. m_use_controller = new QCheckBox(tr("Enable Controller Inpu&t"));
  46. m_use_controller->setToolTip(tr("Warning: Analog inputs may reset to controller values at "
  47. "random. In some cases this can be fixed by adding a deadzone."));
  48. settings_layout->addWidget(m_use_controller, 0, 0, 1, 2);
  49. QLabel* turbo_press_label = new QLabel(tr("Duration of Turbo Button Press (frames):"));
  50. m_turbo_press_frames = new QSpinBox();
  51. m_turbo_press_frames->setMinimum(1);
  52. settings_layout->addWidget(turbo_press_label, 1, 0);
  53. settings_layout->addWidget(m_turbo_press_frames, 1, 1);
  54. QLabel* turbo_release_label = new QLabel(tr("Duration of Turbo Button Release (frames):"));
  55. m_turbo_release_frames = new QSpinBox();
  56. m_turbo_release_frames->setMinimum(1);
  57. settings_layout->addWidget(turbo_release_label, 2, 0);
  58. settings_layout->addWidget(m_turbo_release_frames, 2, 1);
  59. m_settings_box = new QGroupBox(tr("Settings"));
  60. m_settings_box->setLayout(settings_layout);
  61. }
  62. int TASInputWindow::GetTurboPressFrames() const
  63. {
  64. return m_turbo_press_frames->value();
  65. }
  66. int TASInputWindow::GetTurboReleaseFrames() const
  67. {
  68. return m_turbo_release_frames->value();
  69. }
  70. TASCheckBox* TASInputWindow::CreateButton(const QString& text, std::string_view group_name,
  71. std::string_view control_name, InputOverrider* overrider)
  72. {
  73. TASCheckBox* checkbox = new TASCheckBox(text, this);
  74. overrider->AddFunction(group_name, control_name, [this, checkbox](ControlState controller_state) {
  75. return GetButton(checkbox, controller_state);
  76. });
  77. return checkbox;
  78. }
  79. QGroupBox* TASInputWindow::CreateStickInputs(const QString& text, std::string_view group_name,
  80. InputOverrider* overrider, int min_x, int min_y,
  81. int max_x, int max_y, Qt::Key x_shortcut_key,
  82. Qt::Key y_shortcut_key)
  83. {
  84. const QKeySequence x_shortcut_key_sequence = QKeySequence(Qt::ALT | x_shortcut_key);
  85. const QKeySequence y_shortcut_key_sequence = QKeySequence(Qt::ALT | y_shortcut_key);
  86. auto* box =
  87. new QGroupBox(QStringLiteral("%1 (%2/%3)")
  88. .arg(text, x_shortcut_key_sequence.toString(QKeySequence::NativeText),
  89. y_shortcut_key_sequence.toString(QKeySequence::NativeText)));
  90. const int x_default = static_cast<int>(std::round(max_x / 2.));
  91. const int y_default = static_cast<int>(std::round(max_y / 2.));
  92. auto* x_layout = new QHBoxLayout;
  93. TASSpinBox* x_value = CreateSliderValuePair(x_layout, x_default, max_x, x_shortcut_key_sequence,
  94. Qt::Horizontal, box);
  95. auto* y_layout = new QVBoxLayout;
  96. TASSpinBox* y_value =
  97. CreateSliderValuePair(y_layout, y_default, max_y, y_shortcut_key_sequence, Qt::Vertical, box);
  98. y_value->setMaximumWidth(60);
  99. auto* visual = new StickWidget(this, max_x, max_y);
  100. visual->SetX(x_default);
  101. visual->SetY(y_default);
  102. connect(x_value, &QSpinBox::valueChanged, visual, &StickWidget::SetX);
  103. connect(y_value, &QSpinBox::valueChanged, visual, &StickWidget::SetY);
  104. connect(visual, &StickWidget::ChangedX, x_value, &QSpinBox::setValue);
  105. connect(visual, &StickWidget::ChangedY, y_value, &QSpinBox::setValue);
  106. auto* visual_ar = new AspectRatioWidget(visual, max_x, max_y);
  107. auto* visual_layout = new QHBoxLayout;
  108. visual_layout->addWidget(visual_ar);
  109. visual_layout->addLayout(y_layout);
  110. auto* layout = new QVBoxLayout;
  111. layout->addLayout(x_layout);
  112. layout->addLayout(visual_layout);
  113. box->setLayout(layout);
  114. overrider->AddFunction(group_name, ControllerEmu::ReshapableInput::X_INPUT_OVERRIDE,
  115. [this, x_value, x_default, min_x, max_x](ControlState controller_state) {
  116. return GetSpinBox(x_value, x_default, min_x, max_x, controller_state);
  117. });
  118. overrider->AddFunction(group_name, ControllerEmu::ReshapableInput::Y_INPUT_OVERRIDE,
  119. [this, y_value, y_default, min_y, max_y](ControlState controller_state) {
  120. return GetSpinBox(y_value, y_default, min_y, max_y, controller_state);
  121. });
  122. return box;
  123. }
  124. QBoxLayout* TASInputWindow::CreateSliderValuePairLayout(
  125. const QString& text, std::string_view group_name, std::string_view control_name,
  126. InputOverrider* overrider, int zero, int default_, int min, int max, Qt::Key shortcut_key,
  127. QWidget* shortcut_widget, std::optional<ControlState> scale)
  128. {
  129. const QKeySequence shortcut_key_sequence = QKeySequence(Qt::ALT | shortcut_key);
  130. auto* label = new QLabel(QStringLiteral("%1 (%2)").arg(
  131. text, shortcut_key_sequence.toString(QKeySequence::NativeText)));
  132. QBoxLayout* layout = new QHBoxLayout;
  133. layout->addWidget(label);
  134. CreateSliderValuePair(group_name, control_name, overrider, layout, zero, default_, min, max,
  135. shortcut_key_sequence, Qt::Horizontal, shortcut_widget, scale);
  136. return layout;
  137. }
  138. TASSpinBox* TASInputWindow::CreateSliderValuePair(
  139. std::string_view group_name, std::string_view control_name, InputOverrider* overrider,
  140. QBoxLayout* layout, int zero, int default_, int min, int max,
  141. QKeySequence shortcut_key_sequence, Qt::Orientation orientation, QWidget* shortcut_widget,
  142. std::optional<ControlState> scale)
  143. {
  144. TASSpinBox* value = CreateSliderValuePair(layout, default_, max, shortcut_key_sequence,
  145. orientation, shortcut_widget);
  146. InputOverrider::OverrideFunction func;
  147. if (scale)
  148. {
  149. func = [this, value, zero, scale](ControlState controller_state) {
  150. return GetSpinBox(value, zero, controller_state, *scale);
  151. };
  152. }
  153. else
  154. {
  155. func = [this, value, zero, min, max](ControlState controller_state) {
  156. return GetSpinBox(value, zero, min, max, controller_state);
  157. };
  158. }
  159. overrider->AddFunction(group_name, control_name, std::move(func));
  160. return value;
  161. }
  162. // The shortcut_widget argument needs to specify the container widget that will be hidden/shown.
  163. // This is done to avoid ambigous shortcuts
  164. TASSpinBox* TASInputWindow::CreateSliderValuePair(QBoxLayout* layout, int default_, int max,
  165. QKeySequence shortcut_key_sequence,
  166. Qt::Orientation orientation,
  167. QWidget* shortcut_widget)
  168. {
  169. auto* value = new TASSpinBox();
  170. value->setRange(0, 99999);
  171. value->setValue(default_);
  172. connect(value, &QSpinBox::valueChanged, [value, max](int i) {
  173. if (i > max)
  174. value->setValue(max);
  175. });
  176. auto* slider = new TASSlider(default_, orientation);
  177. slider->setRange(0, max);
  178. slider->setValue(default_);
  179. slider->setFocusPolicy(Qt::ClickFocus);
  180. connect(slider, &QSlider::valueChanged, value, &QSpinBox::setValue);
  181. connect(value, &QSpinBox::valueChanged, slider, &QSlider::setValue);
  182. auto* shortcut = new QShortcut(shortcut_key_sequence, shortcut_widget);
  183. connect(shortcut, &QShortcut::activated, [value] {
  184. value->setFocus();
  185. value->selectAll();
  186. });
  187. layout->addWidget(slider);
  188. layout->addWidget(value);
  189. if (orientation == Qt::Vertical)
  190. layout->setAlignment(slider, Qt::AlignRight);
  191. return value;
  192. }
  193. std::optional<ControlState> TASInputWindow::GetButton(TASCheckBox* checkbox,
  194. ControlState controller_state)
  195. {
  196. const bool pressed = std::llround(controller_state) > 0;
  197. if (m_use_controller->isChecked())
  198. checkbox->OnControllerValueChanged(pressed);
  199. return checkbox->GetValue() ? 1.0 : 0.0;
  200. }
  201. std::optional<ControlState> TASInputWindow::GetSpinBox(TASSpinBox* spin, int zero, int min, int max,
  202. ControlState controller_state)
  203. {
  204. const int controller_value =
  205. ControllerEmu::EmulatedController::MapFloat<int>(controller_state, zero, 0, max);
  206. if (m_use_controller->isChecked())
  207. spin->OnControllerValueChanged(controller_value);
  208. return ControllerEmu::EmulatedController::MapToFloat<ControlState, int>(spin->GetValue(), zero,
  209. min, max);
  210. }
  211. std::optional<ControlState> TASInputWindow::GetSpinBox(TASSpinBox* spin, int zero,
  212. ControlState controller_state,
  213. ControlState scale)
  214. {
  215. const int controller_value = static_cast<int>(std::llround(controller_state * scale + zero));
  216. if (m_use_controller->isChecked())
  217. spin->OnControllerValueChanged(controller_value);
  218. return (spin->GetValue() - zero) / scale;
  219. }
  220. void TASInputWindow::changeEvent(QEvent* const event)
  221. {
  222. if (event->type() == QEvent::ActivationChange)
  223. {
  224. const bool active_window_is_tas_input =
  225. qobject_cast<TASInputWindow*>(QApplication::activeWindow()) != nullptr;
  226. // Switching between TAS Input windows will call SetTASInputFocus(true) twice, but that's fine.
  227. Host::GetInstance()->SetTASInputFocus(active_window_is_tas_input);
  228. }
  229. QDialog::changeEvent(event);
  230. }