stick_from_buttons.cpp 12 KB

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