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