stick_from_buttons.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. // SPDX-FileCopyrightText: 2017 Citra Emulator Project
  2. // SPDX-License-Identifier: GPL-2.0-or-later
  3. #include <chrono>
  4. #include <cmath>
  5. #include "common/math_util.h"
  6. #include "common/settings.h"
  7. #include "input_common/helpers/stick_from_buttons.h"
  8. namespace InputCommon {
  9. class Stick final : public Common::Input::InputDevice {
  10. public:
  11. using Button = std::unique_ptr<Common::Input::InputDevice>;
  12. Stick(Button up_, Button down_, Button left_, Button right_, Button modifier_,
  13. float modifier_scale_, float modifier_angle_)
  14. : up(std::move(up_)), down(std::move(down_)), left(std::move(left_)),
  15. right(std::move(right_)), modifier(std::move(modifier_)), modifier_scale(modifier_scale_),
  16. modifier_angle(modifier_angle_) {
  17. up->SetCallback({
  18. .on_change =
  19. [this](const Common::Input::CallbackStatus& callback_) {
  20. UpdateUpButtonStatus(callback_);
  21. },
  22. });
  23. down->SetCallback({
  24. .on_change =
  25. [this](const Common::Input::CallbackStatus& callback_) {
  26. UpdateDownButtonStatus(callback_);
  27. },
  28. });
  29. left->SetCallback({
  30. .on_change =
  31. [this](const Common::Input::CallbackStatus& callback_) {
  32. UpdateLeftButtonStatus(callback_);
  33. },
  34. });
  35. right->SetCallback({
  36. .on_change =
  37. [this](const Common::Input::CallbackStatus& callback_) {
  38. UpdateRightButtonStatus(callback_);
  39. },
  40. });
  41. modifier->SetCallback({
  42. .on_change =
  43. [this](const Common::Input::CallbackStatus& callback_) {
  44. UpdateModButtonStatus(callback_);
  45. },
  46. });
  47. last_x_axis_value = 0.0f;
  48. last_y_axis_value = 0.0f;
  49. }
  50. bool IsAngleGreater(float old_angle, float new_angle) const {
  51. constexpr float TAU = Common::PI * 2.0f;
  52. // Use wider angle to ease the transition.
  53. constexpr float aperture = TAU * 0.15f;
  54. const float top_limit = new_angle + aperture;
  55. return (old_angle > new_angle && old_angle <= top_limit) ||
  56. (old_angle + TAU > new_angle && old_angle + TAU <= top_limit);
  57. }
  58. bool IsAngleSmaller(float old_angle, float new_angle) const {
  59. constexpr float TAU = Common::PI * 2.0f;
  60. // Use wider angle to ease the transition.
  61. constexpr float aperture = TAU * 0.15f;
  62. const float bottom_limit = new_angle - aperture;
  63. return (old_angle >= bottom_limit && old_angle < new_angle) ||
  64. (old_angle - TAU >= bottom_limit && old_angle - TAU < new_angle);
  65. }
  66. float GetAngle(std::chrono::time_point<std::chrono::steady_clock> now) const {
  67. constexpr float TAU = Common::PI * 2.0f;
  68. float new_angle = angle;
  69. auto time_difference = static_cast<float>(
  70. std::chrono::duration_cast<std::chrono::microseconds>(now - last_update).count());
  71. time_difference /= 1000.0f * 1000.0f;
  72. if (time_difference > 0.5f) {
  73. time_difference = 0.5f;
  74. }
  75. if (IsAngleGreater(new_angle, goal_angle)) {
  76. new_angle -= modifier_angle * time_difference;
  77. if (new_angle < 0) {
  78. new_angle += TAU;
  79. }
  80. if (!IsAngleGreater(new_angle, goal_angle)) {
  81. return goal_angle;
  82. }
  83. } else if (IsAngleSmaller(new_angle, goal_angle)) {
  84. new_angle += modifier_angle * time_difference;
  85. if (new_angle >= TAU) {
  86. new_angle -= TAU;
  87. }
  88. if (!IsAngleSmaller(new_angle, goal_angle)) {
  89. return goal_angle;
  90. }
  91. } else {
  92. return goal_angle;
  93. }
  94. return new_angle;
  95. }
  96. void SetGoalAngle(bool r, bool l, bool u, bool d) {
  97. // Move to the right
  98. if (r && !u && !d) {
  99. goal_angle = 0.0f;
  100. }
  101. // Move to the upper right
  102. if (r && u && !d) {
  103. goal_angle = Common::PI * 0.25f;
  104. }
  105. // Move up
  106. if (u && !l && !r) {
  107. goal_angle = Common::PI * 0.5f;
  108. }
  109. // Move to the upper left
  110. if (l && u && !d) {
  111. goal_angle = Common::PI * 0.75f;
  112. }
  113. // Move to the left
  114. if (l && !u && !d) {
  115. goal_angle = Common::PI;
  116. }
  117. // Move to the bottom left
  118. if (l && !u && d) {
  119. goal_angle = Common::PI * 1.25f;
  120. }
  121. // Move down
  122. if (d && !l && !r) {
  123. goal_angle = Common::PI * 1.5f;
  124. }
  125. // Move to the bottom right
  126. if (r && !u && d) {
  127. goal_angle = Common::PI * 1.75f;
  128. }
  129. }
  130. void UpdateUpButtonStatus(const Common::Input::CallbackStatus& button_callback) {
  131. up_status = button_callback.button_status.value;
  132. UpdateStatus();
  133. }
  134. void UpdateDownButtonStatus(const Common::Input::CallbackStatus& button_callback) {
  135. down_status = button_callback.button_status.value;
  136. UpdateStatus();
  137. }
  138. void UpdateLeftButtonStatus(const Common::Input::CallbackStatus& button_callback) {
  139. left_status = button_callback.button_status.value;
  140. UpdateStatus();
  141. }
  142. void UpdateRightButtonStatus(const Common::Input::CallbackStatus& button_callback) {
  143. right_status = button_callback.button_status.value;
  144. UpdateStatus();
  145. }
  146. void UpdateModButtonStatus(const Common::Input::CallbackStatus& button_callback) {
  147. const auto& new_status = button_callback.button_status;
  148. const bool new_button_value = new_status.inverted ? !new_status.value : new_status.value;
  149. modifier_status.toggle = new_status.toggle;
  150. // Update button status with current
  151. if (!modifier_status.toggle) {
  152. modifier_status.locked = false;
  153. if (modifier_status.value != new_button_value) {
  154. modifier_status.value = new_button_value;
  155. }
  156. } else {
  157. // Toggle button and lock status
  158. if (new_button_value && !modifier_status.locked) {
  159. modifier_status.locked = true;
  160. modifier_status.value = !modifier_status.value;
  161. }
  162. // Unlock button ready for next press
  163. if (!new_button_value && modifier_status.locked) {
  164. modifier_status.locked = false;
  165. }
  166. }
  167. UpdateStatus();
  168. }
  169. void UpdateStatus() {
  170. const float coef = modifier_status.value ? modifier_scale : 1.0f;
  171. bool r = right_status;
  172. bool l = left_status;
  173. bool u = up_status;
  174. bool d = down_status;
  175. // Eliminate contradictory movements
  176. if (r && l) {
  177. r = false;
  178. l = false;
  179. }
  180. if (u && d) {
  181. u = false;
  182. d = false;
  183. }
  184. // Move if a key is pressed
  185. if (r || l || u || d) {
  186. amplitude = coef;
  187. } else {
  188. amplitude = 0;
  189. }
  190. const auto now = std::chrono::steady_clock::now();
  191. const auto time_difference = static_cast<u64>(
  192. std::chrono::duration_cast<std::chrono::milliseconds>(now - last_update).count());
  193. if (time_difference < 10) {
  194. // Disable analog mode if inputs are too fast
  195. SetGoalAngle(r, l, u, d);
  196. angle = goal_angle;
  197. } else {
  198. angle = GetAngle(now);
  199. SetGoalAngle(r, l, u, d);
  200. }
  201. last_update = now;
  202. Common::Input::CallbackStatus status{
  203. .type = Common::Input::InputType::Stick,
  204. .stick_status = GetStatus(),
  205. };
  206. last_x_axis_value = status.stick_status.x.raw_value;
  207. last_y_axis_value = status.stick_status.y.raw_value;
  208. TriggerOnChange(status);
  209. }
  210. void ForceUpdate() override {
  211. up->ForceUpdate();
  212. down->ForceUpdate();
  213. left->ForceUpdate();
  214. right->ForceUpdate();
  215. modifier->ForceUpdate();
  216. }
  217. void SoftUpdate() override {
  218. Common::Input::CallbackStatus status{
  219. .type = Common::Input::InputType::Stick,
  220. .stick_status = GetStatus(),
  221. };
  222. if (last_x_axis_value == status.stick_status.x.raw_value &&
  223. last_y_axis_value == status.stick_status.y.raw_value) {
  224. return;
  225. }
  226. last_x_axis_value = status.stick_status.x.raw_value;
  227. last_y_axis_value = status.stick_status.y.raw_value;
  228. TriggerOnChange(status);
  229. }
  230. Common::Input::StickStatus GetStatus() const {
  231. Common::Input::StickStatus status{};
  232. status.x.properties = properties;
  233. status.y.properties = properties;
  234. if (Settings::values.emulate_analog_keyboard) {
  235. const auto now = std::chrono::steady_clock::now();
  236. float angle_ = GetAngle(now);
  237. status.x.raw_value = std::cos(angle_) * amplitude;
  238. status.y.raw_value = std::sin(angle_) * amplitude;
  239. return status;
  240. }
  241. constexpr float SQRT_HALF = 0.707106781f;
  242. int x = 0, y = 0;
  243. if (right_status) {
  244. ++x;
  245. }
  246. if (left_status) {
  247. --x;
  248. }
  249. if (up_status) {
  250. ++y;
  251. }
  252. if (down_status) {
  253. --y;
  254. }
  255. const float coef = modifier_status.value ? modifier_scale : 1.0f;
  256. status.x.raw_value = static_cast<float>(x) * coef * (y == 0 ? 1.0f : SQRT_HALF);
  257. status.y.raw_value = static_cast<float>(y) * coef * (x == 0 ? 1.0f : SQRT_HALF);
  258. return status;
  259. }
  260. private:
  261. Button up;
  262. Button down;
  263. Button left;
  264. Button right;
  265. Button modifier;
  266. float modifier_scale{};
  267. float modifier_angle{};
  268. float angle{};
  269. float goal_angle{};
  270. float amplitude{};
  271. bool up_status{};
  272. bool down_status{};
  273. bool left_status{};
  274. bool right_status{};
  275. float last_x_axis_value{};
  276. float last_y_axis_value{};
  277. Common::Input::ButtonStatus modifier_status{};
  278. const Common::Input::AnalogProperties properties{0.0f, 1.0f, 0.5f, 0.0f, false};
  279. std::chrono::time_point<std::chrono::steady_clock> last_update;
  280. };
  281. std::unique_ptr<Common::Input::InputDevice> StickFromButton::Create(
  282. const Common::ParamPackage& params) {
  283. const std::string null_engine = Common::ParamPackage{{"engine", "null"}}.Serialize();
  284. auto up = Common::Input::CreateInputDeviceFromString(params.Get("up", null_engine));
  285. auto down = Common::Input::CreateInputDeviceFromString(params.Get("down", null_engine));
  286. auto left = Common::Input::CreateInputDeviceFromString(params.Get("left", null_engine));
  287. auto right = Common::Input::CreateInputDeviceFromString(params.Get("right", null_engine));
  288. auto modifier = Common::Input::CreateInputDeviceFromString(params.Get("modifier", null_engine));
  289. auto modifier_scale = params.Get("modifier_scale", 0.5f);
  290. auto modifier_angle = params.Get("modifier_angle", 5.5f);
  291. return std::make_unique<Stick>(std::move(up), std::move(down), std::move(left),
  292. std::move(right), std::move(modifier), modifier_scale,
  293. modifier_angle);
  294. }
  295. } // namespace InputCommon